Skip to content

Lua Scripting

jjui exposes a Lua API for custom workflows and UI automation.

For startup configuration wiring (setup(config), config.action, config.bind), see config.lua.

Define a Lua action in config.toml and bind it:

[[actions]]
name = "hello-lua"
lua = '''
flash("Hello from Lua")
'''
[[bindings]]
action = "hello-lua"
key = "ctrl+h"
scope = "revisions"
desc = "hello"

All functions are available both as top-level globals and under the jjui namespace — flash(...) and jjui.flash(...) are the same.

Built-in action wrappers are additionally available under jjui.builtin.*, which forces built-in resolution and ignores any user-defined overrides for those action names.


Read-only access to the currently highlighted or selected item.

FunctionReturnsNotes
context.change_id()stringAvailable for revisions and files
context.commit_id()stringAvailable for revisions, files, and commits
context.file()stringOnly when a file is selected in the details view
context.operation_id()stringOnly in the oplog
context.checked_files()string[]Files checked in the details view
context.checked_change_ids()string[]Checked revisions or files
context.checked_commit_ids()string[]Checked revisions, files, or commits

FunctionReturnsNotes
revisions.current()stringchange_id of the highlighted revision
revisions.checked()string[]change_id of every checked revision
revisions.refresh({ keep_selections?, selected_revision? })Yields until the revision list finishes loading
revisions.navigate({ by?, page?, target?, to?, fallback?, ensureView?, allowStream? })target: "parent", "child", or "working"

FunctionReturns
revset.current()Active revset expression string
revset.default()Default revset from config

FunctionReturnsNotes
jj(...args)(output, err)Runs synchronously; returns output string and error
jj_async(...args)Yields until the command completes
jj_interactive(...args)Opens an interactive terminal for the command

Args can be passed as individual strings or as a single table of strings.


FunctionReturnsNotes
flash(text)Show a short status message
flash({ text, error?, sticky? })error = true styles as error; sticky = true keeps it visible
choose(option1, option2, ...)string or nilShow a picker; returns the selected value or nil if cancelled
choose({ options, title?, filter?, ordered? })string or nilTable form
input({ title?, prompt? })string or nilShow a text input dialog; returns the entered text or nil
copy_to_clipboard(text)(true, nil) or (nil, err)Copy to system clipboard
split_lines(text, keep_empty?)string[]Split on newlines; strips \r; skips empty lines by default
exec_shell(command)Run a shell command line; yields until done

These pause the script until a matching UI event arrives.

FunctionReturnsNotes
wait_close()boolWaits for any modal or view to close; returns true if it was applied
wait_refresh()Waits for the revision list to finish refreshing

Every built-in action is exposed as a Lua function via jjui.<scope>.<action>(). These dispatch the action into the UI and yield until it is handled, so they compose naturally with other async steps.

Actions that take a single required string arg accept it positionally:

revset.set("trunk()")
-- equivalent to:
revset.set({ value = "trunk()" })
FunctionNotes
revisions.absorb()Run jj absorb on the current revision
revisions.new()Create a new revision
revisions.commit()
revisions.describe()Open external editor to edit description
revisions.edit()
revisions.force_edit()
revisions.diff()
revisions.diff_edit()
revisions.split()
revisions.split_parallel()
revisions.apply({ force? })
revisions.force_apply()
revisions.toggle_select()
revisions.refresh()
revisions.move_up() / move_down()
revisions.page_up() / page_down()
revisions.jump_to_parent()
revisions.jump_to_children()
revisions.jump_to_working_copy()
revisions.ace_jump()
revisions.quick_search_next() / _prev() / _clear()
revisions.open_abandon()Enter abandon mode
revisions.open_details()Open the details view
revisions.open_duplicate()Enter duplicate mode
revisions.open_evolog()Open the evolution log
revisions.open_inline_describe()Start inline describe
revisions.open_rebase()Enter rebase mode
revisions.open_revert()Enter revert mode
revisions.open_set_bookmark()Open bookmark input
revisions.open_set_parents()Enter set-parents mode
revisions.open_squash()Enter squash mode
FunctionArgs
revisions.rebase.set_source({ source })"revision", "branch", or "source" (descendants)
revisions.rebase.set_target({ target })"onto", "after", "before", or "insert"
revisions.rebase.skip_emptied()Toggle
revisions.rebase.apply({ force? })
revisions.rebase.force_apply()
revisions.rebase.cancel()
revisions.rebase.target_picker()
revisions.rebase.ace_jump()
revisions.rebase.jump_to_working_copy()
FunctionArgs
revisions.squash.interactive()Toggle
revisions.squash.keep_emptied()Toggle
revisions.squash.use_destination_msg()Toggle
revisions.squash.apply({ force? })
revisions.squash.force_apply()
revisions.squash.cancel()
revisions.squash.target_picker()
revisions.squash.ace_jump()
revisions.squash.jump_to_working_copy()
FunctionArgs
revisions.duplicate.set_target({ target })"onto", "after", "before", or "insert"
revisions.duplicate.apply({ force? })
revisions.duplicate.force_apply()
revisions.duplicate.cancel()
revisions.duplicate.target_picker()
revisions.duplicate.ace_jump()
revisions.duplicate.jump_to_working_copy()
FunctionArgs
revisions.revert.set_target({ target })"onto", "after", "before", or "insert"
revisions.revert.apply({ force? })
revisions.revert.force_apply()
revisions.revert.cancel()
revisions.revert.target_picker()
Function
revisions.abandon.toggle_select()
revisions.abandon.select_descendants()
revisions.abandon.apply({ force? })
revisions.abandon.force_apply()
revisions.abandon.cancel()
revisions.abandon.ace_jump()
revisions.abandon.jump_to_working_copy()
Function
revisions.set_parents.toggle_select()
revisions.set_parents.apply()
revisions.set_parents.cancel()
revisions.set_parents.ace_jump()
revisions.set_parents.jump_to_working_copy()
FunctionNotes
revisions.inline_describe.accept()Apply the description
revisions.inline_describe.editor()Open external editor
revisions.inline_describe.cancel()Cancel without saving
FunctionArgs
revisions.details.select_file(file)Required string (positional)
revisions.details.toggle_select()
revisions.details.squash()
revisions.details.absorb()
revisions.details.split()
revisions.details.split_parallel()
revisions.details.restore()
revisions.details.diff()
revisions.details.refresh()
revisions.details.revisions_changing_file()
revisions.details.move_up() / move_down()
revisions.details.page_up() / page_down()
revisions.details.cancel() / revisions.details.quit()

