Lua Scripting in jjui
jjui supports lua scripting to extend functionality and create custom workflows. Scripts can access the Jujutsu command-line, manipulate the UI, query context, and interact with the user.
Quick Start
Section titled “Quick Start”Add a Lua custom command to your config file (~/.config/jjui/config.toml):
[custom_commands.hello]key = ["ctrl+h"]lua = '''flash("Hello from Lua!")'''Press Ctrl+h in jjui to see the message.
Core Concepts
Section titled “Core Concepts”Async Execution
Section titled “Async Execution”Lua scripts run asynchronously using coroutines. Functions that interact with the UI (like jj_async, choose, input) yield control until completion. This allows non-blocking operations.
API Reference
Section titled “API Reference”Context API
Section titled “Context API”Query the current selection and checked items.
| Function | Description | Returns |
|---|---|---|
context.change_id() | Returns the change ID of the currently selected item (works for revisions and files) | string or nil |
context.commit_id() | Returns the commit ID of the currently selected item (works for revisions, files, and commits) | string or nil |
context.file() | Returns the file path if a file is currently selected (details view) | string or nil |
context.operation_id() | Returns the operation ID if viewing the operations log | string or nil |
context.checked_files() | Returns an array of checked file paths | table (array of strings) |
context.checked_change_ids() | Returns an array of checked change IDs | table (array of strings) |
context.checked_commit_ids() | Returns an array of checked commit IDs | table (array of strings) |
Revisions API
Section titled “Revisions API”Manipulate revisions and trigger UI actions.
| Function | Description | Returns | Example |
|---|---|---|---|
revisions.current() | Returns the change ID of the currently selected revision | string or nil | |
revisions.checked() | Returns an array of checked revision change IDs | table (array of strings) | |
revisions.open_details() | Open the details view for the selected revision | ||
revisions.refresh(options) | Refreshes the revision list. Options: keep_selections (bool), selected_revision (string) | revisions.refresh({keep_selections = true}) | |
revisions.navigate(options) | Navigate the revision list. Options: by (int), page (bool), target (string: "parent", "child", "working_copy"), to (string), fallback (string), ensureView (bool), allowStream (bool) | revisions.navigate({by = 5}) or revisions.navigate({target = "parent"}) | |
revisions.start_squash(options) | Initiate squash operation. Options: files (table of strings) | revisions.start_squash({files = {"main.go"}}) | |
revisions.start_rebase(options) | Initiate rebase operation. Options: source (string: "revision", "branch", "descendants"), target (string: "destination", "after", "before", "insert") | revisions.start_rebase({source = "branch", target = "after"}) | |
revisions.start_inline_describe() | Open inline editor to change the description. Yields until editor is closed | bool (true if applied) | local applied = revisions.start_inline_describe() |
Revset API
Section titled “Revset API”Manage the current revset filter.
| Function | Description | Returns | Example |
|---|---|---|---|
revset.current() | Returns the current revset expression | string | |
revset.default() | Returns the default revset expression from config | string | |
revset.set(expression) | Set a new revset expression | revset.set("root()") | |
revset.reset() | Reset to the default revset |
Command Execution
Section titled “Command Execution”Execute Jujutsu commands in different modes.
| Function | Mode | Returns | Use For | Example |
|---|---|---|---|---|
jj(...args) | Synchronous | output (string), error (string or nil) | Quick queries that don’t require UI interaction | local output, err = jj("log", "-r", "@", "-T", "change_id") |
jj_async(...args) | Asynchronous | Nothing (fire-and-forget) | Commands that modify state but don’t need output | jj_async("bookmark", "create", "feature") |
jj_interactive(...args) | Interactive | Nothing | Commands requiring user input or editor (e.g., split, absorb) | jj_interactive("split") |
Arguments: All functions accept varargs or a table of strings: jj("log", "-r", "@") or jj({"log", "-r", "@"})
Details:
jj()blocks script execution until command completes, returns outputjj_async()dispatches command and immediately continues script executionjj_interactive()opens command in terminal for user interaction
User Interface
Section titled “User Interface”| Function | Description | Returns | Example |
|---|---|---|---|
flash(message) | Display a temporary message to the user | flash("Done!") | |
choose(options) or choose({options = ..., title = ...}) | Show selection menu, wait for user choice. Accepts varargs, table, or options object with options (table) and title (string) | string or nil | local choice = choose("Yes", "No") or choose({options = {"a", "b"}, title = "Pick"}) |
input(options) | Show input prompt. Options: title (string), prompt (string) | string or nil | local text = input({title = "Name", prompt = "Enter: "}) |
Utilities
Section titled “Utilities”| Function | Description | Returns | Example |
|---|---|---|---|
copy_to_clipboard(text) | Copy text to system clipboard | bool, error (string or nil) | local ok, err = copy_to_clipboard("text") |
split_lines(text, keep_empty) | Split text into lines. By default, empty lines are removed. Args: text (string), keep_empty (bool, default: false) | table (array of strings) | local lines = split_lines(output) or split_lines(output, true) |
exec_shell(command) | Execute a shell command interactively. Unlike os.execute, this properly returns to jjui after the command exits. | bool | exec_shell("vim " .. file) |
Examples
Section titled “Examples”Copy Change ID or File Path
Section titled “Copy Change ID or File Path”[custom_commands.copy_to_clipboard]key = ["Y"]lua = '''local checked_files = context.checked_files()if checked_files and #checked_files > 0 then local file_names = table.concat(checked_files, " ") copy_to_clipboard(file_names) flash("Copied checked files: " .. file_names) returnendlocal selected_file = context.file()if selected_file then copy_to_clipboard(selected_file) flash("Copied file: " .. selected_file) returnendlocal change_id = context.change_id()if change_id then copy_to_clipboard(change_id) flash("Copied change ID: " .. change_id) returnendflash("No item selected to copy")'''Quick Revset Switcher
Section titled “Quick Revset Switcher”Show menu of common revsets:
[custom_commands.quick_revset]key = ["ctrl+r"]lua = '''local selected = choose({ options = { "mine()", "all()", "trunk()..@", revset.default() }, title = "Quick Revset"})
if selected then revset.set(selected) flash("Revset: " .. selected)end'''Open files with editor
Section titled “Open files with editor”Open a file in editor in details view.
[custom_commands.open_file]key = ["O"]lua = '''local file = context.file()if not file then flash("No file selected") returnend-- exec_shell returns to jjui after the editor exitsexec_shell("vim " .. file)-- or your external editoros.execute("code " .. file)'''New revision with --insert-after
Section titled “New revision with --insert-after”Create new revision after a revision, like jj new --insert-after -r $ChangeID
[custom_commands.new_insert_after]key = ["N"]lua = ''' jj("new", "-A", context.change_id()) revisions.refresh()
local new_change_id = jj("log", "-r", "@", "-T", "change_id.shortest()", "--no-graph") revisions.navigate{to=new_change_id} '''Push with --named
Section titled “Push with --named”[custom_commands.push_with_name]key = ["B"]lua = ''' local branch_name = jjui.input({ title = "Create New Branch", prompt = "Branch name: " })
local rev = revisions.current() if branch_name then jj("git", "push", "--named", branch_name.."="..rev) revisions.refresh() end '''