Skip to content

Actions and Bindings

Actions are named operations. Bindings connect actions to keys. Both can be defined in config.toml or config.lua — use whichever fits your workflow.

Use [[actions]] with an inline Lua snippet:

[[actions]]
name = "copy-change-id"
desc = "copy change id to clipboard"
lua = '''
local id = context.change_id()
if id then
copy_to_clipboard(id)
flash("Copied: " .. id)
end
'''

Pass a function directly to config.action:

function setup(config)
config.action("copy-change-id", function()
local id = context.change_id()
if id then
copy_to_clipboard(id)
flash("Copied: " .. id)
end
end, { desc = "copy change id to clipboard" })
end

[[bindings]]
action = "copy-change-id"
key = "Y"
scope = "revisions"
desc = "copy change id"

Either pass binding options directly to config.action:

config.action("copy-change-id", fn, {
key = "Y",
scope = "revisions",
desc = "copy change id",
})

Or bind separately with config.bind:

config.bind({
action = "copy-change-id",
key = "Y",
scope = "revisions",
desc = "copy change id",
})

FieldRequiredNotes
actionyesAction name or built-in action ID
scopeyesWhere the binding is active
key or seqyesSet exactly one
descnoLabel shown in the help/status bar
argsnoArguments for built-in actions

key accepts a single key or a list of alternatives:

key = "r"
key = ["up", "k"]

seq defines an ordered key sequence (chord):

[[bindings]]
action = "ui.open_revset"
seq = ["g", "r"]
scope = "revisions"
desc = "open revset"

You can bind any built-in action by its action ID:

[[bindings]]
action = "ui.open_revset"
key = "L"
scope = "revisions"
desc = "revset"

All built-in action IDs and their default bindings are defined in internal/config/default/bindings.toml in the source repository.


You can replace a built-in action’s behavior entirely by defining an action with the same name as a built-in. Use jjui.builtin.* to call the original implementation from your override.

[[actions]]
name = "revisions.move_down"
lua = '''
jjui.builtin.revisions.move_down()
flash("Moved down to: " .. context.change_id())
'''
function setup(config)
config.action("revisions.move_down", function()
jjui.builtin.revisions.move_down()
flash("Moved down to: " .. context.change_id())
end)
end

Both examples override move_down to show a flash message with the change ID after each move, while still performing the original navigation via jjui.builtin.revisions.move_down().

You can use this pattern with any built-in action — for example, making toggle_select automatically advance the cursor:

config.action("revisions.toggle_select", function()
jjui.builtin.revisions.toggle_select()
jjui.builtin.revisions.move_down()
end)

Legacy [custom_commands] and [leader] sections are no longer supported. See Migrating to v0.10.

Contribute Community