Shown when a details operation (e.g. squash) affects multiple revisions and needs confirmation.

Function
revisions.details.confirmation.apply({ force? })
revisions.details.confirmation.force_apply()
revisions.details.confirmation.cancel()
revisions.details.confirmation.next()
revisions.details.confirmation.prev()
Function
revisions.evolog.restore()
revisions.evolog.apply({ force? })
revisions.evolog.diff()
revisions.evolog.move_up() / move_down()
revisions.evolog.page_up() / page_down()
revisions.evolog.cancel() / revisions.evolog.quit()
Function
revisions.ace_jump.apply()
revisions.ace_jump.cancel()

Shared target picker used by rebase, squash, duplicate, and revert.

Function
revisions.target_picker.move_up() / move_down()
revisions.target_picker.apply({ force? })
revisions.target_picker.force_apply()
revisions.target_picker.cancel()
revisions.target_picker.autocomplete() / autocomplete_back()
Function
revisions.set_bookmark.apply()
revisions.set_bookmark.cancel()
revisions.set_bookmark.autocomplete() / autocomplete_back()
FunctionArgs
revset.set(value)Required string (positional)
revset.reset()
revset.edit({ clear? })Open the revset editor
revset.apply()
revset.cancel()
revset.autocomplete() / autocomplete_back()Cycle completion suggestions
revset.move_up() / revset.move_down()Navigate completion list
FunctionNotes
ui.cancel()
ui.quit()
ui.suspend()
ui.open_bookmarks()
ui.open_git()
ui.open_oplog()
ui.open_undo()
ui.open_redo()
ui.open_revset()
ui.open_command_history()
ui.file_search_toggle()
ui.quick_search()
ui.expand_status()
ui.preview_toggle()
ui.preview_toggle_bottom()
ui.preview_expand() / ui.preview_shrink()
ui.preview_scroll_down() / ui.preview_scroll_up()
ui.preview_half_page_down() / ui.preview_half_page_up()
ui.exec_jj()Open the jj command prompt
ui.exec_shell()Open the shell command prompt
Function
oplog.restore()
oplog.revert()
oplog.diff()
oplog.move_up() / oplog.move_down()
oplog.page_up() / oplog.page_down()
oplog.close() / oplog.quit()
Function
bookmarks.cycle_remotes() / bookmarks.cycle_remotes_back()
bookmarks.filter()
bookmarks.bookmark_delete()
bookmarks.bookmark_forget()
bookmarks.bookmark_move()
bookmarks.bookmark_track()
bookmarks.bookmark_untrack()
bookmarks.move_up() / bookmarks.move_down()
bookmarks.page_up() / bookmarks.page_down()
bookmarks.apply() / bookmarks.cancel() / bookmarks.quit()
Function
git.push()
git.fetch()
git.filter()
git.cycle_remotes() / git.cycle_remotes_back()
git.move_up() / git.move_down()
git.page_up() / git.page_down()
git.apply() / git.cancel() / git.quit()
Function
diff.scroll_up() / diff.scroll_down()
diff.page_up() / diff.page_down()
diff.half_page_up() / diff.half_page_down()
diff.move_top() / diff.move_bottom()
diff.left() / diff.right()
diff.toggle_wrap()
Function
undo.apply() / undo.cancel()
undo.prev() / undo.next()
redo.apply() / redo.cancel()
redo.prev() / redo.next()
Function
choose.apply()
choose.cancel()
choose.move_up() / choose.move_down()
Function
input.apply()
input.cancel()
Function
command_history.move_up() / command_history.move_down()
command_history.close()
command_history.delete_selected()
Function
file_search.move_up() / file_search.move_down()
file_search.page_up() / file_search.page_down()
file_search.preview_half_page_up() / file_search.preview_half_page_down()
file_search.toggle()
file_search.edit()
file_search.apply() / file_search.cancel()
Contribute Community