X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fcontent%2Fhints.js;h=d151c2c3d688f439cc802ea732309d23ec896495;hb=3d837eb266a3a01d424192aa4ec1a167366178c5;hp=004f90716208469a8c42eaa10e952bb680ed1f91;hpb=9044153cb63835e39b9de8ec4ade237c03e3888a;p=dactyl.git diff --git a/common/content/hints.js b/common/content/hints.js index 004f907..d151c2c 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -1,10 +1,10 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns -// 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 */ +"use strict"; /** @scope modules */ /** @instance hints */ @@ -301,7 +301,8 @@ var HintSession = Class("HintSession", CommandMode, { if (!rect.width || !rect.height) if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && DOM(elem).style.float != "none" && isVisible(elem))) - return false; + if (elem.textContent || !elem.name) + return false; let computedStyle = doc.defaultView.getComputedStyle(elem, null); if (computedStyle.visibility != "visible" || computedStyle.display == "none") @@ -311,11 +312,11 @@ var HintSession = Class("HintSession", CommandMode, { let body = doc.body || doc.querySelector("body"); if (body) { - let fragment = DOM(
, doc).appendTo(body); + 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 baseNode = DOM(, doc)[0]; + let baseNode = DOM(["span", { highlight: "Hint", style: "display: none;" }], doc)[0]; let mode = this.hintMode; let res = mode.matcher(doc); @@ -336,9 +337,11 @@ var HintSession = Class("HintSession", CommandMode, { if (elem.hasAttributeNS(NS, "hint")) [hint.text, hint.showText] = [elem.getAttributeNS(NS, "hint"), true]; - else if (isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement])) + else if (isinstance(elem, [Ci.nsIDOMHTMLInputElement, + Ci.nsIDOMHTMLSelectElement, + Ci.nsIDOMHTMLTextAreaElement])) [hint.text, hint.showText] = hints.getInputHint(elem, doc); - else if (elem.firstElementChild instanceof HTMLImageElement && /^\s*$/.test(elem.textContent)) + else if (elem.firstElementChild instanceof Ci.nsIDOMHTMLImageElement && /^\s*$/.test(elem.textContent)) [hint.text, hint.showText] = [elem.firstElementChild.alt || elem.firstElementChild.title, true]; else hint.text = elem.textContent.toLowerCase(); @@ -348,7 +351,7 @@ var HintSession = Class("HintSession", CommandMode, { let leftPos = Math.max((rect.left + offsetX), offsetX); let topPos = Math.max((rect.top + offsetY), offsetY); - if (elem instanceof HTMLAreaElement) + if (elem instanceof Ci.nsIDOMHTMLAreaElement) [leftPos, topPos] = this.getAreaOffset(elem, leftPos, topPos); hint.span.setAttribute("style", ["display: none; left:", leftPos, "px; top:", topPos, "px"].join("")); @@ -489,6 +492,10 @@ var HintSession = Class("HintSession", CommandMode, { let n = 5; (function next() { + if (Cu.isDeadWrapper && Cu.isDeadWrapper(elem)) + // Hint document has been unloaded. + return; + let hinted = n || this.validHints.some(function (h) h.elem === elem); if (!hinted) hints.setClass(elem, null); @@ -532,6 +539,7 @@ var HintSession = Class("HintSession", CommandMode, { */ removeHints: function _removeHints(timeout) { for (let { doc, start, end } in values(this.docs)) { + DOM(doc.documentElement).highlight.remove("Hinting"); // Goddamn stupid fucking Gecko 1.x security manager bullshit. try { delete doc.dactylLabels; } catch (e) { doc.dactylLabels = undefined; } @@ -567,42 +575,48 @@ var HintSession = Class("HintSession", CommandMode, { /** * Display the hints in pageHints that are still valid. */ + showCount: 0, show: function _show() { + let count = ++this.showCount; let hintnum = 1; let validHint = hints.hintMatcher(this.hintString.toLowerCase()); let activeHint = this.hintNumber || 1; this.validHints = []; for (let { doc, start, end } in values(this.docs)) { + DOM(doc.documentElement).highlight.add("Hinting"); let [offsetX, offsetY] = this.getContainerOffsets(doc); inner: for (let i in (util.interruptibleRange(start, end + 1, 500))) { + if (this.showCount != count) + return; + let hint = this.pageHints[i]; hint.valid = validHint(hint.text); if (!hint.valid) continue inner; - if (hint.text == "" && hint.elem.firstChild && hint.elem.firstChild instanceof HTMLImageElement) { + if (hint.text == "" && hint.elem.firstChild && hint.elem.firstChild instanceof Ci.nsIDOMHTMLImageElement) { if (!hint.imgSpan) { let rect = hint.elem.firstChild.getBoundingClientRect(); if (!rect) continue; - hint.imgSpan = util.xmlToDom(, doc); - hint.imgSpan.style.display = "none"; - hint.imgSpan.style.left = (rect.left + offsetX) + "px"; - hint.imgSpan.style.top = (rect.top + offsetY) + "px"; - hint.imgSpan.style.width = (rect.right - rect.left) + "px"; - hint.imgSpan.style.height = (rect.bottom - rect.top) + "px"; - hint.span.parentNode.appendChild(hint.imgSpan); + hint.imgSpan = DOM(["span", { highlight: "Hint", "dactyl:hl": "HintImage" }], doc).css({ + display: "none", + left: (rect.left + offsetX) + "px", + top: (rect.top + offsetY) + "px", + width: (rect.right - rect.left) + "px", + height: (rect.bottom - rect.top) + "px" + }).appendTo(hint.span.parentNode)[0]; } } let str = this.getHintString(hintnum); let text = []; - if (hint.elem instanceof HTMLInputElement) + if (hint.elem instanceof Ci.nsIDOMHTMLInputElement) if (hint.elem.type === "radio") text.push(UTF8(hint.elem.checked ? "⊙" : "○")); else if (hint.elem.type === "checkbox") @@ -720,7 +734,7 @@ var HintSession = Class("HintSession", CommandMode, { * Display the current status to the user. */ updateStatusline: function _updateStatusline() { - statusline.inputBuffer = (this.escapeNumbers ? options["mapleader"] : "") + + statusline.inputBuffer = (this.escapeNumbers ? "\\" : "") + (this.hintNumber ? this.getHintString(this.hintNumber) : ""); }, }); @@ -743,6 +757,7 @@ var Hints = Module("hints", { this.modes = {}; this.addMode(";", "Focus hint", buffer.closure.focusElement); this.addMode("?", "Show information for hint", function (elem) buffer.showElementInfo(elem)); + // TODO: allow for ! override to overwrite existing paths -- where? --djk this.addMode("s", "Save hint", function (elem) buffer.saveLink(elem, false)); this.addMode("f", "Focus frame", function (elem) dactyl.focus(elem.ownerDocument.defaultView)); this.addMode("F", "Focus frame or pseudo-frame", buffer.closure.focusElement, isScrollable); @@ -759,11 +774,17 @@ var Hints = Module("hints", { 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) editor.setRegister(null, loc, true)); this.addMode("Y", "Yank hint description", function (elem) editor.setRegister(null, elem.textContent || "", true)); + this.addMode("A", "Yank hint anchor url", function (elem) { + let uri = elem.ownerDocument.documentURIObject.clone(); + uri.ref = elem.id || elem.name; + dactyl.clipboardWrite(uri.spec, 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)); - function isScrollable(elem) isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]) || + function isScrollable(elem) isinstance(elem, [Ci.nsIDOMHTMLFrameElement, + Ci.nsIDOMHTMLIFrameElement]) || Buffer.isScrollable(elem, 0, true) || Buffer.isScrollable(elem, 0, false); }, @@ -832,7 +853,7 @@ var Hints = Module("hints", { else { for (let [, option] in Iterator(options["hintinputs"])) { if (option == "value") { - if (elem instanceof HTMLSelectElement) { + if (elem instanceof Ci.nsIDOMHTMLSelectElement) { if (elem.selectedIndex >= 0) return [elem.item(elem.selectedIndex).text.toLowerCase(), false]; } @@ -848,7 +869,8 @@ var Hints = Module("hints", { } else if (option == "label") { if (elem.id) { - let label = elem.ownerDocument.dactylLabels[elem.id]; + let label = (elem.ownerDocument.dactylLabels || {})[elem.id]; + // Urgh. if (label) return [label.textContent.toLowerCase(), true]; } @@ -1020,7 +1042,7 @@ var Hints = Module("hints", { let indexOf = String.indexOf; if (options.get("hintmatching").has("transliterated")) - indexOf = Hints.indexOf; + indexOf = Hints.closure.indexOf; switch (options["hintmatching"][0]) { case "contains" : return containsMatcher(hintString); @@ -1169,7 +1191,7 @@ var Hints = Module("hints", { [0x24d0, 0x24e9, "a"], [0xfb00, 0xfb06, ["ff", "fi", "fl", "ffi", "ffl", "st", "st"]], [0xff21, 0xff3a, "A"], [0xff41, 0xff5a, "a"] - ].forEach(function (start, stop, val) { + ].forEach(function ([start, stop, val]) { if (typeof val != "string") for (let i = start; i <= stop; i++) table[String.fromCharCode(i)] = val[(i - start) % val.length]; @@ -1187,7 +1209,7 @@ var Hints = Module("hints", { if (src.length == 0) return 0; outer: - for (var i = 0; i < end; i++) { + for (var i = 0; i <= end; i++) { var j = i; for (var k = 0; k < src.length;) { var s = dest[j++]; @@ -1259,7 +1281,7 @@ var Hints = Module("hints", { "Delete the previous character", function ({ self }) self.backspace()); - bind([""], + bind(["\\"], "Toggle hint filtering", function ({ self }) { self.escapeNumbers = !self.escapeNumbers; }); }, @@ -1267,8 +1289,10 @@ var Hints = Module("hints", { options.add(["extendedhinttags", "eht"], "XPath or CSS selector strings of hintable elements for extended hint modes", "regexpmap", { + // Make sure to update the docs when you change this. "[iI]": "img", "[asOTvVWy]": [":-moz-any-link", "area[href]", "img[src]", "iframe[src]"], + "[A]": ["[id]", "a[name]"], "[f]": "body", "[F]": ["body", "code", "div", "html", "p", "pre", "span"], "[S]": ["input:not([type=hidden])", "textarea", "button", "select"] @@ -1278,17 +1302,20 @@ var Hints = Module("hints", { getKey: function (val, default_) let (res = array.nth(this.value, function (re) let (match = re.exec(val)) match && match[0] == val, 0)) res ? res.matcher : default_, - setter: function (vals) { + parse: function parse(val) { + let vals = parse.supercall(this, val); for (let value in values(vals)) value.matcher = DOM.compileMatcher(Option.splitList(value.result)); return vals; }, + testValues: function testValues(vals, validator) vals.every(function (re) Option.splitList(re).every(validator)), validator: DOM.validateMatcher }); options.add(["hinttags", "ht"], "XPath or CSS selector strings of hintable elements for Hints mode", - "stringlist", ":-moz-any-link,area,button,iframe,input:not([type=hidden]),select,textarea," + + // Make sure to update the docs when you change this. + "stringlist", ":-moz-any-link,area,button,iframe,input:not([type=hidden]),label[for],select,textarea," + "[onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand]," + "[tabindex],[role=link],[role=button],[contenteditable=true]", { @@ -1325,7 +1352,7 @@ var Hints = Module("hints", { { values: { "0": "Follow the first hint as soon as typed text uniquely identifies it. Follow the selected hint on .", - "1": "Follow the selected hint on .", + "1": "Follow the selected hint on ." } }); @@ -1362,4 +1389,4 @@ var Hints = Module("hints", { } }); -// vim: set fdm=marker sw=4 ts=4 et: +// vim: set fdm=marker sw=4 sts=4 ts=8 et: