]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/finder.jsm
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / modules / finder.jsm
index e2d24bb020495fa74ee45d713d963ac31957f679..2696630422d24a1d0309d5eb8a36e06fb4cd5bb8 100644 (file)
@@ -2,18 +2,18 @@
 //
 // This work is licensed for reuse under an MIT license. Details are
 // given in the LICENSE.txt file included with this file.
-"use strict";
+/* use strict */
 
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("finder", {
     exports: ["RangeFind", "RangeFinder", "rangefinder"],
-    require: ["prefs"],
-    use: ["messages", "services", "util"]
+    require: ["prefs"]
 }, this);
 
-function equals(a, b) XPCNativeWrapper(a) == XPCNativeWrapper(b);
+this.lazyRequire("buffer", ["Buffer"]);
+this.lazyRequire("overlay", ["overlay"]);
 
-try {
+function equals(a, b) XPCNativeWrapper(a) == XPCNativeWrapper(b);
 
 /** @instance rangefinder */
 var RangeFinder = Module("rangefinder", {
@@ -25,13 +25,21 @@ 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() {
@@ -40,20 +48,33 @@ var RangeFinder = Module("rangefinder", {
         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 +85,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 +101,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 +120,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 +143,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 +157,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 +165,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 +202,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 +210,7 @@ var RangeFinder = Module("rangefinder", {
     /**
      * Clears all find highlighting.
      */
-    clear: function () {
+    clear: function clear() {
         if (this.rangeFind)
             this.rangeFind.highlight(true);
     }
@@ -205,8 +244,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);
             },
 
@@ -229,7 +269,7 @@ var RangeFinder = Module("rangefinder", {
             function () { rangefinder.openPrompt(modes.FIND_FORWARD); });
 
         mappings.add(myModes,
-            ["?", "<find-backward>"], "Find a pattern backward of the current caret position",
+            ["?", "<find-backward>", "<S-Slash>"], "Find a pattern backward of the current caret position",
             function () { rangefinder.openPrompt(modes.FIND_BACKWARD); });
 
         mappings.add(myModes,
@@ -257,7 +297,6 @@ var RangeFinder = Module("rangefinder", {
     },
     options: function (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 +318,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_<Return>",
             "boolean", true);
@@ -308,11 +361,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 +380,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 +404,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 +448,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 +496,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 +514,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 +578,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 +601,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 +664,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 +706,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 +725,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 +738,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 +750,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 +774,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 +796,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 +809,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 +817,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 +825,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 +836,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: