diff options
author | shunirr | 2008-03-20 16:32:41 +0000 |
---|---|---|
committer | shunirr | 2008-03-20 16:32:41 +0000 |
commit | aad373b519fdaf2b04004db3e95c1571c5d9ddf8 (patch) | |
tree | 53b2ecdfea348e76a1b7180b76ead6252e02f228 /char-hints-mod.js | |
download | vimperator-plugins-aad373b519fdaf2b04004db3e95c1571c5d9ddf8.tar.bz2 |
lang/javascript/vimperator-plugins/trunk
lang/javascript/vimperator-plugins/tags/0.5.3
- mkdir trunk, tags
- mv some files
git-svn-id: http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk@8221 d0d07461-0603-4401-acd4-de1884942a52
Diffstat (limited to 'char-hints-mod.js')
-rw-r--r-- | char-hints-mod.js | 709 |
1 files changed, 709 insertions, 0 deletions
diff --git a/char-hints-mod.js b/char-hints-mod.js new file mode 100644 index 0000000..05a3382 --- /dev/null +++ b/char-hints-mod.js @@ -0,0 +1,709 @@ +// Vimperator plugin: 'Char Hints Mod' +// Last Change: 15-Mar-2008. Jan 2008 +// License: GPL +// Version: 0.2 +// Maintainer: Trapezoid <trapezoid.g@gmail.com> + +// This file is a tweak based on char-hints.js by: +// (c) 2008: marco candrian <mac@calmar.ws> +// This file is a tweak based on hints.js by: +// (c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net> + +// Tested with vimperator 0.6pre from 2008-03-07 +// (won't work with older versions) + +// INSTALL: put this file into ~/.vimperator/plugin/ (create folders if necessary) +// and restart firefox or :source that file + +// plugin-setup +vimperator.plugins.charhints = {}; +var chh = vimperator.plugins.charhints; + +//<<<<<<<<<<<<<<<< EDIT USER SETTINGS HERE + +//chh.hintchars = "asdfjkl"; // chars to use for generating hints +chh.hintchars = "hjklasdfgyuiopqwertnmzxcvb"; // chars to use for generating hints + +chh.showcapitals = true; // show capital letters, even with lowercase hintchars +chh.timeout = 500; // in 1/000sec; when set to 0, press <RET> to follow + +chh.fgcolor = "black"; // hints foreground color +chh.bgcolor = "yellow"; // hints background color +chh.selcolor = "#99FF00"; // selected/active hints background color + +chh.mapNormal = "f"; // trigger normal mode with... +chh.mapNormalNewTab = "F"; // trigger and open in new tab +chh.mapExtended = ";"; // open in extended mode (see notes below) + +chh.hinttags = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " + +"//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " + +"//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " + +"//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | " + +"//xhtml:button | //xhtml:select"; + +//======================================== +// extended hints mode arguments +// +// ; to focus a link and hover it with the mouse +// a to save its destination (prompting for save location) +// s to save its destination +// o to open its location in the current tab +// t to open its location in a new tab +// O to open its location in an :open query +// T to open its location in a :tabopen query +// v to view its destination source +// w to open its destination in a new window +// W to open its location in a :winopen query +// y to yank its location +// Y to yank its text description + +// variables etc//{{{ + + +// ignorecase when showcapitals = true +// (input keys on onEvent gets lowercased too + +if (chh.showcapitals) + chh.hintchars = chh.hintchars.toLowerCase(); + + +chh.submode = ""; // used for extended mode, can be "o", "t", "y", etc. +chh.hintString = ""; // the typed string part of the hint is in this string +chh.hintNumber = 0; // only the numerical part of the hint +chh.usedTabKey = false; // when we used <Tab> to select an element + +chh.hints = []; +chh.validHints = []; // store the indices of the "hints" array with valid elements + +chh.activeTimeout = null; // needed for hinttimeout > 0 +chh.canUpdate = false; + +// used in number2hintchars +chh.transval = {"0":0, "1":1, "2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9, "a":10, "b":11, + "c":12, "d":13,"e":14, "f":15, "g":16, "h":17, "i":18, "j":19, "k":20, "l":21, "m":22, "n":23, + "o":24, "p":25,"q":26, "r":27, "s":28, "t":29, "u":30, "v":31, "w":32, "x":33, "y":34, "z":35}; + +// used in hintchars2number +chh.conversion = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +// keep track of the documents which we generated the hints for +// docs = { doc: document, start: start_index in hints[], end: end_index in hints[] } +chh.docs = []; +//}}} +// reset all important variables +chh.reset = function ()//{{{ +{ + vimperator.statusline.updateInputBuffer(""); + chh.hintString = ""; + chh.hintNumber = 0; + chh.usedTabKey = false; + chh.hints = []; + chh.validHints = []; + chh.canUpdate = false; + chh.docs = []; + + if (chh.activeTimeout) + clearTimeout(chh.activeTimeout); + chh.activeTimeout = null; +} +//}}} +chh.updateStatusline = function ()//{{{ +{ + vimperator.statusline.updateInputBuffer(("") + + (chh.hintString ? "\"" + chh.hintString + "\"" : "") + + (chh.hintNumber > 0 ? " <" + chh.hintNumber + ">" : "")); +} +//}}} +// this function 'click' an element, which also works +// for javascript links +chh.hintchars2number = function (hintstr)//{{{ +{ + // convert into 'normal number then make it decimal-based + + var converted = ""; + + // translate users hintchars into a number (chh.conversion) 0 -> 0, 1 -> 1, ... + for (var i = 0, l = hintstr.length; i < l; i++) + converted += "" + chh.conversion[chh.hintchars.indexOf(hintstr[i])]; + + // add one, since hints begin with 0; + + return parseInt(converted, chh.hintchars.length); // hintchars.length is the base/radix +} +//}}} +chh.number2hintchars = function (nr)//{{{ +{ + var oldnr = nr; + var converted = ""; + var tmp = ""; + + tmp = nr.toString(chh.hintchars.length); // hintchars.length is the base/radix) + + // translate numbers into users hintchars + // tmp might be 2e -> (chh.transval) 2 and 14 -> (chh.hintchars) according hintchars + + for (var i = 0, l = tmp.length; i < l; i++) + converted += "" + chh.hintchars[chh.transval[tmp[i]]]; + + return converted; +} +//}}} +chh.openHint = function (where)//{{{ +{ + if (chh.validHints.length < 1) + return false; + + var x = 1, y = 1; + var elem = chh.validHints[chh.hintNumber - 1] || chh.validHints[0]; + var elemTagName = elem.localName.toLowerCase(); + elem.focus(); + + vimperator.buffer.followLink(elem, where); + return true; +} +//}}} +chh.focusHint = function ()//{{{ +{ + if (chh.validHints.length < 1) + return false; + + var elem = chh.validHints[chh.hintNumber - 1] || chh.validHints[0]; + var doc = window.content.document; + var elemTagName = elem.localName.toLowerCase(); + if (elemTagName == "frame" || elemTagName == "iframe") + { + elem.contentWindow.focus(); + return false; + } + else + { + elem.focus(); + } + + var evt = doc.createEvent("MouseEvents"); + var x = 0; + var y = 0; + // for imagemap + if (elemTagName == "area") + { + [x, y] = elem.getAttribute("coords").split(","); + x = Number(x); + y = Number(y); + } + + evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null); + elem.dispatchEvent(evt); +} +//}}} +chh.yankHint = function (text)//{{{ +{ + if (chh.validHints.length < 1) + return false; + + var elem = chh.validHints[chh.hintNumber - 1] || chh.validHints[0]; + if (text) + var loc = elem.textContent; + else + var loc = elem.href; + + vimperator.copyToClipboard(loc); + vimperator.echo("Yanked " + loc, vimperator.commandline.FORCE_SINGLELINE); +} +//}}} +chh.saveHint = function (skipPrompt)//{{{ +{ + if (chh.validHints.length < 1) + return false; + + var elem = chh.validHints[chh.hintNumber - 1] || chh.validHints[0]; + + try + { + vimperator.buffer.saveLink(elem,skipPrompt); + } + catch (e) + { + vimperator.echoerr(e); + } +} +//}}} +chh.generate = function (win)//{{{ +{ + var startDate = Date.now(); + + if (!win) + win = window.content; + + var doc = win.document; + var height = win.innerHeight; + var width = win.innerWidth; + var scrollX = doc.defaultView.scrollX; + var scrollY = doc.defaultView.scrollY; + + var baseNodeAbsolute = doc.createElementNS("http://www.w3.org/1999/xhtml", "span"); + baseNodeAbsolute.style.backgroundColor = "red"; + baseNodeAbsolute.style.color = "white"; + baseNodeAbsolute.style.position = "absolute"; + baseNodeAbsolute.style.fontSize = "10px"; + baseNodeAbsolute.style.fontWeight = "bold"; + baseNodeAbsolute.style.lineHeight = "10px"; + baseNodeAbsolute.style.padding = "0px 1px 0px 0px"; + baseNodeAbsolute.style.zIndex = "10000001"; + baseNodeAbsolute.style.display = "none"; + baseNodeAbsolute.className = "vimperator-hint"; + + var elem, tagname, text, span, rect; + var res = vimperator.buffer.evaluateXPath(chh.hinttags, doc, null, true); + vimperator.log("shints: evaluated XPath after: " + (Date.now() - startDate) + "ms"); + + var fragment = doc.createDocumentFragment(); + var start = chh.hints.length; + while ((elem = res.iterateNext()) != null) + { + // TODO: for frames, this calculation is wrong + rect = elem.getBoundingClientRect(); + if (!rect || rect.top > height || rect.bottom < 0 || rect.left > width || rect.right < 0) + continue; + + rect = elem.getClientRects()[0]; + if (!rect) + continue; + + // TODO: mozilla docs recommend localName instead of tagName + tagname = elem.tagName.toLowerCase(); + text = ""; + span = baseNodeAbsolute.cloneNode(true); + span.style.left = (rect.left + scrollX) + "px"; + span.style.top = (rect.top + scrollY) + "px"; + fragment.appendChild(span); + + chh.hints.push([elem, text, span, null, elem.style.backgroundColor, elem.style.color]); + } + + doc.body.appendChild(fragment); + chh.docs.push({ doc: doc, start: start, end: chh.hints.length - 1 }); + + // also generate hints for frames + for (var i = 0; i < win.frames.length; i++) + chh.generate(win.frames[i]); + + vimperator.log("shints: generate() completed after: " + (Date.now() - startDate) + "ms"); + return true; +} +//}}} +// TODO: make it aware of imgspans +chh.showActiveHint = function (newID, oldID)//{{{ +{ + var oldElem = chh.validHints[oldID - 1]; + if (oldElem) + oldElem.style.backgroundColor = chh.bgcolor; + + var newElem = chh.validHints[newID - 1]; + if (newElem) + newElem.style.backgroundColor = chh.selcolor; +} +//}}} +chh.showHints = function ()//{{{ +{ + var startDate = Date.now(); + var win = window.content; + var height = win.innerHeight; + var width = win.innerWidth; + + + var elem, tagname, text, rect, span, imgspan; + var hintnum = 1; + //var findTokens = chh.hintString.split(/ +/); + var activeHint = chh.hintNumber || 1; + chh.validHints = []; + + for (var j = 0; j < chh.docs.length; j++) + { + var doc = chh.docs[j].doc; + var start = chh.docs[j].start; + var end = chh.docs[j].end; + var scrollX = doc.defaultView.scrollX; + var scrollY = doc.defaultView.scrollY; + +outer: + for (let i = start; i <= end; i++) + { + [elem, , span, imgspan] = chh.hints[i]; + text = ""; + + if (elem.firstChild && elem.firstChild.tagName == "IMG") + { + if (!imgspan) + { + rect = elem.firstChild.getBoundingClientRect(); + if (!rect) + continue; + + imgspan = doc.createElementNS("http://www.w3.org/1999/xhtml", "span"); + imgspan.style.position = "absolute"; + imgspan.style.opacity = 0.5; + imgspan.style.zIndex = "10000000"; + imgspan.style.left = (rect.left + scrollX) + "px"; + imgspan.style.top = (rect.top + scrollY) + "px"; + imgspan.style.width = (rect.right - rect.left) + "px"; + imgspan.style.height = (rect.bottom - rect.top) + "px"; + imgspan.className = "vimperator-hint"; + chh.hints[i][3] = imgspan; + doc.body.appendChild(imgspan); + } + imgspan.style.backgroundColor = (activeHint == hintnum) ? chh.selcolor : chh.bgcolor; + imgspan.style.display = "inline"; + } + + if (!imgspan) + elem.style.backgroundColor = (activeHint == hintnum) ? chh.selcolor : chh.bgcolor; + elem.style.color = chh.fgcolor; + if (chh.showcapitals) + span.textContent = chh.number2hintchars(hintnum++).toUpperCase(); + else + span.textContent = chh.number2hintchars(hintnum++); + + span.style.display = "inline"; + chh.validHints.push(elem); + } + } + + vimperator.log("shints: showHints() completed after: " + (Date.now() - startDate) + "ms"); + return true; +} +//}}} +chh.removeHints = function (timeout)//{{{ +{ + var firstElem = chh.validHints[0] || null; + var firstElemselcolor = ""; + var firstElemColor = ""; + + for (var j = 0; j < chh.docs.length; j++) + { + var doc = chh.docs[j].doc; + var start = chh.docs[j].start; + var end = chh.docs[j].end; + + for (let i = start; i <= end; i++) + { + // remove the span for the numeric display part + doc.body.removeChild(chh.hints[i][2]); + if (chh.hints[i][3]) // a transparent span for images + doc.body.removeChild(chh.hints[i][3]); + + if (timeout && firstElem == chh.hints[i][0]) + { + firstElemselcolor = chh.hints[i][4]; + firstElemColor = chh.hints[i][5]; + } + else + { + // restore colors + var elem = chh.hints[i][0]; + elem.style.backgroundColor = chh.hints[i][4]; + elem.style.color = chh.hints[i][5]; + } + } + + // animate the disappearance of the first hint + if (timeout && firstElem) + { + setTimeout(function () { + firstElem.style.backgroundColor = firstElemselcolor; + firstElem.style.color = firstElemColor; + }, timeout); + } + } + + vimperator.log("shints: removeHints() done"); + chh.reset(); +} +//}}} +chh.processHints = function (followFirst)//{{{ +{ + if (chh.validHints.length == 0) + { + vimperator.beep(); + return false; + } + + if (!followFirst) + { + var firstHref = chh.validHints[0].getAttribute("href") || null; + if (firstHref) + { + if (chh.validHints.some(function (e) { return e.getAttribute("href") != firstHref; })) + return false; + } + else if (chh.validHints.length > 1) + return false; + } + + var activeNum = chh.hintNumber || 1; + var loc = chh.validHints[activeNum - 1].href || ""; + switch (chh.submode) + { + case ";": chh.focusHint(); break; + case "a": chh.saveHint(false); break; + case "s": chh.saveHint(true); break; + case "o": chh.openHint(vimperator.CURRENT_TAB); break; + case "O": vimperator.commandline.open(":", "open " + loc, vimperator.modes.EX); break; + case "t": chh.openHint(vimperator.NEW_TAB); break; + case "T": vimperator.commandline.open(":", "tabopen " + loc, vimperator.modes.EX); break; + case "w": chh.openHint(vimperator.NEW_WINDOW); break; + case "W": vimperator.commandline.open(":", "winopen " + loc, vimperator.modes.EX); break; + case "y": chh.yankHint(false); break; + case "Y": chh.yankHint(true); break; + default: + vimperator.echoerr("INTERNAL ERROR: unknown submode: " + chh.submode); + } + + var timeout = followFirst ? 0 : 500; + chh.removeHints(timeout); + + if (vimperator.modes.extended & vimperator.modes.ALWAYS_HINT) + { + setTimeout(function () { + chh.canUpdate = true; + chh.hintString = ""; + chh.hintNumber = 0; + vimperator.statusline.updateInputBuffer(""); + }, timeout); + } + else + { + if (timeout == 0 || vimperator.modes.isReplaying) + { + // force a possible mode change, based on wheter an input field has focus + vimperator.events.onFocusChange(); + if (vimperator.mode == vimperator.modes.CUSTOM) + vimperator.modes.reset(false); + } + else + { + vimperator.modes.add(vimperator.modes.INACTIVE_HINT); + setTimeout(function () { + if (vimperator.mode == vimperator.modes.CUSTOM) + vimperator.modes.reset(false); + }, timeout); + } + } + + return true; +} +//}}} +// TODO: implement framesets +chh.show = function (mode, minor, filter)//{{{ +{ + if (mode == vimperator.modes.EXTENDED_HINT && !/^[;asoOtTwWyY]$/.test(minor)) + { + vimperator.beep(); + return; + } + + vimperator.modes.set(vimperator.modes.CUSTOM, mode); + chh.submode = minor || "o"; // open is the default mode + chh.hintString = filter || ""; + chh.hintNumber = 0; + chh.canUpdate = false; + + chh.generate(); + + // get all keys from the input queue + var mt = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread; + while (mt.hasPendingEvents()) + mt.processNextEvent(true); + + chh.canUpdate = true; + chh.showHints(); + + if (chh.validHints.length == 0) + { + vimperator.beep(); + vimperator.modes.reset(); + return false; + } + else if (chh.validHints.length == 1) + { + chh.processHints(true); + return false; + } + else // still hints visible + return true; +} +//}}} +chh.hide = function ()//{{{ +{ + chh.removeHints(0); +} +//}}} +chh.onEvent = function (event)//{{{ +{ + var key = vimperator.events.toString(event); + + if (chh.showcapitals && key.length == 1) + key = key.toLowerCase(); + + // clear any timeout which might be active after pressing a number + if (chh.activeTimeout) + { + clearTimeout(chh.activeTimeout); + chh.activeTimeout = null; + } + + switch (key) + { + case "<Return>": + chh.processHints(true); + break; + + case "<Tab>": + case "<S-Tab>": + chh.usedTabKey = true; + if (chh.hintNumber == 0) + chh.hintNumber = 1; + + var oldID = chh.hintNumber; + if (key == "<Tab>") + { + if (++chh.hintNumber > chh.validHints.length) + chh.hintNumber = 1; + } + else + { + if (--chh.hintNumber < 1) + chh.hintNumber = chh.validHints.length; + } + chh.showActiveHint(chh.hintNumber, oldID); + return; + + case "<BS>": //TODO: may tweak orig hints.js too (adding 2 lines ...) + var oldID = chh.hintNumber; + if (chh.hintNumber > 0) + { + chh.hintNumber = Math.floor(chh.hintNumber / chh.hintchars.length); + chh.hintString = chh.hintString.substr(0, chh.hintString.length - 1); + chh.usedTabKey = false; + } + else + { + chh.usedTabKey = false; + chh.hintNumber = 0; + vimperator.beep(); + return; + } + chh.showActiveHint(chh.hintNumber, oldID); + break; + + case "<C-w>": + case "<C-u>": + chh.hintString = ""; + chh.hintNumber = 0; + break; + + default: + // pass any special or ctrl- etc. prefixed key back to the main vimperator loop + if (/^<./.test(key) || key == ":") + { + //FIXME: won't work probably + var map = null; + if ((map = vimperator.mappings.get(vimperator.modes.NORMAL, key)) || + (map = vimperator.mappings.get(vimperator.modes.CUSTOM, key))) //TODO + { + map.execute(null, -1); + return; + } + + vimperator.beep(); + return; + } + + if (chh.hintchars.indexOf(key) >= 0) // TODO: check if in hintchars + { + chh.hintString += key; + var oldHintNumber = chh.hintNumber; + if (chh.hintNumber == 0 || chh.usedTabKey) + { + chh.usedTabKey = false; + } + + chh.hintNumber = chh.hintchars2number(chh.hintString); + + chh.updateStatusline(); + + if (!chh.canUpdate) + return; + + if (chh.docs.length == 0) + { + chh.generate(); + chh.showHints(); + } + chh.showActiveHint(chh.hintNumber, oldHintNumber || 1); + + if (chh.hintNumber == 0 || chh.hintNumber > chh.validHints.length) + { + vimperator.beep(); + return; + } + + // orig hints.js comment: if we write a numeric part like 3, but we have 45 hints, only follow + // the hint after a timeout, as the user might have wanted to follow link 34 + if (chh.hintNumber > 0 && chh.hintNumber * chh.hintchars.length <= chh.validHints.length) + { + if (chh.timeout > 0) + chh.activeTimeout = setTimeout(function () { chh.processHints(true); }, chh.timeout); + + return false; + } + // we have a unique hint + chh.processHints(true); + return; + } + + if (chh.usedTabKey) + { + chh.usedTabKey = false; + chh.showActiveHint(1, chh.hintNumber); + } + } + + chh.updateStatusline(); +}//}}} + + +// <<<<<<<<<<<<<<< registering/setting up this plugin + +vimperator.modes.setCustomMode ("CHAR-HINTS", vimperator.plugins.charhints.onEvent, + vimperator.plugins.charhints.hide); + +vimperator.mappings.addUserMap([vimperator.modes.NORMAL], [chh.mapNormal], + "Start Custum-QuickHint mode", + function () { vimperator.plugins.charhints.show(vimperator.modes.QUICK_HINT); }, + { noremap: true } +); + +vimperator.mappings.addUserMap([vimperator.modes.NORMAL], [chh.mapNormalNewTab], + "Start Custum-QuickHint mode, but open link in a new tab", + function () { vimperator.plugins.charhints.show(vimperator.modes.QUICK_HINT, "t"); }, + { noremap: true } +); + +vimperator.mappings.addUserMap([vimperator.modes.NORMAL], [chh.mapExtended], + "Start an extended hint mode", + function (arg) + { + if (arg == "f") + vimperator.plugins.charhints.show(vimperator.modes.ALWAYS_HINT, "o"); + else if (arg == "F") + vimperator.plugins.charhints.show(vimperator.modes.ALWAYS_HINT, "t"); + else + vimperator.plugins.charhints.show(vimperator.modes.EXTENDED_HINT, arg); + }, + { + flags: vimperator.Mappings.flags.ARGUMENT, + noremap: true + } +); + +// vim: set fdm=marker sw=4 ts=4 et: |