Tips & Tricks 4: making keybinds

Table of contents


Welcome to the fourth tips and tricks newsletter for donors! Sorry for the extreme tardiness - I moved house and lost track of time.

This newsletter is going to cover probably the most important feature of Tridactyl: the ability to have stuff happen when you press keys in a certain order, which we call binding. It’s a topic with a surprising amount of depth so feel free to dip in and out of this newsletter. It’s probably a bit too long and broad to tackle in one sitting.

Binding ¶

In Tridactyl, we bind sequences of keypresses to ex-strings (also called ex-commands) which are immediately executed. Most actions in Tridactyl - for example, scrolling down (:scrollline) or switching to a specific tab (:tab [N]) - are ex-commands. Keys which are not part of a valid key-sequence are passed through to websites and Firefox.

Firefox reserves some key-combinations such as Ctrl-T and Ctrl-W; Tridactyl binds using these key combinations will not work (and there is no easy way of finding out other than just trying them). If you’re feeling intrepid you can overcome this restriction by running this script on Linux - https://github.com/alerque/que-firefox/blob/6dd5d6c9e1f60ff888cda33d8f2c198458b14a71/bin/patch-firefox.sh - but you’ll need to run it each time Firefox updates.

We don’t map key sequences to key sequences like Vim does (so, for example, you can’t bind j to the down arrow) due to Firefox limitations.

The general syntax for binding is simply :bind [key sequence] [ex-string]. We’ll now get into the gory details…

Each element of a key sequence is either:

The special permitted modifiers are:

The names of special keys can be found here: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values

A key sequence is made up of a series of elements with no separators between them.

For example, the key sequence ,<C-ArrowDown>k would match the following interaction with your keyboard:

  1. , pressed and released
  2. Control pressed and held
  3. ArrowDown pressed and released
  4. Control released
  5. k pressed and released

All binds accept numeric prefixes, such as 2gt which would take you to the second tab. They do this by simply appending the supplied prefix to the bound ex-string before it is executed. For example, if I run

:bind ,j fillcmdline tabopen
10,j

fillcmdline tabopen 10 will be executed and the command line will appear prefilled with :tabopen 10 .

This does mean that binds cannot start with a numeric prefix (but e.g. bind ,1 [ex string] does work).

Not all ex-strings will do anything meaningful with the supplied count - but many will. If there is a command that you would like to support counts, just file an issue - it’s usually easy for us to add.

By default, :bind binds key sequences to ex-strings in normal mode (the mode you are in most of the time). You can create bindings in all of the modes in Tridactyl - even ignore mode - very simply:

:bind --mode=[modename] [key sequence] [ex-string]

For example, to make : work in ignore mode, you could run :bind --mode=ignore : fillcmdline_notrail. Ex-strings that are exclusively meaningful in a specific mode have the syntax [mode].[command name] - for example, ex.accept_line is the ex-string that tells Tridactyl to execute the command that is currently written in the command line. See the “Viewing binds” section for more details on these mode-specific ex-strings.

Unbinding ¶

