about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorMarco Peereboom <marco@conformal.com>2011-10-27 13:57:39 -0500
committerMarco Peereboom <marco@conformal.com>2011-10-27 13:57:39 -0500
commit10b53e44535a94aa649c6c552d976c743900e348 (patch)
treece7f944e315781f1d4d8313b26793bbd2c2d3793
parentabee6c7a87dfe25430147a587a707335c69bbc6e (diff)
downloadxombrero-10b53e44535a94aa649c6c552d976c743900e348.tar.gz
rewrite hinting code completely
Import new vimprobable JavaScript code.

Make hinting mode now print what has been typed.  It works along the
same lines as '/' and '?' in search mode but it uses '.' and ','
instead.

Focus on default input box and unfocus on default input box when ESC is
hit.

This also adds i and ESC to switch between input/command mode.
-rw-r--r--hinting.js575
-rw-r--r--input-focus.js53
-rw-r--r--xxxterm.c334
3 files changed, 581 insertions, 381 deletions
diff --git a/hinting.js b/hinting.js
index f5a5f52..135c4dd 100644
--- a/hinting.js
+++ b/hinting.js
@@ -1,189 +1,303 @@
 /*
-    (c) 2009 by Leon Winter
-    (c) 2009, 2010 by Hannes Schueller
-    (c) 2010 by Hans-Peter Deifel
-
-    Copyright (c) 2009 Leon Winter
-    Copyright (c) 2009, 2010 Hannes Schueller
-    Copyright (c) 2009, 2010 Matto Fransen
-    Copyright (c) 2010 Hans-Peter Deifel
-    Copyright (c) 2010 Thomas Adam
-
-    Permission is hereby granted, free of charge, to any person obtaining a copy
-    of this software and associated documentation files (the "Software"), to deal
-    in the Software without restriction, including without limitation the rights
-    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-    copies of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be included in
-    all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-    THE SOFTWARE.
+Copyright (c) 2009 Leon Winter
+Copyright (c) 2009-2011 Hannes Schueller
+Copyright (c) 2009-2010 Matto Fransen
+Copyright (c) 2010-2011 Hans-Peter Deifel
+Copyright (c) 2010-2011 Thomas Adam
+Copyright (c) 2011 Albert Kim
+Copyright (c) 2011 Daniel Carl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
 */
