Previous: , Up: API  


1.23 (heks-linux wezterm)

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.

1.23.1 Parameters

Parameter: wezterm-config-dir

Default value:

"/root/.config/wezterm"
Parameter: wezterm-config-filename

Default value:

"wezterm.lua"
Parameter: wezterm-font-fallbacks

Default value:

("Noto Color Emoji")
Parameter: wezterm-font-size

Default value:

12
Parameter: wezterm-prefix-timeout

Default value:

4000
Parameter: wezterm-scrollback-lines

Default value:

100000
Parameter: wezterm-window-opacity

Default value:

0.88
Parameter: wezterm-window-padding

Default value:

16
Parameter: wezterm-yield-apps

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")

1.23.2 Procedures

Procedure: install-wezterm-config

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.

Procedure: serialize-wezterm-config KEY: #:config

Join a configuration line list, such as (wezterm-config), into one string with newlines between the lines.

(serialize-wezterm-config #:config (wezterm-config))
Procedure: serialize-wezterm-key KEY: #:key #:mods #:action

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' }
Procedure: serialize-wezterm-name name

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']
Procedure: serialize-wezterm-prefix-entry KEY: #:key #:action

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 }
Procedure: serialize-wezterm-value value KEY: #:level #:compact?

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
Procedure: translate-wezterm-mods mods

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.

Procedure: wezterm-colors

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.

Procedure: wezterm-compact value

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 }
Procedure: wezterm-compact? value

Return #t when value was marked with wezterm-compact.

Procedure: wezterm-config

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.

Procedure: wezterm-emacs-key-lines

Return the rendered lines of the emacs_keys array.

Procedure: wezterm-emacs-keys

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.

Procedure: wezterm-font

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.

Procedure: wezterm-prefix-table-lines

Return the rendered lines of the emacs_prefix_tables table.

Procedure: wezterm-prefix-tables

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.

Procedure: wezterm-raw-lua text

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
Procedure: wezterm-raw-lua? value

Return #t when value is a literal Lua fragment.

(wezterm-raw-lua? (wezterm-raw-lua "act.PasteFrom 'Clipboard'"))
⇒ #t
Procedure: wezterm-settings

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
Procedure: wezterm-window-decorations

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.

Procedure: wezterm-window-padding-table

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.

Procedure: wezterm-yield-app-lines

Return the rendered lines of the yield_apps set.

1.23.3 Variables

Variable: wezterm-lua-runtime
"-- 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