X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Ffinder.jsm;h=9c1897deb9859bd34c0bbf62404423de2fd31f75;hb=3d837eb266a3a01d424192aa4ec1a167366178c5;hp=e2d24bb020495fa74ee45d713d963ac31957f679;hpb=70740024f9c028c1fd63e1a1850ab062ff956054;p=dactyl.git diff --git a/common/modules/finder.jsm b/common/modules/finder.jsm index e2d24bb..9c1897d 100644 --- a/common/modules/finder.jsm +++ b/common/modules/finder.jsm @@ -1,19 +1,20 @@ -// Copyright (c) 2008-2011 by Kris Maglione +// Copyright (c) 2008-2012 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. "use strict"; -Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("finder", { exports: ["RangeFind", "RangeFinder", "rangefinder"], - require: ["prefs"], - use: ["messages", "services", "util"] -}, this); + require: ["prefs", "util"] +}); -function equals(a, b) XPCNativeWrapper(a) == XPCNativeWrapper(b); +lazyRequire("buffer", ["Buffer"]); +lazyRequire("overlay", ["overlay"]); -try { +function id(w) w.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils) + .outerWindowID; +function equals(a, b) id(a) == id(b); /** @instance rangefinder */ var RangeFinder = Module("rangefinder", { @@ -25,35 +26,56 @@ var RangeFinder = Module("rangefinder", { this.lastFindPattern = ""; }, + get content() { + let { window } = this.modes.getStack(0).params; + return window || this.window.content; + }, + get rangeFind() { - let find = modules.buffer.localStore.rangeFind; - if (find && find.stale || !isinstance(find, RangeFind)) + let find = overlay.getData(this.content.document, + "range-find", null); + + if (!isinstance(find, RangeFind) || find.stale) return this.rangeFind = null; return find; }, - set rangeFind(val) modules.buffer.localStore.rangeFind = val + set rangeFind(val) overlay.setData(this.content.document, + "range-find", val) }), init: function init() { prefs.safeSet("accessibility.typeaheadfind.autostart", false); - // The above should be sufficient, but: http://dactyl.sf.net/bmo/348187 + // The above should be sufficient, but: http://bugzil.la/348187 prefs.safeSet("accessibility.typeaheadfind", false); }, + cleanup: function cleanup() { + for (let doc in util.iterDocuments()) { + let find = overlay.getData(doc, "range-find", null); + if (find) + find.highlight(true); + + overlay.setData(doc, "range-find", null); + } + }, + get commandline() this.modules.commandline, get modes() this.modules.modes, get options() this.modules.options, - openPrompt: function (mode) { + openPrompt: function openPrompt(mode) { + this.modules.marks.push(); this.commandline; - this.CommandMode(mode).open(); + this.CommandMode(mode, this.content).open(); + + Buffer(this.content).resetCaret(); if (this.rangeFind && equals(this.rangeFind.window.get(), this.window)) this.rangeFind.reset(); this.find("", mode == this.modes.FIND_BACKWARD); }, - bootstrap: function (str, backward) { + bootstrap: function bootstrap(str, backward) { if (arguments.length < 2 && this.rangeFind) backward = this.rangeFind.reverse; @@ -64,7 +86,7 @@ var RangeFinder = Module("rangefinder", { let matchCase = this.options["findcase"] === "smart" ? /[A-Z]/.test(str) : this.options["findcase"] === "ignore" ? false : true; - str = str.replace(/\\(.|$)/g, function (m, n1) { + function replacer(m, n1) { if (n1 == "c") matchCase = false; else if (n1 == "C") @@ -80,8 +102,14 @@ var RangeFinder = Module("rangefinder", { else return m; return ""; - }); + } + this.options["findflags"].forEach(function (f) replacer(f, f)); + + let pattern = str.replace(/\\(.|$)/g, replacer); + + if (str) + this.lastFindPattern = str; // It's possible, with :tabdetach for instance, for the rangeFind to // actually move from one window to another, which breaks things. if (!this.rangeFind @@ -93,17 +121,22 @@ var RangeFinder = Module("rangefinder", { if (this.rangeFind) this.rangeFind.cancel(); - this.rangeFind = RangeFind(this.window, matchCase, backward, + this.rangeFind = null; + this.rangeFind = RangeFind(this.window, this.content, matchCase, backward, linksOnly && this.options.get("hinttags").matcher, regexp); this.rangeFind.highlighted = highlighted; this.rangeFind.selections = selections; } - return this.lastFindPattern = str; + this.rangeFind.pattern = str; + return pattern; }, - find: function (pattern, backwards) { + find: function find(pattern, backwards) { + this.modules.marks.push(); let str = this.bootstrap(pattern, backwards); + this.backward = this.rangeFind.reverse; + if (!this.rangeFind.find(str)) this.dactyl.echoerr(_("finder.notFound", pattern), this.commandline.FORCE_SINGLELINE); @@ -111,7 +144,8 @@ var RangeFinder = Module("rangefinder", { return this.rangeFind.found; }, - findAgain: function (reverse) { + findAgain: function findAgain(reverse) { + this.modules.marks.push(); if (!this.rangeFind) this.find(this.lastFindPattern); else if (!this.rangeFind.find(null, reverse)) @@ -124,7 +158,7 @@ var RangeFinder = Module("rangefinder", { | this.commandline.FORCE_SINGLELINE); } else - this.commandline.echo((this.rangeFind.backward ? "?" : "/") + this.lastFindPattern, + this.commandline.echo((this.rangeFind.backward ? "?" : "/") + this.rangeFind.pattern, "Normal", this.commandline.FORCE_SINGLELINE); if (this.options["hlfind"]) @@ -132,26 +166,32 @@ var RangeFinder = Module("rangefinder", { this.rangeFind.focus(); }, - onCancel: function () { + onCancel: function onCancel() { if (this.rangeFind) this.rangeFind.cancel(); }, - onChange: function (command) { + onChange: function onChange(command) { if (this.options["incfind"]) { command = this.bootstrap(command); this.rangeFind.find(command); } }, - onHistory: function () { + onHistory: function onHistory() { this.rangeFind.found = false; }, - onSubmit: function (command) { + onSubmit: function onSubmit(command) { + if (!command && this.lastFindPattern) { + this.find(this.lastFindPattern, this.backward); + this.findAgain(); + return; + } + if (!this.options["incfind"] || !this.rangeFind || !this.rangeFind.found) { this.clear(); - this.find(command || this.lastFindPattern, this.modes.extended & this.modes.FIND_BACKWARD); + this.find(command || this.lastFindPattern, this.backward); } if (this.options["hlfind"]) @@ -163,7 +203,7 @@ var RangeFinder = Module("rangefinder", { * Highlights all occurrences of the last sought for string in the * current buffer. */ - highlight: function () { + highlight: function highlight() { if (this.rangeFind) this.rangeFind.highlight(); }, @@ -171,7 +211,7 @@ var RangeFinder = Module("rangefinder", { /** * Clears all find highlighting. */ - clear: function () { + clear: function clear() { if (this.rangeFind) this.rangeFind.highlight(true); } @@ -205,8 +245,9 @@ var RangeFinder = Module("rangefinder", { commandline: function initCommandline(dactyl, modules, window) { const { rangefinder } = modules; rangefinder.CommandMode = Class("CommandFindMode", modules.CommandMode, { - init: function init(mode) { + init: function init(mode, window) { this.mode = mode; + this.window = window; init.supercall(this); }, @@ -220,7 +261,7 @@ var RangeFinder = Module("rangefinder", { get onSubmit() modules.rangefinder.closure.onSubmit }); }, - mappings: function (dactyl, modules, window) { + mappings: function initMappings(dactyl, modules, window) { const { Buffer, buffer, config, mappings, modes, rangefinder } = modules; var myModes = config.browserModes.concat([modes.CARET]); @@ -229,7 +270,7 @@ var RangeFinder = Module("rangefinder", { function () { rangefinder.openPrompt(modes.FIND_FORWARD); }); mappings.add(myModes, - ["?", ""], "Find a pattern backward of the current caret position", + ["?", "", ""], "Find a pattern backward of the current caret position", function () { rangefinder.openPrompt(modes.FIND_BACKWARD); }); mappings.add(myModes, @@ -255,9 +296,8 @@ var RangeFinder = Module("rangefinder", { }); }, - options: function (dactyl, modules, window) { + options: function initOptions(dactyl, modules, window) { const { options, rangefinder } = modules; - const { prefs } = require("prefs"); options.add(["hlfind", "hlf"], "Highlight all /find pattern matches on the current page after submission", @@ -279,6 +319,20 @@ var RangeFinder = Module("rangefinder", { } }); + options.add(["findflags", "ff"], + "Default flags for find invocations", + "charlist", "", + { + values: { + "c": "Ignore case", + "C": "Match case", + "r": "Perform a regular expression search", + "R": "Perform a plain string search", + "l": "Search only in links", + "L": "Search all text" + } + }); + options.add(["incfind", "if"], "Find a pattern incrementally as it is typed rather than awaiting c_", "boolean", true); @@ -308,11 +362,11 @@ var RangeFinder = Module("rangefinder", { * large amounts of data are concerned (e.g., for API documents). */ var RangeFind = Class("RangeFind", { - init: function init(window, matchCase, backward, elementPath, regexp) { - this.window = Cu.getWeakReference(window); - this.content = window.content; + init: function init(window, content, matchCase, backward, elementPath, regexp) { + this.window = util.weakReference(window); + this.content = content; - this.baseDocument = Cu.getWeakReference(this.content.document); + this.baseDocument = util.weakReference(this.content.document); this.elementPath = elementPath || null; this.reverse = Boolean(backward); @@ -327,25 +381,18 @@ var RangeFind = Class("RangeFind", { this.lastString = ""; }, - get store() this.content.document.dactylStore = this.content.document.dactylStore || {}, + get store() overlay.getData(this.content.document, "buffer", Object), get backward() this.finder.findBackwards, + set backward(val) this.finder.findBackwards = val, get matchCase() this.finder.caseSensitive, set matchCase(val) this.finder.caseSensitive = Boolean(val), - get regexp() this.finder.regularExpression || false, - set regexp(val) { - try { - return this.finder.regularExpression = Boolean(val); - } - catch (e) { - return false; - } - }, - get findString() this.lastString, + get flags() this.matchCase ? "" : "i", + get selectedRange() { let win = this.store.focusedFrame && this.store.focusedFrame.get() || this.content; @@ -358,13 +405,15 @@ var RangeFind = Class("RangeFind", { this.range.selectionController.scrollSelectionIntoView( this.range.selectionController.SELECTION_NORMAL, 0, false); - this.store.focusedFrame = Cu.getWeakReference(range.startContainer.ownerDocument.defaultView); + this.store.focusedFrame = util.weakReference(range.startContainer.ownerDocument.defaultView); }, cancel: function cancel() { this.purgeListeners(); - this.range.deselect(); - this.range.descroll(); + if (this.range) { + this.range.deselect(); + this.range.descroll(); + } }, compareRanges: function compareRanges(r1, r2) { @@ -400,8 +449,8 @@ var RangeFind = Class("RangeFind", { focus: function focus() { if (this.lastRange) - var node = util.evaluateXPath(RangeFind.selectNodePath, - this.lastRange.commonAncestorContainer).snapshotItem(0); + var node = DOM.XPath(RangeFind.selectNodePath, + this.lastRange.commonAncestorContainer).snapshotItem(0); if (node) { node.focus(); // Re-highlight collapsed selection @@ -448,7 +497,7 @@ var RangeFind = Class("RangeFind", { } }, - indexIter: function (private_) { + indexIter: function indexIter(private_) { let idx = this.range.index; if (this.backward) var groups = [util.range(idx + 1, 0, -1), util.range(this.ranges.length, idx, -1)]; @@ -466,22 +515,38 @@ var RangeFind = Class("RangeFind", { } }, - iter: function (word) { - let saved = ["lastRange", "lastString", "range"].map(function (s) [s, this[s]], this); + iter: function iter(word) { + let saved = ["lastRange", "lastString", "range", "regexp"].map(function (s) [s, this[s]], this); + let res; try { - this.range = this.ranges[0]; + let regexp = this.regexp && word != util.regexp.escape(word); this.lastRange = null; - this.lastString = word; - var res; - while (res = this.find(null, this.reverse, true)) - yield res; + this.regexp = false; + if (regexp) { + let re = RegExp(word, "gm" + this.flags); + for (this.range in array.iterValues(this.ranges)) { + for (let match in util.regexp.iterate(re, DOM.stringify(this.range.range, true))) { + let lastRange = this.lastRange; + if (res = this.find(null, this.reverse, true)) + yield res; + else + this.lastRange = lastRange; + } + } + } + else { + this.range = this.ranges[0]; + this.lastString = word; + while (res = this.find(null, this.reverse, true)) + yield res; + } } finally { saved.forEach(function ([k, v]) this[k] = v, this); } }, - makeFrameList: function (win) { + makeFrameList: function makeFrameList(win) { const self = this; win = win.top; let frames = []; @@ -514,7 +579,7 @@ var RangeFind = Class("RangeFind", { for (let frame in array.iterValues(win.frames)) { let range = doc.createRange(); - if (util.computedStyle(frame.frameElement).visibility == "visible") { + if (DOM(frame.frameElement).style.visibility == "visible") { range.selectNode(frame.frameElement); pushRange(pageStart, RangeFind.endpoint(range, true)); pageStart = RangeFind.endpoint(range, false); @@ -537,75 +602,53 @@ var RangeFind = Class("RangeFind", { return frames; }, - reset: function () { + reset: function reset() { this.ranges = this.makeFrameList(this.content); this.startRange = this.selectedRange; this.startRange.collapse(!this.reverse); this.lastRange = this.selectedRange; - this.range = this.findRange(this.startRange); + this.range = this.findRange(this.startRange) || this.ranges[0]; + util.assert(this.range, "Null range", false); this.ranges.first = this.range; this.ranges.forEach(function (range) range.save()); this.forward = null; this.found = false; }, - // This doesn't work yet. - resetCaret: function () { - let equal = RangeFind.equal; - let selection = this.win.getSelection(); - if (selection.rangeCount == 0) - selection.addRange(this.pageStart); - function getLines() { - let orig = selection.getRangeAt(0); - function getRanges(forward) { - selection.removeAllRanges(); - selection.addRange(orig); - let cur = orig; - while (true) { - var last = cur; - this.sel.lineMove(forward, false); - cur = selection.getRangeAt(0); - if (equal(cur, last)) - break; - yield cur; - } - } - yield orig; - for (let range in getRanges(true)) - yield range; - for (let range in getRanges(false)) - yield range; - } - for (let range in getLines()) { - if (this.sel.checkVisibility(range.startContainer, range.startOffset, range.startOffset)) - return range; - } - return null; - }, - - find: function (word, reverse, private_) { + find: function find(pattern, reverse, private_) { if (!private_ && this.lastRange && !RangeFind.equal(this.selectedRange, this.lastRange)) this.reset(); this.wrapped = false; - this.finder.findBackwards = reverse ? !this.reverse : this.reverse; - let again = word == null; + this.backward = reverse ? !this.reverse : this.reverse; + let again = pattern == null; if (again) - word = this.lastString; + pattern = this.lastString; if (!this.matchCase) - word = word.toLowerCase(); + pattern = pattern.toLowerCase(); - if (!again && (word === "" || word.indexOf(this.lastString) !== 0 || this.backward)) { + if (!again && (pattern === "" || pattern.indexOf(this.lastString) !== 0 || this.backward)) { if (!private_) this.range.deselect(); - if (word === "") + if (pattern === "") this.range.descroll(); this.lastRange = this.startRange; this.range = this.ranges.first; } - if (word == "") + let word = pattern; + let regexp = this.regexp && word != util.regexp.escape(word); + + if (regexp) + try { + RegExp(pattern); + } + catch (e) { + pattern = ""; + } + + if (pattern == "") var range = this.startRange; else for (let i in this.indexIter(private_)) { @@ -622,15 +665,33 @@ var RangeFind = Class("RangeFind", { if (this.backward && !again) start = RangeFind.endpoint(this.startRange, false); + if (regexp) { + let range = this.range.range.cloneRange(); + range[this.backward ? "setEnd" : "setStart"]( + start.startContainer, start.startOffset); + range = DOM.stringify(range); + + if (!this.backward) + var match = RegExp(pattern, "m" + this.flags).exec(range); + else { + match = RegExp("[^]*(?:" + pattern + ")", "m" + this.flags).exec(range); + if (match) + match = RegExp(pattern + "$", this.flags).exec(match[0]); + } + if (!(match && match[0])) + continue; + word = match[0]; + } + var range = this.finder.Find(word, this.range.range, start, this.range.range); - if (range) + if (range && DOM(range.commonAncestorContainer).isVisible) break; } if (range) this.lastRange = range.cloneRange(); if (!private_) { - this.lastString = word; + this.lastString = pattern; if (range == null) { this.cancel(); this.found = false; @@ -646,18 +707,18 @@ var RangeFind = Class("RangeFind", { get stale() this._stale || this.baseDocument.get() != this.content.document, set stale(val) this._stale = val, - addListeners: function () { + addListeners: function addListeners() { for (let range in array.iterValues(this.ranges)) range.window.addEventListener("unload", this.closure.onUnload, true); }, - purgeListeners: function () { + purgeListeners: function purgeListeners() { for (let range in array.iterValues(this.ranges)) try { range.window.removeEventListener("unload", this.closure.onUnload, true); } catch (e if e.result === Cr.NS_ERROR_FAILURE) {} }, - onUnload: function (event) { + onUnload: function onUnload(event) { this.purgeListeners(); if (this.highlighted) this.highlight(true); @@ -665,14 +726,12 @@ var RangeFind = Class("RangeFind", { } }, { Range: Class("RangeFind.Range", { - init: function (range, index) { + init: function init(range, index) { this.index = index; this.range = range; this.document = range.startContainer.ownerDocument; this.window = this.document.defaultView; - this.docShell = this.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell); if (this.selection == null) return false; @@ -680,9 +739,11 @@ var RangeFind = Class("RangeFind", { this.save(); }, + docShell: Class.Memoize(function () util.docShell(this.window)), + intersects: function (range) RangeFind.intersects(this.range, range), - save: function () { + save: function save() { this.scroll = Point(this.window.pageXOffset, this.window.pageYOffset); this.initialSelection = null; @@ -690,11 +751,11 @@ var RangeFind = Class("RangeFind", { this.initialSelection = this.selection.getRangeAt(0); }, - descroll: function () { + descroll: function descroll() { this.window.scrollTo(this.scroll.x, this.scroll.y); }, - deselect: function () { + deselect: function deselect() { if (this.selection) { this.selection.removeAllRanges(); if (this.initialSelection) @@ -714,17 +775,19 @@ var RangeFind = Class("RangeFind", { } } }), - contains: function (range, r) { + contains: function contains(range, r, quiet) { try { return range.compareBoundaryPoints(range.START_TO_END, r) >= 0 && range.compareBoundaryPoints(range.END_TO_START, r) <= 0; } catch (e) { - util.reportError(e, true); + if (e.result != Cr.NS_ERROR_DOM_WRONG_DOCUMENT_ERR && !quiet) + util.reportError(e, true); return false; } }, - intersects: function (range, r) { + containsNode: function containsNode(range, n, quiet) n.ownerDocument && this.contains(range, RangeFind.nodeRange(n), quiet), + intersects: function intersects(range, r) { try { return r.compareBoundaryPoints(range.START_TO_END, range) >= 0 && r.compareBoundaryPoints(range.END_TO_START, range) <= 0; @@ -734,12 +797,12 @@ var RangeFind = Class("RangeFind", { return false; } }, - endpoint: function (range, before) { + endpoint: function endpoint(range, before) { range = range.cloneRange(); range.collapse(before); return range; }, - equal: function (r1, r2) { + equal: function equal(r1, r2) { try { return !r1.compareBoundaryPoints(r1.START_TO_START, r2) && !r1.compareBoundaryPoints(r1.END_TO_END, r2); } @@ -747,7 +810,7 @@ var RangeFind = Class("RangeFind", { return false; } }, - nodeContents: function (node) { + nodeContents: function nodeContents(node) { let range = node.ownerDocument.createRange(); try { range.selectNodeContents(node); @@ -755,7 +818,7 @@ var RangeFind = Class("RangeFind", { catch (e) {} return range; }, - nodeRange: function (node) { + nodeRange: function nodeRange(node) { let range = node.ownerDocument.createRange(); try { range.selectNode(node); @@ -763,7 +826,7 @@ var RangeFind = Class("RangeFind", { catch (e) {} return range; }, - sameDocument: function (r1, r2) { + sameDocument: function sameDocument(r1, r2) { if (!(r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument)) return false; try { @@ -774,12 +837,18 @@ var RangeFind = Class("RangeFind", { } return true; }, - selectNodePath: ["a", "xhtml:a", "*[@onclick]"].map(function (p) "ancestor-or-self::" + p).join(" | ") + selectNodePath: ["a", "xhtml:a", "*[@onclick]"].map(function (p) "ancestor-or-self::" + p).join(" | "), + union: function union(a, b) { + let start = a.compareBoundaryPoints(a.START_TO_START, b) < 0 ? a : b; + let end = a.compareBoundaryPoints(a.END_TO_END, b) > 0 ? a : b; + let res = start.cloneRange(); + res.setEnd(end.endContainer, end.endOffset); + return res; + } }); -} catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } +// catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } endModule(); - -// vim: set fdm=marker sw=4 ts=4 et ft=javascript: +// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: