Previous: (heks-linux waybar), Up: API
Renders ~/.config/wezterm/wezterm.lua.
WezTerm is configured in Lua, so this module generates a Lua file: the
settings, the application list and the key bindings are Scheme data
rendered by serialize-wezterm-value and
serialize-wezterm-key, wrapped by a fixed block of Lua
(wezterm-lua-runtime) that turns them into a configuration.
Keeping the mechanism in one visible place is the point of the split:
the parts a user changes are data, and the part that implements the
policy never has to be re-read.
The key bindings are the Emacs ones, and that is where WezTerm differs
from the other two terminals in this project. Alacritty and kitty can
only choose *not* to claim a chord, because neither can make a binding
conditional on what is running: once kitty maps C-y, the pager and
Emacs never see it again. WezTerm hands every action a callback, and
the callback can ask the pane which process is in front of it, so every
chord below is *guarded*: when the foreground process is one of
wezterm-yield-apps - Emacs above all, but also less and
every full-screen program - the chord is re-sent to that application
untouched and the terminal stays out of the way. C-x is the
clearest example: at a shell prompt it is the prefix of the window
commands, and inside Emacs it is Emacs’s own C-x.
Two rules are inherited from the rest of the terminal configuration and are checked by the unit tests:
- C-v and M-v are never claimed. The shared lesskey file
gives them to the pager (forw-screen and back-screen); the guard above
would keep them working inside Emacs too, but a chord the terminal never
binds cannot be lost by accident, so the scrollback lives on
S-PageUp and S-PageDown, exactly as in Alacritty. - Blur is
left to the compositor. Niri already blurs the WezTerm window through
niri-blur-rules, so only the opacity is set here.
The prefix table maps Emacs’s window commands onto panes and its buffer commands onto tabs: C-x 2 splits below, C-x 3 splits right, C-x 0 closes, C-x 1 zooms, C-x o moves to the next pane, C-x b picks a pane by name, and C-x <left> / C-x <right> walk the tabs the way they walk buffers in Emacs.
Default value:
"/root/.config/wezterm"
Default value:
"wezterm.lua"
Default value:
("Noto Color Emoji")
Default value:
12
Default value:
4000
Default value:
100000
Default value:
0.88
Default value:
16
Default value:
("alsamixer" "atop" "bat" "btop" "byobu" "cava" "cmus" "emacs" "emacsclient" "fzf" "ghci" "gitui" "guile" "helix" "htop" "hx" "info" "ipython" "irb" "irssi" "joe" "jshell" "kak" "lazygit" "less" "lf" "lua" "man" "mc" "mcedit" "micro" "mongosh" "more" "most" "mosh" "mutt" "nano" "ncspot" "neomutt" "newsboat" "nnn" "nvim" "nvtop" "pg" "pico" "psql" "python" "python3" "ranger" "redis-cli" "rmpc" "sbcl" "screen" "sqlite3" "ssh" "telnet" "tig" "tmux" "top" "tuir" "vi" "view" "vim" "weechat" "yazi")
Write the WezTerm configuration to wezterm-config-dir.
Local overrides are read from
$XDG_CONFIG_HOME/heks-linux/wezterm-settings.scm first, so a
machine can change the font, the opacity or the list of applications
that get the keyboard without editing this module. WezTerm reloads its
configuration when the file changes, so no restart is needed.
Join a configuration line list, such as (wezterm-config), into
one string with newlines between the lines.
(serialize-wezterm-config #:config (wezterm-config))
Render one guarded key assignment as an inline Lua table.
mods uses the short syntax translated by
translate-wezterm-mods, key is a WezTerm key name and
action is the Lua expression the chord performs when no
application owns the keyboard.
(serialize-wezterm-key #:key "y" #:mods "C" #:action "act.PasteFrom 'Clipboard'")
⇒ { key = 'y', mods = 'CTRL', action = act.PasteFrom 'Clipboard' }
Render name as a Lua table field name.
Names that are not a valid Lua identifier - emacs-cx, for
instance - are bracketed and quoted, the way Lua requires.
(serialize-wezterm-name "emacs-cx") ⇒ ['emacs-cx']
Render one entry of a prefix key table as an inline Lua table.
Unlike serialize-wezterm-key, an entry carries no modifier of its
own: the runtime expands it into the two spellings a prefix needs - with
and without CTRL still held down - so that both C-x 2 and
C-x then 2 work.
(serialize-wezterm-prefix-entry #:key "2" #:action "act.SplitVertical")
⇒ { key = '2', action = act.SplitVertical }
Render value as a Lua expression.
Strings are single-quoted - Lua’s spelling, and it keeps the generated
file free of escapes - numbers and booleans pass through, a list of
pairs becomes a Lua object and any other list becomes a Lua array.
Tables are laid out one entry per line unless the value was wrapped in
wezterm-compact, and literal Lua fragments from
wezterm-raw-lua are emitted verbatim.
(serialize-wezterm-value #t) ⇒ true
Translate the project’s short modifier syntax into WezTerm’s spelling.
WezTerm separates modifiers with a vertical bar and spells them out in capitals. Unknown parts are passed through unchanged, so a literal key name survives.
For example, C-S becomes CTRL|SHIFT.
Return the Lua colors table as an association list.
The foreground and background come from the palette’s text and
background values, the cursor borrows the palette’s
primary so it matches the accent used by the rest of the desktop,
and selections are drawn in the palette’s background-l - the same
keys Alacritty’s [colors.primary] section uses.
Return value marked to be rendered as a single-line Lua table, for values short enough that spreading them over several lines would only add noise.
(serialize-wezterm-value (wezterm-compact '((left . 16) (top . 16))))
⇒ { left = 16, top = 16 }
Return #t when value was marked with
wezterm-compact.
Return the complete WezTerm configuration as a list of text lines.
The file is assembled in four parts, in the order WezTerm needs them:
the generated application set, the generated settings, the generated key
bindings, and finally wezterm-lua-runtime, which consumes all
three. Passing the result to string-join with a newline produces
the file that is written to disk; serialize-wezterm-config does
exactly that.
Return the rendered lines of the emacs_keys array.
Return the rendered guarded key assignments, one inline Lua table each.
Every chord here is one an Emacs user reaches for, so every one of them
is guarded: the runtime hands it back to the application whenever
wezterm-yield-apps owns the keyboard. The set is deliberately
small, and two chords are missing on purpose:
- C-v and M-v are not bound at all. They belong to the
pager (forw-screen and back-screen in the shared lesskey
file) and to Emacs’s own scrolling, so the scrollback pages with
S-PageUp and S-PageDown the way Alacritty does. - C-c
is not bound either. It is Emacs’s leader key, but in a terminal it is
also the interrupt signal, and no terminal may take it.
Creating and renaming tabs is left to WezTerm’s own defaults in the Ctrl+Shift namespace, which neither Emacs nor a pager wants.
Return the Lua expression for the font setting.
The family comes from heks-linux-mono-font so the terminal stays
in step with Emacs and the rest of the environment, and
wezterm-font-fallbacks supplies the families WezTerm falls back
to for glyphs the main font lacks.
Return the rendered lines of the emacs_prefix_tables table.
Return the prefix key tables, as a list of (name . entries)
pairs.
emacs-cx is Emacs’s C-x prefix, mapped onto panes and tabs:
the window commands keep their Emacs keys (2 splits below, 3
splits right, 0 closes, 1 zooms, o is the next pane),
b picks a pane the way C-x b picks a buffer, and the arrow
keys walk the tabs the way they walk buffers. g quits, as
C-g does in Emacs.
The table is only reachable through a guarded C-x, so Emacs and the pager never lose their prefix.
Return text wrapped so that serialize-wezterm-value emits
it as literal Lua instead of quoting it.
(serialize-wezterm-value (wezterm-raw-lua "act.TogglePaneZoomState")) ⇒ act.TogglePaneZoomState
Return #t when value is a literal Lua fragment.
(wezterm-raw-lua? (wezterm-raw-lua "act.PasteFrom 'Clipboard'")) ⇒ #t
Return the settings WezTerm receives, as an association list.
The renderer turns this into the settings table of the generated
file, which is then copied onto the configuration one field at a time.
Every value here is either a parameter or derived from the palette and
the font settings, so changing a setting is enough to change the file.
(assoc-ref (wezterm-settings) "scrollback_lines") ⇒ 100000
Return WezTerm’s window_decorations value.
heks-linux-client-side-decorations? decides, exactly as it does
for Alacritty: with decorations on, WezTerm draws a title bar and a
resize border; with them off, it keeps the resize border only, so the
window can still be resized but nothing is drawn.
Return the window_padding table, one entry per side.
WezTerm wants the four sides spelled out; wezterm-window-padding
holds the single number they all take.
Return the rendered lines of the yield_apps set.
"-- Strip the directory part of a path, so that '/usr/bin/less' and 'less'\n-- compare equal.\nlocal function basename(path)\n return (path:gsub('^.*/', ''))\nend\n\n-- True when the process in front of this pane is one of the applications that\n-- must receive the keys itself. When the name cannot be determined the\n-- terminal keeps its own bindings rather than swallowing a chord.\nlocal function application_owns_keyboard(pane)\n if not pane then\n return false\n end\n local ok, name = pcall(function()\n return pane:get_foreground_process_name()\n end)\n if not ok or type(name) ~= 'string' or name == '' then\n return false\n end\n return yield_apps[basename(name):lower()] == true\nend\n\n-- Re-send a chord to the pane, encoded the way the application expects it.\nlocal function send_key(spec)\n local assignment = @{ key = spec.key @}\n if spec.mods and spec.mods ~= 'NONE' then\n assignment.mods = spec.mods\n end\n return act.SendKey(assignment)\nend\n\n-- Wrap one key assignment so that the application wins when it owns the\n-- keyboard.\nlocal function guarded(spec)\n return @{\n key = spec.key,\n mods = spec.mods,\n action = wezterm.action_callback(function(window, pane)\n if application_owns_keyboard(pane) then\n window:perform_action(send_key(spec), pane)\n else\n window:perform_action(spec.action, pane)\n end\n end),\n @}\nend\n\n-- A prefix table has to match the chord whether the modifier is still held\n-- down (C-x 2, as in Emacs) or released in between (C-x, then 2).\nlocal function prefix_table(entries)\n local assignments = @{@}\n for _, entry in ipairs(entries) do\n for _, mods in ipairs @{ 'NONE', 'CTRL' @} do\n assignments[#assignments + 1] = @{\n key = entry.key,\n mods = mods,\n action = entry.action,\n @}\n end\n end\n return assignments\nend\n\n-- ===== wiring ==========================================================\n\nfor name, value in pairs(settings) do\n config[name] = value\nend\n\n-- Build the lists aside and assign them whole: reading an unset field of the\n-- config builder yields a fresh default table each time, so inserting straight\n-- into config.keys would be lost.\nlocal keys = @{@}\nfor _, spec in ipairs(emacs_keys) do\n keys[#keys + 1] = guarded(spec)\nend\nconfig.keys = keys\n\nlocal key_tables = @{@}\nfor name, entries in pairs(emacs_prefix_tables) do\n key_tables[name] = prefix_table(entries)\nend\nconfig.key_tables = key_tables"
Previous: (heks-linux waybar), Up: API