Why vimperator hint mode doesn’t weed out duplicate links

Vimperator has a commonly used “hint” mode where you may enter either a generated combination of characters or link’s text to interact with that element.

As you type characters in, vimperator filters out matching links for you. For example, if there’s two links with the text “About” in them, typing in ‘a’, ‘b’, … slowly narrows the selection down to these items.

But what if you’ve typed “Abo” and there’s two “About” links (and nothing else containing “abo” in the page) pointing to the exact same href? Shouldn’t vimperator just go ahead and load either on its own? Yeah, let’s try to patch that in.


common/content/hints.js handles this mode.

Looking at it you will likely be drawn to a _checkUnique function, but that only deals with entering “hintchars”, not matching against the element’s text.

if (this._hintNumber * options.hintchars.length <= this._validHints.length) {
    let timeout = options.hinttimeout;
    if (timeout > 0)
        this._activeTimeout = this.setTimeout(function () { this._processHints(true); }, timeout);
    return false;
}

You will also find _showHints which calls the hintMatcher. This matcher is not aware of anything but the string the user has given and the link text, so there the filtering on the level that we want to happen is not going to be placed there.

let validHint = this._hintMatcher(this._hintString.toLowerCase());

...

let valid = validHint(hint.text);

But what calls _showHints that could be handling our use-case? The _onInput function. You will notice the behavior is to not decide on a link until the selection has been narrowed down to one link. A-ha, this is what we want to change. We want to interate over the still-matching elems and see if they all link to the same URL, right?

this._showHints();
if (this._validHints.length == 1)
    this._processHints(false);

For that we need to extract the URL from elem. I see elem is a child of hint, but there’s no reference to elem.url or anything similar anywhere in the code.

How is a link even followed then? Looking at the show function, you will see that a lookup is made on the _hintModes table to figure out how to do that. You’ll then see the entire elem is passed to buffer.followLink. Weird, why does it need that entire object?

this._hintMode = this._hintModes[minor];
commandline.input(this._hintMode.prompt, null, { onChange: this.closure._onInput });

...

o: Mode("Follow hint",                          function (elem) buffer.followLink(elem, liberator.CURRENT_TAB)),

A-ha. followLink does a fake click…

/**
 * Fakes a click on a link.
 *
 * ...
 */

And now why vimperator doesn’t already do the filtering I wanted to patch in is obvious. Javascript makes it impossible to decide whether two links are the same, thus you can’t guess which one to click if there is any doubt.

Woops. No patching today.