]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/content/hints.js
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / content / hints.js
index a30ef295057f33655020e429bf58aacb61b918f0..004f90716208469a8c42eaa10e952bb680ed1f91 100644 (file)
@@ -4,7 +4,7 @@
 //
 // 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 */
 
 /** @scope modules */
 /** @instance hints */
@@ -17,9 +17,8 @@ var HintSession = Class("HintSession", CommandMode, {
 
         opts = opts || {};
 
-        // Hack.
-        if (!opts.window && modes.main == modes.OUTPUT_MULTILINE)
-            opts.window = commandline.widgets.multilineOutput.contentWindow;
+        if (!opts.window)
+            opts.window = modes.getStack(0).params.window;
 
         this.hintMode = hints.modes[mode];
         dactyl.assert(this.hintMode);
@@ -27,7 +26,7 @@ var HintSession = Class("HintSession", CommandMode, {
         this.activeTimeout = null; // needed for hinttimeout > 0
         this.continue = Boolean(opts.continue);
         this.docs = [];
-        this.hintKeys = events.fromString(options["hintkeys"]).map(events.closure.toString);
+        this.hintKeys = DOM.Event.parse(options["hintkeys"]).map(DOM.Event.closure.stringify);
         this.hintNumber = 0;
         this.hintString = opts.filter || "";
         this.pageHints = [];
@@ -35,6 +34,7 @@ var HintSession = Class("HintSession", CommandMode, {
         this.usedTabKey = false;
         this.validHints = []; // store the indices of the "hints" array with valid elements
 
+        mappings.pushCommand();
         this.open();
 
         this.top = opts.window || content;
@@ -98,6 +98,8 @@ var HintSession = Class("HintSession", CommandMode, {
         leave.superapply(this, arguments);
 
         if (!stack.push) {
+            mappings.popCommand();
+
             if (hints.hintSession == this)
                 hints.hintSession = null;
             if (this.top) {
@@ -255,7 +257,7 @@ var HintSession = Class("HintSession", CommandMode, {
     getContainerOffsets: function _getContainerOffsets(doc) {
         let body = doc.body || doc.documentElement;
         // TODO: getComputedStyle returns null for Facebook channel_iframe doc - probable Gecko bug.
-        let style = util.computedStyle(body);
+        let style = DOM(body).style;
 
         if (style && /^(absolute|fixed|relative)$/.test(style.position)) {
             let rect = body.getClientRects()[0];
@@ -298,7 +300,7 @@ var HintSession = Class("HintSession", CommandMode, {
                 return false;
 
             if (!rect.width || !rect.height)
-                if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && util.computedStyle(elem).float != "none" && isVisible(elem)))
+                if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && DOM(elem).style.float != "none" && isVisible(elem)))
                     return false;
 
             let computedStyle = doc.defaultView.getComputedStyle(elem, null);
@@ -309,12 +311,11 @@ var HintSession = Class("HintSession", CommandMode, {
 
         let body = doc.body || doc.querySelector("body");
         if (body) {
-            let fragment = util.xmlToDom(<div highlight="hints"/>, doc);
-            body.appendChild(fragment);
-            util.computedStyle(fragment).height; // Force application of binding.
-            let container = doc.getAnonymousElementByAttribute(fragment, "anonid", "hints") || fragment;
+            let fragment = DOM(<div highlight="hints"/>, doc).appendTo(body);
+            fragment.style.height; // Force application of binding.
+            let container = doc.getAnonymousElementByAttribute(fragment[0], "anonid", "hints") || fragment[0];
 
-            let baseNodeAbsolute = util.xmlToDom(<span highlight="Hint" style="display: none;"/>, doc);
+            let baseNode = DOM(<span highlight="Hint" style="display: none;"/>, doc)[0];
 
             let mode = this.hintMode;
             let res = mode.matcher(doc);
@@ -342,7 +343,7 @@ var HintSession = Class("HintSession", CommandMode, {
                 else
                     hint.text = elem.textContent.toLowerCase();
 
-                hint.span = baseNodeAbsolute.cloneNode(false);
+                hint.span = baseNode.cloneNode(false);
 
                 let leftPos = Math.max((rect.left + offsetX), offsetX);
                 let topPos  = Math.max((rect.top + offsetY), offsetY);
@@ -402,7 +403,7 @@ var HintSession = Class("HintSession", CommandMode, {
      */
     onKeyPress: function onKeyPress(eventList) {
         const KILL = false, PASS = true;
-        let key = events.toString(eventList[0]);
+        let key = DOM.Event.stringify(eventList[0]);
 
         this.clearTimeout();
 
@@ -500,6 +501,7 @@ var HintSession = Class("HintSession", CommandMode, {
                 this.timeout(next, 50);
         }).call(this);
 
+        mappings.pushCommand();
         if (!this.continue) {
             modes.pop();
             if (timeout)
@@ -509,6 +511,7 @@ var HintSession = Class("HintSession", CommandMode, {
         dactyl.trapErrors("action", this.hintMode,
                           elem, elem.href || elem.src || "",
                           this.extendedhintCount, top);
+        mappings.popCommand();
 
         this.timeout(function () {
             if (modes.main == modes.IGNORE && !this.continue)
@@ -532,7 +535,7 @@ var HintSession = Class("HintSession", CommandMode, {
             // Goddamn stupid fucking Gecko 1.x security manager bullshit.
             try { delete doc.dactylLabels; } catch (e) { doc.dactylLabels = undefined; }
 
-            for (let elem in util.evaluateXPath("//*[@dactyl:highlight='hints']", doc))
+            for (let elem in DOM.XPath("//*[@dactyl:highlight='hints']", doc))
                 elem.parentNode.removeChild(elem);
             for (let i in util.range(start, end + 1)) {
                 this.pageHints[i].ambiguous = false;
@@ -754,9 +757,9 @@ var Hints = Module("hints", {
         this.addMode("S", "Add a search keyword",                 function (elem) bookmarks.addSearchKeyword(elem));
         this.addMode("v", "View hint source",                     function (elem, loc) buffer.viewSource(loc, false));
         this.addMode("V", "View hint source in external editor",  function (elem, loc) buffer.viewSource(loc, true));
-        this.addMode("y", "Yank hint location",                   function (elem, loc) dactyl.clipboardWrite(loc, true));
-        this.addMode("Y", "Yank hint description",                function (elem) dactyl.clipboardWrite(elem.textContent || "", true));
-        this.addMode("c", "Open context menu",                    function (elem) buffer.openContextMenu(elem));
+        this.addMode("y", "Yank hint location",                   function (elem, loc) editor.setRegister(null, loc, true));
+        this.addMode("Y", "Yank hint description",                function (elem) editor.setRegister(null, elem.textContent || "", true));
+        this.addMode("c", "Open context menu",                    function (elem) DOM(elem).contextmenu());
         this.addMode("i", "Show image",                           function (elem) dactyl.open(elem.src));
         this.addMode("I", "Show image in a new tab",              function (elem) dactyl.open(elem.src, dactyl.NEW_TAB));
 
@@ -824,7 +827,7 @@ var Hints = Module("hints", {
 
         let type = elem.type;
 
-        if (elem instanceof HTMLInputElement && Set.has(util.editableInputs, elem.type))
+        if (DOM(elem).isInput)
             return [elem.value, false];
         else {
             for (let [, option] in Iterator(options["hintinputs"])) {
@@ -1031,15 +1034,21 @@ var Hints = Module("hints", {
 
     open: function open(mode, opts) {
         this._extendedhintCount = opts.count;
-        commandline.input(["Normal", mode], "", {
+
+        opts = opts || {};
+
+        mappings.pushCommand();
+        commandline.input(["Normal", mode], null, {
             autocomplete: false,
             completer: function (context) {
                 context.compare = function () 0;
                 context.completions = [[k, v.prompt] for ([k, v] in Iterator(hints.modes))];
             },
+            onCancel: mappings.closure.popCommand,
             onSubmit: function (arg) {
                 if (arg)
                     hints.show(arg, opts);
+                mappings.popCommand();
             },
             onChange: function (arg) {
                 if (Object.keys(hints.modes).some(function (m) m != arg && m.indexOf(arg) == 0))
@@ -1077,7 +1086,24 @@ var Hints = Module("hints", {
         this.hintSession = HintSession(mode, opts);
     }
 }, {
-    translitTable: Class.memoize(function () {
+    isVisible: function isVisible(elem, offScreen) {
+        let rect = elem.getBoundingClientRect();
+        if (!rect.width || !rect.height)
+            if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && DOM(elem).style.float != "none" && isVisible(elem)))
+                return false;
+
+        let win = elem.ownerDocument.defaultView;
+        if (offScreen && (rect.top + win.scrollY < 0 || rect.left + win.scrollX < 0 ||
+                          rect.bottom + win.scrollY > win.scrolMaxY + win.innerHeight ||
+                          rect.right + win.scrollX > win.scrolMaxX + win.innerWidth))
+            return false;
+
+        if (!DOM(elem).isVisible)
+            return false;
+        return true;
+    },
+
+    translitTable: Class.Memoize(function () {
         const table = {};
         [
             [0x00c0, 0x00c6, ["A"]], [0x00c7, 0x00c7, ["C"]],
@@ -1191,53 +1217,58 @@ var Hints = Module("hints", {
         });
     },
     mappings: function () {
-        var myModes = config.browserModes.concat(modes.OUTPUT_MULTILINE);
-        mappings.add(myModes, ["f"],
+        let bind = function bind(names, description, action, params)
+            mappings.add(config.browserModes, names, description,
+                         action, params);
+
+        bind(["f"],
             "Start Hints mode",
             function () { hints.show("o"); });
 
-        mappings.add(myModes, ["F"],
+        bind(["F"],
             "Start Hints mode, but open link in a new tab",
             function () { hints.show(options.get("activate").has("links") ? "t" : "b"); });
 
-        mappings.add(myModes, [";"],
+        bind([";"],
             "Start an extended hints mode",
             function ({ count }) { hints.open(";", { count: count }); },
             { count: true });
 
-        mappings.add(myModes, ["g;"],
+        bind(["g;"],
             "Start an extended hints mode and stay there until <Esc> is pressed",
             function ({ count }) { hints.open("g;", { continue: true, count: count }); },
             { count: true });
 
-        mappings.add(modes.HINTS, ["<Return>"],
+        let bind = function bind(names, description, action, params)
+            mappings.add([modes.HINTS], names, description,
+                         action, params);
+
+        bind(["<Return>"],
             "Follow the selected hint",
             function ({ self }) { self.update(true); });
 
-        mappings.add(modes.HINTS, ["<Tab>"],
+        bind(["<Tab>"],
             "Focus the next matching hint",
             function ({ self }) { self.tab(false); });
 
-        mappings.add(modes.HINTS, ["<S-Tab>"],
+        bind(["<S-Tab>"],
             "Focus the previous matching hint",
             function ({ self }) { self.tab(true); });
 
-        mappings.add(modes.HINTS, ["<BS>", "<C-h>"],
+        bind(["<BS>", "<C-h>"],
             "Delete the previous character",
             function ({ self }) self.backspace());
 
-        mappings.add(modes.HINTS, ["<Leader>"],
+        bind(["<Leader>"],
             "Toggle hint filtering",
             function ({ self }) { self.escapeNumbers = !self.escapeNumbers; });
     },
     options: function () {
-        function xpath(arg) util.makeXPath(arg);
-
         options.add(["extendedhinttags", "eht"],
             "XPath or CSS selector strings of hintable elements for extended hint modes",
             "regexpmap", {
                 "[iI]": "img",
-                "[asOTvVWy]": ["a[href]", "area[href]", "img[src]", "iframe[src]"],
+                "[asOTvVWy]": [":-moz-any-link", "area[href]", "img[src]", "iframe[src]"],
                 "[f]": "body",
                 "[F]": ["body", "code", "div", "html", "p", "pre", "span"],
                 "[S]": ["input:not([type=hidden])", "textarea", "button", "select"]
@@ -1249,23 +1280,23 @@ var Hints = Module("hints", {
                         res ? res.matcher : default_,
                 setter: function (vals) {
                     for (let value in values(vals))
-                        value.matcher = util.compileMatcher(Option.splitList(value.result));
+                        value.matcher = DOM.compileMatcher(Option.splitList(value.result));
                     return vals;
                 },
-                validator: util.validateMatcher
+                validator: DOM.validateMatcher
             });
 
         options.add(["hinttags", "ht"],
             "XPath or CSS selector strings of hintable elements for Hints mode",
-            "stringlist", "input:not([type=hidden]),a[href],area,iframe,textarea,button,select," +
+            "stringlist", ":-moz-any-link,area,button,iframe,input:not([type=hidden]),select,textarea," +
                           "[onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand]," +
                           "[tabindex],[role=link],[role=button],[contenteditable=true]",
             {
                 setter: function (values) {
-                    this.matcher = util.compileMatcher(values);
+                    this.matcher = DOM.compileMatcher(values);
                     return values;
                 },
-                validator: util.validateMatcher
+                validator: DOM.validateMatcher
             });
 
         options.add(["hintkeys", "hk"],
@@ -1277,7 +1308,7 @@ var Hints = Module("hints", {
                     "asdfg;lkjh": "Home Row"
                 },
                 validator: function (value) {
-                    let values = events.fromString(value).map(events.closure.toString);
+                    let values = DOM.Event.parse(value).map(DOM.Event.closure.stringify);
                     return Option.validIf(array.uniq(values).length === values.length && values.length > 1,
                                           _("option.hintkeys.duplicate"));
                 }