# Lua API
tpane loads top-level `*.lua` files under:
```text
~/.config/tmux/tpane
```
Set `TPANE_CONFIG_DIR` to use another directory.
Files in subdirectories are not auto-loaded. Use Lua's `require`:
```lua
local colors = require("theme.colors") -- ~/.config/tmux/tpane/theme/colors.lua
```
## Replacing tmux.conf
### Options
Use `tpane.opt` for normal tmux options:
```lua
tpane.opt.mouse = true -- set -g mouse on
tpane.opt.history_limit = 5000 -- set -g history-limit 5000
tpane.opt.mode_keys = "vi" -- set -g mode-keys vi
tpane.opt.renumber_windows = true -- set -g renumber-windows on
tpane.opt.escape_time = 0 -- set -g escape-time 0
```
### Appending to tmux options
Some tmux options are usually extended instead of replaced. In `tmux.conf` that
looks like this:
```tmux
set -ga update-environment TERM
set -ga update-environment TERM_PROGRAM
```
In Lua, use `tpane.append`:
```lua
tpane.append("update_environment", "TERM")
tpane.append("update_environment", "TERM_PROGRAM")
```
### Key bindings
Use `tpane.bind(key, action[, opts])`:
```lua
tpane.bind("h", tpane.pane.select("left"))
tpane.bind("j", tpane.pane.select("down"))
tpane.bind("k", tpane.pane.select("up"))
tpane.bind("l", tpane.pane.select("right"))
```
By default, bindings uses tmux's prefix. Use `prefix = false` for keybindings that are not using tmux prefix:
```lua
tpane.bind("M-Left", tpane.pane.resize("left", 10), { prefix = false })
```
Use `mode = "copy"` for copy mode:
```lua
tpane.bind("v", tpane.copy.begin(), { mode = "copy" })
tpane.bind("r", tpane.copy.rectangle(), { mode = "copy" })
tpane.bind("y", tpane.copy.copy(), { mode = "copy" })
```
### Lua key handlers
A binding can run Lua. The handler receives the pane that invoked the binding:
```lua
tpane.bind("L", function(pane)
tpane.toggle(pane, "logs")
end)
```
### Raw tmux commands
If tpane does not have a helper for something, you can write the tmux command directly:
```lua
tpane.bind("R", "source-file ~/.config/tmux/tmux.conf ; display 'reloaded'")
```
For multiple raw commands, use `tpane.raw` with a list:
```lua
tpane.bind("C-S-l", tpane.raw({
"swap-window -t +1",
"select-window -t +1",
}), { prefix = false })
```
### Unbinding
```lua
tpane.unbind("C-b")
tpane.unbind("v", { mode = "copy" })
```
## Actions
Actions are values you pass to `tpane.bind`.
### `tpane.raw`
Run raw tmux commands:
```lua
tpane.raw("select-pane -L")
tpane.raw({ "swap-window -t +1", "select-window -t +1" })
```
### Pane actions
```lua
tpane.pane.select("left")
tpane.pane.select("right")
tpane.pane.select("up")
tpane.pane.select("down")
tpane.pane.resize("left", 10)
tpane.pane.resize("right", 10)
tpane.pane.resize("up", 5)
tpane.pane.resize("down", 5)
tpane.pane.split("right", { cwd = "pane" })
tpane.pane.split("down", { cwd = "pane" })
tpane.pane.split("left")
tpane.pane.split("up")
```
`cwd = "pane"` means use the current pane's directory.
### Window actions
```lua
tpane.window.new({ cwd = "pane" })
tpane.window.swap("next")
tpane.window.swap("prev")
```
### Copy-mode actions
```lua
tpane.copy.begin()
tpane.copy.begin({ rectangle = true })
tpane.copy.rectangle()
tpane.copy.copy()
```
### Key actions
```lua
tpane.key.prefix()
```
## Status bar and tabs
### Widgets
A widget is a Lua function that returns text, a styled table, a list of parts, or
`nil` to hide itself.
```lua
local host = tpane.widget(function()
return os.getenv("HOSTNAME") or ""
end)
local mode = tpane.widget(function(ctx)
if ctx.pane and ctx.pane.zoomed then
return { text = "zoom", fg = "yellow", bold = true }
end
end)
```
Widget context:
```lua
ctx.session -- current session name
ctx.window -- current window id, like @2
ctx.pane -- current pane object, or nil
ctx.panes -- all pane objects
```
Built-in widgets live under `tpane.widgets`.
Plain widgets are handles:
```lua
tpane.widgets.session
tpane.widgets.host
tpane.widgets.clock
tpane.widgets.date
tpane.widgets.prefix
```
Job-backed widgets are factories. Call them once, then put the returned handle in
your statusline:
```lua
local cpu = tpane.widgets.cpu({ every = "2s" })
local memory = tpane.widgets.memory({ every = "5s" })
local battery = tpane.widgets.battery({ every = "30s" })
local player = tpane.widgets.player({ every = "5s" })
tpane.statusline {
right = { player, cpu, memory, battery, tpane.widgets.clock },
}
```
Raw tmux format strings also work:
```lua
local prefix = tpane.widget(function()
return tpane.fmt.prefix("PREFIX", "")
end)
```
### Statusline
```lua
tpane.statusline {
position = "top",
interval = 1,
left = { tpane.widgets.session },
right = { host, mode, tpane.widgets.clock },
separator = " ",
}
```
For a multiline status bar, use `rows`:
```lua
tpane.statusline {
position = "top",
rows = {
{ left = { tpane.widgets.session }, right = { tpane.widgets.clock } },
{ left = { tpane.widgets.tabs }, right = { tpane.widgets.prefix } },
},
}
```
### Jobs
Use jobs for widget data that comes from shell commands. Jobs run in the
background on their own interval and return a handle that widgets can render.
Status rendering does not block on the command.
```lua
local uptime = tpane.job({
every = "1m",
timeout = "5s",
cmd = "uptime",
})
tpane.statusline {
right = { uptime },
}
```
`every` and `timeout` can be seconds or a string ending in `s`, `m`, or `h`.
`timeout` defaults to `10s`.
```lua
tpane.job({ every = 30, timeout = "5s", cmd = "acpi -b" })
tpane.job({ every = "5s", timeout = "2s", cmd = "playerctl metadata title" })
```
### Styled parts
```lua
return { text = "ok", fg = "green", bold = true }
```
Supported style keys:
```text
fg, bg, bold, dim, italics, blink, reverse, hidden, strikethrough, underscore,
align, fill
```
### Tabline
`tpane.tabline` writes the common `window-status-format` options for you:
```lua
tpane.tabline {
label = "cwd", -- cwd, name, or a raw tmux format string
inactive = { fg = "#777777" },
current = { fg = "#8caaee", bold = true },
}
```
## Plugins
Plugins are referenced from Lua with `tpane.use`. See [plugins.md](plugins.md) for plugin details.
Built-in plugins:
```lua
tpane.use("sensible")
tpane.use("vim-navigator")
tpane.use("yank")
tpane.use("themes")
```
`sensible` applies a small set of common tmux defaults:
```lua
tpane.use("sensible")
```
It sets options like lower escape delay, larger history, and focus events.
Themes:
```lua
tpane.use("themes")
tpane.theme("Catppuccin Mocha")
tpane.theme("Gruvbox Dark", { transparent = true })
tpane.theme("Gruvbox Dark", { status_bg = "default" })
```
List bundled themes from the shell:
```sh
tpane themes
```
`themes` bundles the iTerm2 Color Schemes collection and applies the selected
palette to the tmux statusline, tabline, pane borders, and tpane state colors.
`transparent = true` keeps the terminal background behind the status bar.
Git plugins:
```lua
tpane.use("theme", {
repo = "https://github.com/example/tpane-theme.git",
branch = "main",
})
```
Monorepo plugin path:
```lua
tpane.use("tool", {
repo = "https://github.com/example/tools.git",
path = "plugins/tpane-tool",
})
```
`repo` is the git URL. `url` also works. `branch`, `tag`, and `rev` are mutually
exclusive. `path` is relative to the repo and uses sparse checkout.
Plugin commands:
```sh
tpane plugin status # show referenced, installed, dirty, and update state
tpane plugin sync # install/update plugins referenced by Lua config
tpane plugin update # update all installed plugins
tpane plugin update NAME # update one plugin
tpane plugin clean # remove installed plugins not referenced by Lua config
tpane plugin list # list installed git plugins
tpane plugin remove NAME # remove one installed plugin
```
## Reusable panes
Register panes you want to show/hide later:
```lua
tpane.register_pane("logs", {
side = "bottom",
size = "25%",
command = "tail -f logs/app.log",
})
```
Use key handlers to control it:
```lua
tpane.bind("L", function(pane)
tpane.toggle(pane, "logs")
end)
tpane.bind("M-L", function(pane)
tpane.expand(pane, "logs")
end, { prefix = false })
```
`toggle` shows or hides it. Hidden panes are stashed, so the process keeps
running. `expand` shows it and zooms the layout around it.
Options:
```lua
tag = "logs" -- defaults to registered name
name = "logs" -- stash name, defaults to registered name
full = true -- split across the full window
anchor = { tag = "editor" } -- optional target pane for split/unstash
command = "tail -f app.log"
title = "logs"
label = "logs"
blocked_message = "..." -- shown instead of hiding a blocked pane
```
Use `tpane.split` when you want a one-off split instead of a registered pane:
```lua
local pane = tpane.split(current, {
side = "bottom",
size = "25%",
command = "zsh",
})
```
## Pane objects
Kind callbacks, key handlers, events, widgets, and `tpane.panes()` use pane
objects.
Fields:
```lua
pane.id -- tmux pane id, like %3
pane.pid -- root process pid
pane.cwd -- current directory
pane.cwd_basename -- last path component of cwd
pane.command -- tmux pane_current_command
pane.session -- session name
pane.window -- window id, like @2
pane.active -- true if focused
pane.zoomed -- true if window is zoomed
pane.kind -- detected kind
pane.label -- shown label
pane.tag -- user tag set by tpane
pane.home -- home window for stashed panes
pane.state -- current state, if any
```
Methods:
```lua
pane:running("psql")
pane:var("@tmux_var")
pane:set { tag = "logs", label = "logs" }
pane:capture()
pane:proc_tree()
```
Process tree example:
```lua
pane:proc_tree():any(function(proc)
return proc.argv:match("--watch") ~= nil
end)
```
Find panes:
```lua
local logs = tpane.find { tag = "logs" }
local all_logs = tpane.find_all { tag = "logs" }
```
All fields in the query must match.
## Kinds and states
A kind tells tpane what a pane is.
```lua
tpane.kind { name = "database", match = "psql" }
```
When a pane is running `psql`:
```lua
pane.kind -- database
pane.label -- database
```
Use `detect` for custom matching:
```lua
tpane.kind {
name = "server",
detect = function(pane)
return pane:running("node") and pane.cwd:match("/server$") ~= nil
end,
}
```
Use `label` to change what is shown:
```lua
tpane.kind {
name = "editor",
match = "nvim",
label = function(pane)
return "editor " .. pane.cwd_basename
end,
}
```
Kinds can report state:
```lua
tpane.kind {
name = "worker",
match = "worker",
state = function(pane)
if pane:capture():match("blocked") then return "blocked" end
if pane:capture():match("running") then return "working" end
return "idle"
end,
}
```
Built-in state presentations:
```text
approval
blocked
working
done_unseen
idle_seen
```
Declare custom state presentation:
```lua
tpane.state("waiting", { color = "yellow", glyph = "…" })
local presentation = tpane.state("waiting")
```
Returning `done` from detection is treated as `done_unseen` until the pane is
focused.
## Store
`tpane.store` is a small JSON-backed store for config and plugins.
```lua
tpane.store.set("counter", 1)
local value = tpane.store.get("counter")
tpane.store.delete("counter")
```
Values may be strings, numbers, booleans, tables, or nil.
## Events
```lua
tpane.on("tick", function() end)
tpane.on("pane:new", function(pane) end)
tpane.on("pane:focus", function(pane) end)
tpane.on("window:close", function(window_id) end)
tpane.on("state:change", function(pane_id) end)
```
## Panels
Panels are simple TUI views shown by `tpane control`.
```lua
tpane.panel {
id = "tools",
title = "Tools",
cards = function()
return {
{ title = "logs", tag = "pane", pane = "%1" },
}
end,
}
```
## Workspaces
A workspace is a named tmux layout. You can call `tpane.apply_workspace(name)` from a command or key binding to activate the workspace:
```lua
tpane.workspace {
name = "dev",
windows = {
{ name = "app", command = "zsh" },
{
name = "logs",
panes = {
{ side = "bottom", size = "30%", command = "tail -f app.log" },
},
},
},
}
tpane.bind("D", function()
tpane.apply_workspace("dev")
end)
```
## Format helpers
Use `tpane.fmt` for tmux conditionals that do not have Lua equivalents:
```lua
tpane.fmt.prefix("", "")
tpane.fmt.when("window_zoomed_flag", "Z", "")
```
## Low-level tmux helpers
Use these when the higher-level helpers are not enough:
```lua
local window = tpane.tmux.new_window { name = "logs", cwd = pane.cwd, command = "zsh" }
tpane.tmux.select_window(window)
tpane.tmux.send_keys { target = pane.id, keys = "npm test", enter = true }
tpane.tmux.split { target = pane.id, dir = "below", size = "25%", cwd = pane.cwd }
tpane.tmux.stash { pane = pane.id, window = pane.window, cwd = pane.cwd, name = "hidden" }
tpane.tmux.unstash { pane = hidden.id, target = pane.id, horizontal = true, size = "35%" }
tpane.tmux.unzoom(pane.window)
tpane.tmux.select(pane.id)
tpane.tmux.zoom(pane.id)
tpane.tmux.display { target = pane.id, message = "message" }
```
## Public API
| `tpane.use(name_or_spec)` | Load a built-in or git plugin. |
| `tpane.opt` | Set tmux options with assignment, like `tpane.opt.mouse = true`. |
| `tpane.append(name, value)` | Append to tmux options such as `update-environment`. |
| `tpane.options(table)` | Set nested tmux options. |
| `tpane.bind(key, action, opts)` | Bind a key. |
| `tpane.unbind(key, opts)` | Remove a key binding. |
| `tpane.raw(command)` | Build an action from raw tmux command text. |
| `tpane.pane.*` | Pane actions, lookup, and pane handles. |
| `tpane.window.*` | Window actions. |
| `tpane.copy.*` | Copy-mode actions. |
| `tpane.key.*` | Key helpers such as `tpane.key.prefix()`. |
| `tpane.widget(fn)` | Create a widget handle. |
| `tpane.widgets.*` | Built-in widget handles and factories. |
| `tpane.job(opts)` | Run shell-backed widget data in the background. |
| `tpane.statusline(opts)` | Configure the tmux statusline. |
| `tpane.theme(name_or_palette[, opts])` | Apply a theme from the `themes` plugin. |
| `tpane.tabline(opts)` | Configure tmux window tabs. |
| `tpane.panel(opts)` | Register a panel. |
| `tpane.register_pane(name, opts)` | Register a reusable pane definition. |
| `tpane.split(target, opts)` | Split/open a reusable pane. |
| `tpane.toggle(target, opts)` | Toggle a reusable pane. |
| `tpane.show(target, opts)` | Show a reusable pane. |
| `tpane.hide(target, opts)` | Hide a reusable pane. |
| `tpane.expand(target, opts)` | Zoom or expand a pane. |
| `tpane.workspace(def)` | Register a named layout. |
| `tpane.apply_workspace(name)` | Apply a registered layout once. |
| `tpane.panes()` | Return current pane objects. |
| `tpane.find(query)` | Find one pane by fields. |
| `tpane.find_all(query)` | Find all panes by fields. |
| `tpane.kind(name, opts)` | Register pane detection. |
| `tpane.state(name, opts)` | Register state presentation. |
| `tpane.on(event, fn)` | Register an event handler. |
| `tpane.store` | Persistent Lua key-value store. |
| `tpane.tmux` | Low-level tmux helpers. |
| `tpane.fmt` | tmux format helpers. |