Keyboard Shortcuts

Keyboard shortcuts are defined in the configuration file with rex.bind and removed with rex.unbind. A shortcut binds a key to an action, such as splitting a pane or switching sessions.

rex.bind("cmd+e", "pane.zoom")
rex.bind("shift+cmd+d", "pane.split", { direction = "down" })

Run rex config reload after changing the file for the shortcuts to take effect.

Binding a Key

rex.bind takes a key, the name of an action, and optionally a table of arguments for that action:

rex.bind(key, action, args)

Some actions require arguments and some have optional arguments. For example, pane.resize requires a direction and accepts an optional amount:

rex.bind("ctrl+alt+left", "pane.resize", { direction = "left" })
rex.bind("ctrl+alt+shift+left", "pane.resize", {
  direction = "left",
  amount = 15,
})

Binding a key that already has a binding replaces it. The newest binding for a key always wins.

Finding Actions

Actions are provided by the client, so the list depends on which client you use. List the actions of a connected client with the CLI:

$ rex client ls
LABEL     CLIENT                KIND  PRINCIPAL          AGE     ID
my-mac    Rex Beta 0.1.0 (996)  app   uid 501 over unix  1h ago  db494d

$ rex -C my-mac actions
NAME                   TITLE             ARGS
client.tab.new         New Tab
client.tab.goto        Go to Tab         index
pane.split             Split Pane        direction
pane.zoom              Zoom Pane
pane.resize            Resize Pane       direction, amount?
pane.send_key          Send Key          key
session.switch         Change Session…
...

The ARGS column lists the arguments each action takes. An argument followed by ? is optional.

You can also define your own actions in Lua and bind keys to them. See Custom Actions.

Writing Keys

A key is written as zero or more modifiers followed by one key, joined by +. Case and the order of modifiers don’t matter, so shift+cmd+d and Cmd+Shift+D are the same key.

ModifierAlso Written
cmdcommand, super, meta, win
ctrlcontrol
altopt, option
shift

The key is a character such as d, 1, or , or a name such as enter, escape, tab, space, backspace, delete, left, right, up, down, home, end, pageup, pagedown, or f1 through f12.

Keys follow your keyboard layout: ctrl+a is whichever key types “a”. To bind a physical key regardless of layout, write its US-layout name in brackets, such as ctrl+[KeyA]. The names are the W3C KeyboardEvent.code values, such as [Digit1], [Slash], and [ArrowLeft].

Write the key without shift applied. On a US layout, plus is cmd+shift+=, not cmd++. Bindings are always matched on unshifted keys.

A shortcut can also be several keys pressed one after another, such as ctrl+b>c. See Key Sequences.

Shortcuts can also be grouped into a mode that is only active when you enter it, such as a mode for resizing panes. See Key Modes.

Removing a Shortcut

rex.unbind removes every binding for a key. It can be used to remove a binding you set as well as unbind a key that the client defaults with:

rex.unbind("cmd+d")

Pass an action name to remove the key only when it is bound to that action:

rex.unbind("cmd+t", "client.tab.new")

An unbound key isn’t handled by Rex at all, so it is sent to the focused terminal.

To change what a default shortcut does, bind it. There is no need to unbind it first:

-- Cmd+K clears the screen instead
rex.bind("cmd+k", "pane.send_key", { key = "ctrl+l" })

How Shortcuts Are Resolved

A key can get its meaning from three places. From lowest to highest priority:

  1. The default shortcuts the client ships with.
  2. Your configuration file.
  3. Shortcuts you set in the client itself, such as with the app’s Change Shortcut… command.

A shortcut set in the app therefore wins over the configuration file. If a binding in your file appears to do nothing, check that the app doesn’t have its own shortcut for the same key.

Binding a Lua Function

A key can run a Lua function instead of an action. Return true to say the key was handled:

rex.bind("ctrl+shift+n", function(ctx, ev)
  rex.session.new_window{
    layout = rex.layout.block{
      flavor = "com.superlogical.terminal.shell",
    },
  }
  return true
end)

The function can call any Rex API, such as rex.session.new_window above. The Lua API reference has the full list.

If the function returns false or nothing, Rex tries the next-newest function bound to the same key, and sends the key to the focused terminal if none of them handle it.

A function binding has no name, so it doesn’t appear in the command palette and can’t be run from the CLI. For anything you’d want to do more than one way, define a custom action and bind the key to that.

Inspecting Bindings

A configuration that loads other files with require can look at what they bound and remove what it doesn’t want. rex.bindings() returns every binding made so far, in the order they were made.

require("keys")

-- Remove every sequence keys.lua bound behind ctrl+b
for _, b in ipairs(rex.bindings()) do
  if b.key:find("ctrl+b>", 1, true) == 1 then
    rex.unbind(b)
  end
end

Each binding is a table with key, mode, action, args, and source (the file and line that made it). Passing one to rex.unbind removes exactly that binding.

rex.bindings also accepts a filter on key, mode, or action, such as rex.bindings{ action = "pane.split" }. rex.modes() does the same for key modes.

These functions only see your configuration file and the files it loads. They can’t see the client’s default shortcuts or the ones set in the app.

Debugging

rex config check reports mistakes in the file itself, such as a key that can’t be parsed:

$ rex config check
...
error init.lua:1: unknown key "foo"
Error: the configuration has errors

rex keymap asks the running server what it loaded and lists each binding with the line that made it:

$ rex keymap
KEY          ACTION          ARGS                  SOURCE
shift+cmd+d  pane.split      {"direction":"down"}  init.lua:2
cmd+e        pane.zoom       -                     init.lua:1
...

A misspelled action name is not an error in rex config check, because the file alone can’t know which actions a client has. The client reports it as a warning when it loads the keymap.