Unbound keys are passed through to the webpage or Firefox. Unbind a key sequence with :unbind [--mode=[mode=normal] [keysequence], for example, :unbind <C-f>.

Keys which are not bound in Tridactyl are passed through to websites and Firefox, so websites and Firefox can bind actions to them. If a website wants to bind to a key, usually it takes priority over Firefox except for a few special key combinations.

If a page is stealing a key that you want Firefox to claim, for example, ' (Firefox’s “focus link matching text” mode), run :set blacklistkeys ["'","/"]. Note that you must use JavaScript array syntax and that you need to include all the keys you wish to protect, including / which is protected by default. If you omitted / it would no longer be protected.

NB: this works on most but not all pages. See issue #904.

Resetting a bind to default ¶

reset [key sequence] resets the key sequence to its default value. Not to be confused with :unset which does the same thing but for settings (the insanity in the naming comes from the fact that we had keybinds before we had general settings - sorry!).

Per-site binds ¶

:bindurl [url regex] and :unbindurl allow you to bind keys on sites that match the supplied regex. This is often used to control which links have hints displayed on them, as was explored extensively in the first newsletter.

Unbinding keys per site works similarly with :unbindurl. For example, it is common to unbind f on YouTube so that YouTube’s own bind for f to toggle fullscreen video can be used: :unbindurl ^http(s?)://youtube\.com f.

Special modes ¶

Most modes are just collections of key sequences bound to ex-strings. There are some exceptions to this; sometimes keys perform some other action and sometimes modes have other properties.

Here I’ll list each of the slightly weird modes and what is weird about them.

In hint mode, keys being fed to the hint filter are not done so using an ex-string - see :set hintchars instead to control which keys perform filtering. The rest of the commands in the mode are bound to ex-strings - for example, <Enter> is bound to hint.selectFocusedHint.

There’s nothing special about keys in this mode. Insert mode is entered automatically once a text area is focused, unless you’re already in input mode (e.g. from gi). You are returned to normal mode once a text area loses focus.

You may find the text. ex-strings useful for manipulating text - they can be viewed by clicking the link at the top of the :help page where it says “Tridactyl also provides a few functions to manipulate text in the command line or text areas that can be found here.”.

Ex-mode, the mode you are in when the command line is open, is a fairly standard mode where key sequences are bound to ex-strings. For example, <C-o>yy is bound to :ex.execute_ex_on_completion_args clipboard yank which copies the highlighted completion to the clipboard. The exception to this is that the command line is a normal text-area - there are no key-sequences associated with characters entered into the text-area. Filtering is done on the contents of this text-area (and so, e.g., pasting text works normally there).

This mode is unlike any other. Rather than binding key sequences to ex-strings, it binds “octopus-style” Mozilla-approved keyboard shortcuts firstly to “user actions” and, if it finds none, ex-strings. It has two advantages to the other modes: it is available everywhere in the browser, including protected pages such as about:addons, and it can perform user-actions (actions which Mozilla will only allow to happen if we can prove that a user instigated it) which our usual binds cannot. For now, the only user-action we have is escapehatch which opens and closes the sidebar to get focus back to the page, bound to <C-,> by default.

Valid keyboard shortcuts consist of one or more modifiers (such as control) and a normal key, so long as that combination of modifiers and key is not on a blacklist of binds that are Mozilla thinks are too important for you to redefine. No errors are provided by Mozilla if the shortcut you choose is on the blacklist - it just won’t work when you press the shortcut.

This blacklist varies per system - see https://searchfox.org/mozilla-central/rev/1f4e023df8ff04b893ca792492f1e2e9629bfddb/toolkit/modules/ShortcutUtils.jsm#315 and https://searchfox.org/mozilla-central/rev/1f4e023df8ff04b893ca792492f1e2e9629bfddb/toolkit/modules/ShortcutUtils.jsm#287 if you want to get an idea of how complicated it is. My personal approach is this: try binding it. If it doesn’t work, assume it’s on the blacklist and try a different shortcut.

A workaround if your desired shortcut is on the blacklist is to use an external program to map that shortcut to another one which can be used. For example, you could use xkeysnail under X11/Linux, AutoHotKey under Windows, or karabiner under OSX.

If you wanted to bind <C-t> to :tabopen to avoid focus on the address bar, with xkeysnail’s config.py you might do:

# ~/.config/xkeysnail/config.py
from xkeysnail.transform import *

define_keymap(re.compile("Firefox|Google-chrome"), {
    # Rebind ctrl-t to ctrl-. so it can be rebound
    K("C-t"): K("C-."),
}, "Firefox and Chrome")
# ~/.config/tridactyl/tridactylrc
bind --mode=browser <C-.> tabopen

As with insert mode, visual mode is entered and left automatically. This happens when text is selected or deselected and can be controlled with :set visual{enter,exit}auto.

This is a special “meta” mode. It accepts N key sequences, regardless of validity, in the underlying mode and then executes the final command provided. There is only one explicit bind in nmode: pressing <Esc> executes the final command immediately.

E.g. :nmode [underlying mode] [key sequences to accept] [final ex-string]. For example, in ignore mode, <C-o> is bound to nmode normal 1 mode ignore, which allows you to execute one bind in normal mode before returning to ignore mode. In normal mode, <C-v> similarly runs :nmode ignore 1 mode normal which lets you pass a single key through to a website. I personally find this particularly useful on YouTube: <C-v>f toggles fullscreen video by using the default YouTube bind.

Mapping keys to keys for Tridactyl ¶

You can map single characters to other characters within Tridactyl with keymap [source] [dest].

This is mostly useful for non-QWERTY users who want to be able to use Tridactyl binds without having to rebind lots of them manually. See the wiki for example usage for Cyrillic and bépo layouts: https://github.com/tridactyl/tridactyl/wiki/Internationalisation.

Getting the command line back ¶

There’s nothing in Tridactyl that prevents you from unbinding :, the key which lets you access the command line. If this happens to you, you can get it back easily by using the Firefox address bar to run tri reset :. In general tri [ex-command] is mostly equivalent to running the supplied ex-command in the command line and is very handy in a pinch.

Viewing binds ¶

You can see binds with bind completions (e.g. :bind --mode=visual) or, en masse, with :viewconfig [name of bind store]. All of these “store” names end with the word maps. For sentimental reasons the most common modes have irregular names. These are:

Every other mode, including custom modes, follows the convention [mode name]maps. For example, ignore mode has the store ignoremaps.

So to view the ignore mode bindings you can run :viewconfig ignoremaps. If you wish to only view the bindings that you have set, run :viewconfig --user ignoremaps.

For normal mode you can also do :help [key sequence].

There is currently a bug in Tridactyl where the help for ex-strings for other modes, e.g. hint. is not available. Follow https://github.com/tridactyl/tridactyl/issue/3626 and https://github.com/tridactyl/tridactyl/issue/2926 for updates.

Shadowing ¶

If you try to bind to a key sequence for which there already exists a bind to a prefix of that key sequence, you will get a warning and you will be unable to use the bind (although it will still be made).

For example, if you have a bind for j and add a bind jk, you will get a warning that jk is shadowed by j - you won’t be able to execute jk as the bind for j will immediately be executed when you press j. However, you would be able to bind to ,jk and execute that bind. If you wished to be able to use jk you would first need to run :unbind j.

Custom modes ¶

Creating a new mode is very straightforward: simply add a bind with :bind --mode=[new modename]. It’s wise to make this first bind be a way to return to normal mode, e.g. :bind --mode=mynewmode <Esc> mode normal. You can enter the mode with :mode [new modename]. You might want to run this from a bind or in an autocmd, e.g. :autocmd DocStart ^https://mysite.com mode mysitemode.

Custom modes can serve a similar purpose to :bindurl, but are easier to apply to multiple sites or across parts of a site. For example, you might want a special video mode that you trigger on :autocmd FullscreenEnter.

Custom modes are often existing modes plus a few extra binds - by default, for example, visual mode is based on normal mode and input mode (where you can press <Tab> to cycle through text boxes) is based on insert mode.

We have made this marginally less painful by adding a special optional 🕷🕷INHERITS🕷🕷 field to the bind stores, which tells a mode that it should add its bindings on top of the specified mode. For now, only default bindings are inherited - bindings you make to modes are not inherited - but we’d like to fix that someday.

You can set the field with set [mode name]maps.🕷🕷INHERITS🕷🕷 [mode name to inherit from]maps. E.g. set youtubemaps.🕷🕷INHERITS🕷🕷 ignoremaps if I had made a youtube mode. NB: the [mode name]maps follow the naming conventions specified in the “Viewing binds” section.

Final remarks ¶

The next tips and tricks newsletter will be on the native messenger, but the next newsletter you will receive will be the quarterly newsletter. I’ll probably finish that within a week or two. And apologies once again for the lateness of this newsletter!

Thanks for your support, bovine3dom