+function Hints() {
+    var config = {
+        maxAllowedHints: 500,
+        hintCss: "z-index:100000;font-family:monospace;font-size:10px;"
+               + "font-weight:bold;color:white;background-color:red;"
+               + "padding:0px 1px;position:absolute;",
+        hintClass: "hinting_mode_hint",
+        hintClassFocus: "hinting_mode_hint_focus",
+        elemBackground: "#ff0",
+        elemBackgroundFocus: "#8f0",
+        elemColor: "#000"
+    };
 
-function vimprobable_clearfocus() {
-    if(document.activeElement && document.activeElement.blur)
-        document.activeElement.blur();
-}
+    var hintContainer;
+    var currentFocusNum = 1;
+    var hints = [];
+    var mode;
 
-function vimprobable_show_hints(inputText) {
-    if (document.getElementsByTagName("body")[0] !== null && typeof(document.getElementsByTagName("body")[0]) == "object") {
-        var height = window.innerHeight;
-        var width = window.innerWidth;
-        var scrollX = document.defaultView.scrollX;
-        var scrollY = document.defaultView.scrollY;
-        /* prefixing html: will result in namespace error */
-        var hinttags;
-        if (typeof(inputText) == "undefined" || inputText == "") {
-            hinttags = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select";
-        } else {
-            /* only elements which match the text entered so far */
-            hinttags = "//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href) and contains(., '" + inputText + "')] | //input[not(@type='hidden') and contains(., '" + inputText + "')] | //a[contains(., '" + inputText + "')] | //area[contains(., '" + inputText + "')] | //iframe[contains(@name, '" + inputText + "')] | //textarea[contains(., '" + inputText + "')] | //button[contains(@value, '" + inputText + "')] | //select[contains(., '" + inputText + "')]";
+    this.createHints = function(inputText, hintMode)
+    {
+        if (hintMode) {
+            mode = hintMode;
         }
 
-        /* iterator type isn't suitable here, because: "DOMException NVALID_STATE_ERR: The document has been mutated since the result was returned." */
-        var r = document.evaluate(hinttags, document,
-            function(p) {
-                return 'http://www.w3.org/1999/xhtml';
-            }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
-        div = document.createElement("div");
-        /* due to the different XPath result type, we will need two counter variables */
-        vimprobable_j = 0;
-        var i;
-        vimprobable_a = [];
-        vimprobable_colors = [];
-        vimprobable_backgrounds = [];
-        for (i = 0; i < r.snapshotLength; i++)
-        {
-            var elem = r.snapshotItem(i);
-            rect = elem.getBoundingClientRect();
-            if (!rect || rect.top > height || rect.bottom < 0 || rect.left > width || rect.right < 0 || !(elem.getClientRects()[0]))
-                continue;
-            var computedStyle = document.defaultView.getComputedStyle(elem, null);
-            if (computedStyle.getPropertyValue("visibility") != "visible" || computedStyle.getPropertyValue("display") == "none")
-                continue;
-            var leftpos = Math.max((rect.left + scrollX), scrollX);
-            var toppos = Math.max((rect.top + scrollY), scrollY);
-            vimprobable_a.push(elem);
-            /* making this block DOM compliant */
-            var hint = document.createElement("span");
-            hint.setAttribute("class", "hinting_mode_hint");
-            hint.setAttribute("id", "vimprobablehint" + vimprobable_j);
-            hint.style.position = "absolute";
-            hint.style.left = leftpos + "px";
-            hint.style.top =  toppos + "px";
-            hint.style.background = "red";
-            hint.style.color = "#fff";
-            hint.style.font = "bold 10px monospace";
-            hint.style.zIndex = "99";
-            var text = document.createTextNode(vimprobable_j + 1);
-            hint.appendChild(text);
-            div.appendChild(hint);
-            /* remember site-defined colour of this element */
-            vimprobable_colors[vimprobable_j] = elem.style.color;
-            vimprobable_backgrounds[vimprobable_j] = elem.style.background;
-            /* make the link black to ensure it's readable */
-            elem.style.color = "#000";
-            elem.style.background = "#ff0";
-            vimprobable_j++;
-        }
-        i = 0;
-        while (typeof(vimprobable_a[i]) != "undefined") {
-            vimprobable_a[i].className += " hinting_mode_hint";
-            i++;
-        }
-        document.getElementsByTagName("body")[0].appendChild(div);
-        vimprobable_clearfocus();
-        vimprobable_h = null;
-        if (i == 1) {
+        var topwin = window;
+        var top_height = topwin.innerHeight;
+        var top_width = topwin.innerWidth;
+        var xpath_expr;
+
+        var hintCount = 0;
+        this.clearHints();
+
+        function helper (win, offsetX, offsetY) {
+            var doc = win.document;
+
+            var win_height = win.height;
+            var win_width = win.width;
+
+            /* Bounds */
+            var minX = offsetX < 0 ? -offsetX : 0;
+            var minY = offsetY < 0 ? -offsetY : 0;
+            var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width;
+            var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height;
+
+            var scrollX = win.scrollX;
+            var scrollY = win.scrollY;
+
+            hintContainer = doc.createElement("div");
+            hintContainer.id = "hint_container";
+
+            if (typeof(inputText) == "undefined" || inputText == "") {
+                xpath_expr = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a[href] | //area | //textarea | //button | //select";
+            } else {
+                xpath_expr = "//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href) and contains(., '" + inputText + "')] | //input[not(@type='hidden') and contains(., '" + inputText + "')] | //a[@href and contains(., '" + inputText + "')] | //area[contains(., '" + inputText + "')] |  //textarea[contains(., '" + inputText + "')] | //button[contains(@value, '" + inputText + "')] | //select[contains(., '" + inputText + "')]";
+            }
+
+            var res = doc.evaluate(xpath_expr, doc,
+                function (p) {
+                    return "http://www.w3.org/1999/xhtml";
+                }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+
+            /* generate basic hint element which will be cloned and updated later */
+            var hintSpan = doc.createElement("span");
+            hintSpan.setAttribute("class", config.hintClass);
+            hintSpan.style.cssText = config.hintCss;
+
+            /* due to the different XPath result type, we will need two counter variables */
+            var rect, elem, text, node, show_text;
+            for (var i = 0; i < res.snapshotLength; i++)
+            {
+                if (hintCount >= config.maxAllowedHints)
+                    break;
+
+                elem = res.snapshotItem(i);
+                rect = elem.getBoundingClientRect();
+                if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
+                    continue;
+
+                var style = topwin.getComputedStyle(elem, "");
+                if (style.display == "none" || style.visibility != "visible")
+                    continue;
+
+                var leftpos = Math.max((rect.left + scrollX), scrollX);
+                var toppos = Math.max((rect.top + scrollY), scrollY);
+
+                /* making this block DOM compliant */
+                var hint = hintSpan.cloneNode(false);
+                hint.setAttribute("id", "vimprobablehint" + hintCount);
+                hint.style.left = leftpos + "px";
+                hint.style.top =  toppos + "px";
+                text = doc.createTextNode(hintCount + 1);
+                hint.appendChild(text);
+
+                hintContainer.appendChild(hint);
+                hintCount++;
+                hints.push({
+                    elem:       elem,
+                    number:     hintCount,
+                    span:       hint,
+                    background: elem.style.background,
+                    foreground: elem.style.color}
+                );
+
+                /* make the link black to ensure it's readable */
+                elem.style.color = config.elemColor;
+                elem.style.background = config.elemBackground;
+            }
+
+            doc.documentElement.appendChild(hintContainer);
+
+            /* recurse into any iframe or frame element */
+            var frameTags = ["frame","iframe"];
+            for (var f = 0; f < frameTags.length; ++f) {
+                var frames = doc.getElementsByTagName(frameTags[f]);
+                for (var i = 0, nframes = frames.length; i < nframes; ++i) {
+                    elem = frames[i];
+                    rect = elem.getBoundingClientRect();
+                    if (!elem.contentWindow || !rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
+                        continue;
+                    helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top);
+                }
+            }
+        }
+
+        helper(topwin, 0, 0);
+
+        this.clearFocus();
+        this.focusHint(1);
+        if (hintCount == 1) {
             /* just one hinted element - might as well follow it */
-            return vimprobable_fire(1);
+            return this.fire(1);
         }
-    }
-}
-function vimprobable_fire(n)
-{
-    if (typeof(vimprobable_a[n - 1]) != "undefined") {
-        el = vimprobable_a[n - 1];
-        tag = el.nodeName.toLowerCase();
-        vimprobable_clear();
-        if(tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
-            el.focus();
-            if (tag == "textarea" || tag == "input")
-                console.log('insertmode_on');
+    };
+
+    /* set focus on hint with given number */
+    this.focusHint = function(n)
+    {
+        /* reset previous focused hint */
+        var hint = _getHintByNumber(currentFocusNum);
+        if (hint !== null) {
+            hint.elem.className = hint.elem.className.replace(config.hintClassFocus, config.hintClass);
+            hint.elem.style.background = config.elemBackground;
+        }
+
+        currentFocusNum = n;
+
+        /* mark new hint as focused */
+        var hint = _getHintByNumber(currentFocusNum);
+        if (hint !== null) {
+            hint.elem.className = hint.elem.className.replace(config.hintClass, config.hintClassFocus);
+            hint.elem.style.background = config.elemBackgroundFocus;
+        }
+    };
+
+    /* set focus to next avaiable hint */
+    this.focusNextHint = function()
+    {
+        var index = _getHintIdByNumber(currentFocusNum);
+
+        if (typeof(hints[index + 1]) != "undefined") {
+            this.focusHint(hints[index + 1].number);
         } else {
-            if (el.onclick) {
-                var evObj = document.createEvent('MouseEvents');
-                evObj.initMouseEvent('click', true, true, window, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
-                el.dispatchEvent(evObj);
-            } else if (el.href) {
-                if (el.href.match(/^javascript:/)) {
-                    var evObj = document.createEvent('MouseEvents');
-                    evObj.initMouseEvent('click', true, true, window, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
-                    el.dispatchEvent(evObj);
-                } else {
-                    /* send signal to open link */
-                    return "open;" + el.href;
-                }
-            } else {
-                var evObj = document.createEvent('MouseEvents');
-                evObj.initMouseEvent('click', true, true, window, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
-                el.dispatchEvent(evObj);
+            this.focusHint(hints[0].number);
+        }
+    };
+
+    /* set focus to previous avaiable hint */
+    this.focusPreviousHint = function()
+    {
+        var index = _getHintIdByNumber(currentFocusNum);
+        if (index != 0 && typeof(hints[index - 1].number) != "undefined") {
+            this.focusHint(hints[index - 1].number);
+        } else {
+            this.focusHint(hints[hints.length - 1].number);
+        }
+    };
+
+    /* filters hints matching given number */
+    this.updateHints = function(n)
+    {
+        if (n == 0) {
+            return this.createHints();
+        }
+        /* remove none matching hints */
+        var remove = [];
+        for (var i = 0; i < hints.length; ++i) {
+            var hint = hints[i];
+            if (0 != hint.number.toString().indexOf(n.toString())) {
+                remove.push(hint.number);
             }
         }
-    }
-}
-function vimprobable_cleanup()
-{
-    for(e in vimprobable_a) {
-        if (typeof(vimprobable_a[e].className) != "undefined") {
-            vimprobable_a[e].className = vimprobable_a[e].className.replace(/hinting_mode_hint/,'');
-            /* reset to site-defined colour */
-            vimprobable_a[e].style.color = vimprobable_colors[e];
-            vimprobable_a[e].style.background = vimprobable_backgrounds[e];
+
+        for (var i = 0; i < remove.length; ++i) {
+            _removeHint(remove[i]);
         }
-    }
-    div.parentNode.removeChild(div);
-    window.onkeyup = null;
-}
-function vimprobable_clear()
-{
-    vimprobable_cleanup();
-    console.log("hintmode_off")
-}
 
-function vimprobable_update_hints(n)
-{
-    if(vimprobable_h != null) {
-        vimprobable_h.className = vimprobable_h.className.replace("_focus","");
-        vimprobable_h.style.background = "#ff0";
-    }
-    if (vimprobable_j - 1 < n * 10 && typeof(vimprobable_a[n - 1]) != "undefined") {
-        /* return signal to follow the link */
-        return "fire;" + n;
-    } else {
-        if (typeof(vimprobable_a[n - 1]) != "undefined") {
-            (vimprobable_h = vimprobable_a[n - 1]).className = vimprobable_a[n - 1].className.replace("hinting_mode_hint", "hinting_mode_hint_focus");
-            vimprobable_h.style.background = "#8f0";
+        if (hints.length === 1) {
+            return this.fire(hints[0].number);
+        } else {
+            return this.focusHint(n);
         }
-    }
-}
+    };
+
+    this.clearFocus = function()
+    {
+        if (document.activeElement && document.activeElement.blur) {
+            document.activeElement.blur();
+        }
+    };
+
+    /* remove all hints and set previous style to them */
+    this.clearHints = function()
+    {
+        if (hints.length == 0) {
+            return;
+        }
+        for (var i = 0; i < hints.length; ++i) {
+            var hint = hints[i];
+            if (typeof(hint.elem) != "undefined") {
+                hint.elem.style.background = hint.background;
+                hint.elem.style.color = hint.foreground;
+                hint.span.parentNode.removeChild(hint.span);
+            }
+        }
+        hints = [];
+        hintContainer.parentNode.removeChild(hintContainer);
+        window.onkeyup = null;
+    };
+
+    /* fires the modeevent on hint with given number */
+    this.fire = function(n)
+    {
+        var doc, result;
+        if (!n) {
+            var n = currentFocusNum;
+        }
+        var hint = _getHintByNumber(n);
+        if (typeof(hint.elem) == "undefined")
+            return "done;";
+
+        var el = hint.elem;
+        var tag = el.nodeName.toLowerCase();
+
+        this.clearHints();
+
+        if (tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
+            el.focus();
+            if (tag == "input" || tag == "textarea") {
+                return "insert;"
+            }
+            return "done;";
+        }
+
+        switch (mode)
+        {
+            case "f": result = _open(el); break;
+            case "F": result = _openNewWindow(el); break;
+            default:  result = _getElemtSource(el);
+        }
+
+        return result;
+    };
+
+    this.focusInput = function()
+    {
+        if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object")
+            return;
 
-function vimprobable_focus_input()
-{
-    if (document.getElementsByTagName("body")[0] !== null && typeof(document.getElementsByTagName("body")[0]) == "object") {
         /* prefixing html: will result in namespace error */
         var hinttags = "//input[@type='text'] | //input[@type='password'] | //textarea";
         var r = document.evaluate(hinttags, document,
             function(p) {
-                return 'http://www.w3.org/1999/xhtml';
+                return "http://www.w3.org/1999/xhtml";
             }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
         var i;
         var j = 0;
@@ -201,23 +315,126 @@ function vimprobable_focus_input()
             if (j == 1 && elem.style.display != "none" && elem.style.visibility != "hidden") {
                 elem.focus();
                 var tag = elem.nodeName.toLowerCase();
-                if (tag == "textarea" || tag == "input")
-                    console.log('insertmode_on');
+                if (tag == "textarea" || tag == "input") {
+                    return "insert;";
+                }
                 break;
-            } else {
-                if (elem == document.activeElement)
-                    j = 1;
+            }
+            if (elem == document.activeElement) {
+                j = 1;
             }
             k++;
         }
-        if (j == 0) {
-            /* no appropriate field found focused - focus the first one */
-            if (first !== null) {
-                first.focus();
-                var tag = elem.nodeName.toLowerCase();
-                if (tag == "textarea" || tag == "input")
-                    console.log('insertmode_on');
+        /* no appropriate field found focused - focus the first one */
+        if (j == 0 && first !== null) {
+            first.focus();
+            var tag = elem.nodeName.toLowerCase();
+            if (tag == "textarea" || tag == "input") {
+                return "insert;";
             }
         }
+    };
+
+    /* retrieves text content fro given element */
+    function _getTextFromElement(el)
+    {
+        if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
+            text = el.value;
+        } else if (el instanceof HTMLSelectElement) {
+            if (el.selectedIndex >= 0) {
+                text = el.item(el.selectedIndex).text;
+            } else{
+                text = "";
+            }
+        } else {
+            text = el.textContent;
+        }
+        return text.toLowerCase();;
+    }
+
+    /* retrieves the hint for given hint number */
+    function _getHintByNumber(n)
+    {
+        var index = _getHintIdByNumber(n);
+        if (index !== null) {
+            return hints[index];
+        }
+        return null;
+    }
+
+    /* retrieves the id of hint with given number */
+    function _getHintIdByNumber(n)
+    {
+        for (var i = 0; i < hints.length; ++i) {
+            var hint = hints[i];
+            if (hint.number === n) {
+                return i;
+            }
+        }
+        return null;
+    }
+
+    /* removes hint with given number from hints array */
+    function _removeHint(n)
+    {
+        var index = _getHintIdByNumber(n);
+        if (index === null) {
+            return;
+        }
+        var hint = hints[index];
+        if (hint.number === n) {
+            hint.elem.style.background = hint.background;
+            hint.elem.style.color = hint.foreground;
+            hint.span.parentNode.removeChild(hint.span);
+
+            /* remove hints from all hints */
+            hints.splice(index, 1);
+        }
+    }
+
+    /* opens given element */
+    function _open(elem)
+    {
+        if (elem.target == "_blank") {
+            elem.removeAttribute("target");
+        }
+        _clickElement(elem);
+        return "done;";
+    }
+
+    /* opens given element into new window */
+    function _openNewWindow(elem)
+    {
+        var oldTarget = elem.target;
+
+        /* set target to open in new window */
+        elem.target = "_blank";
+        _clickElement(elem);
+        elem.target = oldTarget;
+
+        return "done;";
+    }
+
+    /* fire moudedown and click event on given element */
+    function _clickElement(elem)
+    {
+        doc = elem.ownerDocument;
+        view = elem.contentWindow;
+
+        var evObj = doc.createEvent("MouseEvents");
+        evObj.initMouseEvent("mousedown", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
+        elem.dispatchEvent(evObj);
+
+        var evObj = doc.createEvent("MouseEvents");
+        evObj.initMouseEvent("click", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
+        elem.dispatchEvent(evObj);
+    }
+
+    /* retrieves the url of given element */
+    function _getElemtSource(elem)
+    {
+        var url = elem.href || elem.src;
+        return url;
     }
 }
+hints = new Hints();
diff --git a/input-focus.js b/input-focus.js
index bc25ed4..f8d4212 100644
--- a/input-focus.js
+++ b/input-focus.js
@@ -1,33 +1,30 @@
 /*
-    (c) 2009 by Leon Winter
-    (c) 2009, 2010 by Hannes Schueller
-    (c) 2010 by Hans-Peter Deifel
-
-    Copyright (c) 2009 Leon Winter
-    Copyright (c) 2009, 2010 Hannes Schueller
-    Copyright (c) 2009, 2010 Matto Fransen
-    Copyright (c) 2010 Hans-Peter Deifel
-    Copyright (c) 2010 Thomas Adam
-
-    Permission is hereby granted, free of charge, to any person obtaining a copy
-    of this software and associated documentation files (the "Software"), to deal
-    in the Software without restriction, including without limitation the rights
-    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-    copies of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be included in
-    all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-    THE SOFTWARE.
+Copyright (c) 2009 Leon Winter
+Copyright (c) 2009-2011 Hannes Schueller
+Copyright (c) 2009-2010 Matto Fransen
+Copyright (c) 2010-2011 Hans-Peter Deifel
+Copyright (c) 2010-2011 Thomas Adam
+Copyright (c) 2011 Albert Kim
+Copyright (c) 2011 Daniel Carl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
 */
-
 function vimprobable_v(e, y) {
     t = e.nodeName.toLowerCase();
     if((t == 'input' && /^(text|password|checkbox|radio)$/.test(e.type))
diff --git a/xxxterm.c b/xxxterm.c
index 7ba1074..ce72408 100644
--- a/xxxterm.c
+++ b/xxxterm.c
@@ -94,8 +94,12 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
 javascript.h borrowed from vimprobable2 under the following license:
 
 Copyright (c) 2009 Leon Winter
-Copyright (c) 2009 Hannes Schueller
-Copyright (c) 2009 Matto Fransen
+Copyright (c) 2009-2011 Hannes Schueller
+Copyright (c) 2009-2010 Matto Fransen
+Copyright (c) 2010-2011 Hans-Peter Deifel
+Copyright (c) 2010-2011 Thomas Adam
+Copyright (c) 2011 Albert Kim
+Copyright (c) 2011 Daniel Carl
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -244,13 +248,7 @@ struct tab {
 #endif
 	/* hints */
 	int			hints_on;
-	int			hint_mode;
-#define XT_HINT_NONE		(0)
-#define XT_HINT_NUMERICAL	(1)
-#define XT_HINT_ALPHANUM	(2)
 	int			new_tab;
-	char			hint_buf[128];
-	char			hint_num[128];
 
 	/* custom stylesheet */
 	int			styled;
@@ -536,6 +534,9 @@ struct karg {
 
 #define XT_HINT_NEWTAB		(1<<0)
 
+#define XT_MODE_INSERT		(0)
+#define XT_MODE_COMMAND		(1)
+
 #define XT_TABS_NORMAL		0
 #define XT_TABS_COMPACT		1
 
@@ -2374,29 +2375,31 @@ js_ref_to_string(JSContextRef context, JSValueRef ref)
 void
 disable_hints(struct tab *t)
 {
-	bzero(t->hint_buf, sizeof t->hint_buf);
-	bzero(t->hint_num, sizeof t->hint_num);
-	run_script(t, "vimprobable_clear()");
+	DNPRINTF(XT_D_JS, "%s\n", __func__);
+
+	run_script(t, "hints.clearHints();");
 	t->hints_on = 0;
-	t->hint_mode = XT_HINT_NONE;
 	t->new_tab = 0;
 }
 
 void
 enable_hints(struct tab *t)
 {
-	bzero(t->hint_buf, sizeof t->hint_buf);
-	run_script(t, "vimprobable_show_hints()");
+	DNPRINTF(XT_D_JS, "%s\n", __func__);
+
+	run_script(t, JS_HINTING);
+
+	if (t->new_tab)
+		run_script(t, "hints.createHints('', 'F');");
+	else
+		run_script(t, "hints.createHints('', 'f');");
 	t->hints_on = 1;
-	t->hint_mode = XT_HINT_NONE;
 }
 
-#define XT_JS_OPEN	("open;")
-#define XT_JS_OPEN_LEN	(strlen(XT_JS_OPEN))
-#define XT_JS_FIRE	("fire;")
-#define XT_JS_FIRE_LEN	(strlen(XT_JS_FIRE))
-#define XT_JS_FOUND	("found;")
-#define XT_JS_FOUND_LEN	(strlen(XT_JS_FOUND))
+#define XT_JS_DONE		("done;")
+#define XT_JS_DONE_LEN		(strlen(XT_JS_DONE))
+#define XT_JS_INSERT		("insert;")
+#define XT_JS_INSERT_LEN	(strlen(XT_JS_INSERT))
 
 int
 run_script(struct tab *t, char *s)
@@ -2405,7 +2408,7 @@ run_script(struct tab *t, char *s)
 	WebKitWebFrame		*frame;
 	JSStringRef		str;
 	JSValueRef		val, exception;
-	char			*es, buf[128];
+	char			*es;
 
 	DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
 	    t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
@@ -2422,36 +2425,24 @@ run_script(struct tab *t, char *s)
 	if (val == NULL) {
 		es = js_ref_to_string(ctx, exception);
 		DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
-		g_free(es);
+		if (es) {
+			show_oops(t, "script exception: %s", es);
+			g_free(es);
+		}
 		return (1);
 	} else {
-		/* if set open in new tab */
-		if (t->new_tab)
-			t->ctrl_click = 1;
-
 		es = js_ref_to_string(ctx, val);
-		DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
-
-		/* handle return value right here */
-		if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
-			disable_hints(t);
-			marks_clear(t);
-			load_uri(t, &es[XT_JS_OPEN_LEN]);
-		}
-
-		if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
-			snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
-			    &es[XT_JS_FIRE_LEN]);
-			run_script(t, buf);
-			disable_hints(t);
-		}
-
-		if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
-			if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
-				disable_hints(t);
+#if 0
+		/* return values */
+		if (!strncmp(es, XT_JS_DONE, XT_JS_DONE_LEN))
+			; /* do nothing */
+		if (!strncmp(es, XT_JS_INSERT, XT_JS_INSERT_LEN))
+			; /* do nothing */
+#endif
+		if (es) {
+			DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
+			g_free(es);
 		}
-
-		g_free(es);
 	}
 
 	return (0);
@@ -2463,12 +2454,11 @@ hint(struct tab *t, struct karg *args)
 
 	DNPRINTF(XT_D_JS, "hint: tab %d args %d\n", t->tab_id, args->i);
 
-	if (args->i == XT_HINT_NEWTAB)
-		t->new_tab = 1;
-
-	if (t->hints_on == 0)
+	if (t->hints_on == 0) {
+		if (args->i == XT_HINT_NEWTAB)
+			t->new_tab = 1;
 		enable_hints(t);
-	else
+	} else
 		disable_hints(t);
 
 	return (0);
@@ -4806,11 +4796,22 @@ movetab(struct tab *t, struct karg *args)
 int cmd_prefix = 0;
 
 int
+command_mode(struct tab *t, struct karg *args)
+{
+	if (args->i == XT_MODE_COMMAND)
+		run_script(t, "hints.clearFocus();");
+	else
+		run_script(t, "hints.focusInput();");
+	return (XT_CB_HANDLED);
+}
+
+int
 command(struct tab *t, struct karg *args)
 {
 	char			*s = NULL, *ss = NULL;
 	GdkColor		color;
 	const gchar		*uri;
+	struct karg		a;
 
 	if (t == NULL || args == NULL) {
 		show_oops(NULL, "command invalid parameters");
@@ -4833,6 +4834,18 @@ command(struct tab *t, struct karg *args)
 			cmd_prefix = 0;
 		}
 		break;
+	case '.':
+		bzero(&a, sizeof a);
+		a.i = 0;
+		hint(t, &a);
+		s = ".";
+		break;
+	case ',':
+		bzero(&a, sizeof a);
+		a.i = XT_HINT_NEWTAB;
+		hint(t, &a);
+		s = ",";
+		break;
 	case XT_CMD_OPEN:
 		s = ":open ";
 		break;
@@ -5727,6 +5740,8 @@ struct key_binding {
 	guint				key;
 	TAILQ_ENTRY(key_binding)	entry;	/* in bss so no need to init */
 } keys[] = {
+	{ "command_mode",	0,	0,	GDK_Escape	},
+	{ "insert_mode",	0,	0,	GDK_i		},
 	{ "cookiejar",		MOD1,	0,	GDK_j		},
 	{ "downloadmgr",	MOD1,	0,	GDK_d		},
 	{ "history",		MOD1,	0,	GDK_h		},
@@ -5757,10 +5772,12 @@ struct key_binding {
 
 	/* hinting */
 	{ "hinting",		0,	0,	GDK_f		},
+	{ "hinting",		0,	0,	GDK_period	},
 	{ "hinting_newtab",	SHFT,	0,	GDK_F		},
+	{ "hinting_newtab",	0,	0,	GDK_comma	},
 
 	/* custom stylesheet */
-	{ "userstyle",		0,	0,	GDK_i		},
+	{ "userstyle",		0,	0,	GDK_s		},
 
 	/* navigation */
 	{ "goback",		0,	0,	GDK_BackSpace	},
@@ -6010,9 +6027,13 @@ struct cmd {
 	int		arg;
 	int		type;
 } cmds[] = {
+	{ "command_mode",	0,	command_mode,		XT_MODE_COMMAND,	0 },
+	{ "insert_mode",	0,	command_mode,		XT_MODE_INSERT,		0 },
 	{ "command",		0,	command,		':',			0 },
 	{ "search",		0,	command,		'/',			0 },
 	{ "searchb",		0,	command,		'?',			0 },
+	{ "hinting",		0,	command,		'.',			0 },
+	{ "hinting_newtab",	0,	command,		',',			0 },
 	{ "togglesrc",		0,	toggle_src,		0,			0 },
 
 	/* yanking and pasting */
@@ -7095,6 +7116,8 @@ notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
 		/* take focus if we are visible */
 		focus_webview(t);
 		t->focus_wv = 1;
+
+		marks_clear(t);
 #ifdef USE_THREAD
 		/* kill color thread */
 		t->thread = NULL;
@@ -7202,6 +7225,8 @@ void
 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
 {
 	run_script(t, JS_HINTING);
+	if (t->tab_id == gtk_notebook_get_current_page(notebook))
+		run_script(t, "hints.focusInput();");
 }
 
 void
@@ -7241,7 +7266,7 @@ webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
 	if (parse_xtp_url(t, uri))
 		    return (TRUE);
 
-	if (t->ctrl_click) {
+	if ((t->hints_on && t->new_tab) || t->ctrl_click) {
 		t->ctrl_click = 0;
 		create_new_tab(uri, NULL, ctrl_click_focus, -1);
 		webkit_web_policy_decision_ignore(pd);
@@ -7979,8 +8004,7 @@ handle_keypress(struct tab *t, GdkEventKey *e, int entry)
 int
 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
 {
-	char			s[2], buf[128];
-	const char		*errstr = NULL;
+	char			s[2];
 
 	/* don't use w directly; use t->whatever instead */
 
@@ -7993,115 +8017,7 @@ wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
 	    e->keyval, e->state, t);
 
 	if (t->hints_on) {
-		/* ESC */
-		if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
-			disable_hints(t);
-			return (XT_CB_HANDLED);
-		}
-
-		/* RETURN */
-		if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
-			if (errstr) {
-				/* we have a string */
-			} else {
-				/* we have a number */
-				snprintf(buf, sizeof buf,
-				    "vimprobable_fire(%s)", t->hint_num);
-				run_script(t, buf);
-			}
-			disable_hints(t);
-		}
-
-		/* BACKSPACE */
-		/* XXX unfuck this */
-		if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
-			if (t->hint_mode == XT_HINT_NUMERICAL) {
-				/* last input was numerical */
-				int		l;
-				l = strlen(t->hint_num);
-				if (l > 0) {
-					l--;
-					if (l == 0) {
-						disable_hints(t);
-						enable_hints(t);
-					} else {
-						t->hint_num[l] = '\0';
-						goto num;
-					}
-				}
-			} else if (t->hint_mode == XT_HINT_ALPHANUM) {
-				/* last input was alphanumerical */
-				int		l;
-				l = strlen(t->hint_buf);
-				if (l > 0) {
-					l--;
-					if (l == 0) {
-						disable_hints(t);
-						enable_hints(t);
-					} else {
-						t->hint_buf[l] = '\0';
-						goto anum;
-					}
-				}
-			} else {
-				/* bogus */
-				disable_hints(t);
-			}
-		}
-
-		/* numerical input */
-		if (CLEAN(e->state) == 0 &&
-		    ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
-		    (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
-			snprintf(s, sizeof s, "%c", e->keyval);
-			strlcat(t->hint_num, s, sizeof t->hint_num);
-			DNPRINTF(XT_D_JS, "wv_keypress_after_cb: num %s\n",
-			    t->hint_num);
-num:
-			if (errstr) {
-				DNPRINTF(XT_D_JS, "wv_keypress_after_cb: "
-				    "invalid link number\n");
-				disable_hints(t);
-			} else {
-				snprintf(buf, sizeof buf,
-				    "vimprobable_update_hints(%s)",
-				    t->hint_num);
-				t->hint_mode = XT_HINT_NUMERICAL;
-				run_script(t, buf);
-			}
-
-			/* empty the counter buffer */
-			bzero(t->hint_buf, sizeof t->hint_buf);
-			return (XT_CB_HANDLED);
-		}
-
-		/* alphanumerical input */
-		if ((CLEAN(e->state) == 0 && e->keyval >= GDK_a &&
-		    e->keyval <= GDK_z) ||
-		    (CLEAN(e->state) == GDK_SHIFT_MASK &&
-		    e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
-		    (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 &&
-		    e->keyval <= GDK_9) ||
-		    ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) &&
-		    (t->hint_mode != XT_HINT_NUMERICAL))))) {
-			snprintf(s, sizeof s, "%c", e->keyval);
-			strlcat(t->hint_buf, s, sizeof t->hint_buf);
-			DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical"
-			    " %s\n", t->hint_buf);
-anum:
-			snprintf(buf, sizeof buf, "vimprobable_cleanup()");
-			run_script(t, buf);
-
-			snprintf(buf, sizeof buf,
-			    "vimprobable_show_hints('%s')", t->hint_buf);
-			t->hint_mode = XT_HINT_ALPHANUM;
-			run_script(t, buf);
-
-			/* empty the counter buffer */
-			bzero(t->hint_num, sizeof t->hint_num);
-			return (XT_CB_HANDLED);
-		}
-
+		/* XXX make sure cmd entry is enabled */
 		return (XT_CB_HANDLED);
 	} else {
 		/* prefix input*/
@@ -8130,12 +8046,52 @@ wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
 }
 
 gboolean
+hint_continue(struct tab *t)
+{
+	const gchar		*c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
+	char			*s;
+	const gchar		*errstr = NULL;
+	gboolean		rv = TRUE;
+	long long		i;
+
+	if (!(c[0] == '.' || c[0] == ','))
+		goto done;
+	if (strlen(c) == 1) {
+		/* XXX should not happen */
+		rv = TRUE;
+		goto done;
+	}
+
+	if (isdigit(c[1])) {
+		/* numeric input */
+		i = strtonum(&c[1], 1, 4096, &errstr);
+		if (errstr) {
+			show_oops(t, "invalid numerical hint %s", &c[1]);
+			goto done;
+		}
+		s = g_strdup_printf("hints.updateHints(%lld);", i);
+		run_script(t, s);
+		g_free(s);
+	} else {
+		/* alphanumeric input */
+		s = g_strdup_printf("hints.createHints('%s', '%c');",
+		    &c[1], c[0] == '.' ? 'f' : 'F');
+		run_script(t, s);
+		g_free(s);
+	}
+
+	rv = TRUE;
+done:
+	return (rv);
+}
+
+gboolean
 search_continue(struct tab *t)
 {
 	const gchar		*c = gtk_entry_get_text(GTK_ENTRY(t->cmd));
 	gboolean		rv = FALSE;
 
-	if (c[0] == ':')
+	if (c[0] == ':' || c[0] == '.' || c[0] == ',')
 		goto done;
 	if (strlen(c) == 1) {
 		webkit_web_view_unmark_text_matches(t->wv);
@@ -8198,6 +8154,13 @@ cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
 	DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
 	    e->keyval, e->state, t);
 
+	/* hinting */
+	if (!(e->keyval == GDK_Tab || e->keyval == GDK_ISO_Left_Tab)) {
+		if (hint_continue(t) == FALSE)
+			goto done;
+	}
+
+	/* search */
 	if (search_continue(t) == FALSE)
 		goto done;
 
@@ -8558,6 +8521,7 @@ cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
 {
 	int			rv = XT_CB_HANDLED;
 	const gchar		*c = gtk_entry_get_text(w);
+	char			*s;
 
 	if (t == NULL) {
 		show_oops(NULL, "cmd_keypress_cb parameters");
@@ -8570,7 +8534,8 @@ cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
 	/* sanity */
 	if (c == NULL)
 		e->keyval = GDK_Escape;
-	else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
+	else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?' ||
+	    c[0] == '.' || c[0] == ','))
 		e->keyval = GDK_Escape;
 
 	if (e->keyval != GDK_Tab && e->keyval != GDK_Shift_L &&
@@ -8581,11 +8546,14 @@ cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
 	case GDK_Tab:
 		if (c[0] == ':')
 			cmd_complete(t, (char *)&c[1], 1);
+		else if (c[0] == '.' || c[0] == ',')
+			run_script(t, "hints.focusNextHint();");
 		goto done;
 	case GDK_ISO_Left_Tab:
 		if (c[0] == ':')
 			cmd_complete(t, (char *)&c[1], -1);
-
+		else if (c[0] == '.' || c[0] == ',')
+			run_script(t, "hints.focusPreviousHint();");
 		goto done;
 	case GDK_Down:
 		if (c[0] != ':') {
@@ -8620,8 +8588,19 @@ cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
 
 		goto done;
 	case GDK_BackSpace:
-		if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
+		if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?") ||
+		    !strcmp(c, ".") || !strcmp(c, ","))) {
+			/* see if we are doing hinting and reset it */
+			if (c[0] == '.' || c[0] == ',') {
+				/* recreate hints */
+				s = g_strdup_printf("hints.createHints('', "
+				    "'%c');", c[0] == '.' ? 'f' : 'F');
+				run_script(t, s);
+				g_free(s);
+			}
 			break;
+		}
+
 		/* FALLTHROUGH */
 	case GDK_Escape:
 		hide_cmd(t);
@@ -8630,6 +8609,8 @@ cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
 		/* cancel search */
 		if (c != NULL && (c[0] == '/' || c[0] == '?'))
 			webkit_web_view_unmark_text_matches(t->wv);
+
+		/* no need to cancel hints */
 		goto done;
 	}
 
@@ -8670,6 +8651,7 @@ cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
 
 	hide_cmd(t);
 	hide_oops(t);
+	disable_hints(t);
 
 	if (show_url == 0 || t->focus_wv)
 		focus_webview(t);
@@ -8692,14 +8674,13 @@ cmd_activate_cb(GtkEntry *entry, struct tab *t)
 
 	DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
 
-	hide_cmd(t);
-
 	/* sanity */
 	if (c == NULL)
 		goto done;
-	else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
+	else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?' ||
+	    c[0] == '.' || c[0] == ','))
 		goto done;
-	if (strlen(c) < 2)
+	if (strlen(c) < 1)
 		goto done;
 	s = (char *)&c[1];
 
@@ -8724,11 +8705,16 @@ cmd_activate_cb(GtkEntry *entry, struct tab *t)
 
 		history_add(&shl, search_file, s, &search_history_count);
 		goto done;
+	} else if (c[0] == '.' || c[0] == ',') {
+		run_script(t, "hints.fire();");
+		/* XXX history for link following? */
+		goto done;
 	}
 
 	history_add(&chl, command_file, s, &cmd_history_count);
 	cmd_execute(t, s);
 done:
+	hide_cmd(t);
 	return;
 }