A WezTerm terminal config
Terminal emulators often become personal tools, reflecting years of accumulated preferences, workflow optimizations and moments of madness. After years of cobbling together my Tmux and Kitty setup (which worked kind of worked fine), I threw it all away about a year ago for WezTerm giving me a simpler config and setup.
I had considered Alacritty (since it has solid performance), but WezTerm's Lua-based configuration offered the programmability I wanted without sacrificing speed - and has great community support. Its robust feature set captured everything I needed in a single tool, eliminating the need to manage separate terminal and multiplexer configurations.
Navigation between WezTerm and Neovim
An important aspect of my setup bridges the gap between terminal panes and Neovim splits. It was the first thing I needed to get right before I could move off Kitty and Tmux. Instead of remembering different key bindings for each context, the same shortcuts work everywhere:
local function isVi(pane)
return pane:get_foreground_process_name():find("n?vim") ~= nil
end
local act = wezterm.action
local function activatePane(window, pane, pane_direction, vim_direction)
if isVi(pane) then
window:perform_action(act.SendKey({ key = vim_direction, mods = "CTRL" }), pane)
else
window:perform_action(act.ActivatePaneDirection(pane_direction), pane)
end
end
Thank you Navigator.nvim WezTerm Integration which guided me in the creation of wezterm.nvim. Similar approaches are explored in WezTerm and Neovim keybindings in macOS.
This integration means Ctrl+h/j/k/l
moves between panes regardless of whether
you're in the terminal or inside a Neovim session. The terminal detects when
Neovim is running and sends the appropriate navigation command. It's the kind of
seamless experience that reduces cognitive overhead when switching between
different contexts.
For Neovim configuration management, I currently use LazyVim which provides a well-structured foundation with sensible defaults and easy customization.
Tabs bootstrap
Rather than manually opening tabs and navigating to project directories each morning, the configuration handles workspace initialization:
wezterm.on("gui-startup", function(_)
local _, _, window = wezterm.mux.spawn_window({})
window:gui_window():maximize()
local _, second_pane, _ = window:spawn_tab({ cwd = wezterm.home_dir .. "/.dotfiles" })
local _, third_pane, _ = window:spawn_tab({ cwd = wezterm.home_dir .. "/projects/things" })
window:spawn_tab({ cwd = wezterm.home_dir .. "/projects" })
second_pane:send_text("vi\n")
third_pane:send_text("vi\n")
end)
Each startup creates a predefined set of tabs in specific directories with Neovim already launched. The window maximizes automatically, and the first tab becomes active. This setup eliminates the repetitive navigation that starts most coding sessions—a small automation that compounds over time.
Tab titles
Tab titles adapt based on the current working directory, with some custom logic for frequently used directories:
local function tab_title(tab)
-- Personally for each tab, I have nvim in the directory of interest in the
-- first pane. Other panes I might navigate around. I want the tab label to
-- based on the directory of this first pane, since this is my context.
local pane = tab.panes[1]
local current_working_dir = pane.current_working_dir
if current_working_dir == nil then
return "special"
end
if current_working_dir.file_path == wezterm.home_dir then
return "~"
end
local relative_working_dir = get_relative_working_dir(pane)
if relative_working_dir == ".dotfiles" then
return "."
elseif relative_working_dir == "things" then
return ""
end
return relative_working_dir
end
This keeps tab labels concise while remaining informative. The dotfiles directory shows as a simple dot, my main project gets a file character, and everything else displays the directory name. It's a detail that makes better of screen real estate for something that is always there.
Hyperlink detection
The configuration includes custom hyperlink patterns for development workflows:
config.hyperlink_rules = {
-- Matches: a URL in parens: (URL)
{
regex = "\\((https?://\\S+)\\)",
format = "$1",
highlight = 1,
},
-- Matches: a URL in brackets: [URL]
{
regex = "\\[(https?://\\S+)\\]",
format = "$1",
highlight = 1,
},
-- Matches: a URL in curly braces: {URL}
{
regex = "\\{(https?://\\S+)\\}",
format = "$1",
highlight = 1,
},
-- Matches: a URL in angle brackets: <URL>
{
regex = "<(https?://\\S+)>",
format = "$1",
highlight = 1,
},
-- Then handle URLs not wrapped in brackets
{
regex = "\\bhttps?://\\S+[)/a-zA-Z0-9-]+",
format = "$0",
},
}
These patterns recognize URLs in various bracket formats commonly found in markdown and documentation, making them clickable without manual selection. A practical improvement for anyone working regularly with documentation or issue trackers.
Key binding
The key bindings prioritize consistency with system conventions and muscle memory:
config.keys = {
{
key = "-",
mods = "CMD",
action = act.SplitVertical,
},
{
key = "\\",
mods = "CMD",
action = act.SplitHorizontal,
},
{
key = "LeftArrow",
mods = "OPT",
action = act.ActivateTabRelative(-1),
},
{
key = "RightArrow",
mods = "OPT",
action = act.ActivateTabRelative(1),
},
{
key = "RightArrow",
mods = "CTRL",
action = act.EmitEvent("ActivatePaneDirection-right"),
},
{
key = "LeftArrow",
mods = "CTRL",
action = act.EmitEvent("ActivatePaneDirection-left"),
},
{
key = "UpArrow",
mods = "CTRL",
action = act.EmitEvent("ActivatePaneDirection-up"),
},
{
key = "DownArrow",
mods = "CTRL",
action = act.EmitEvent("ActivatePaneDirection-down"),
},
}
CMD key feel natural on macOS, while the directional navigation maintains consistency with Vim-style movement patterns. The bindings avoid conflicts with shortcuts from some other application I use.
In practice
This configuration removes daily friction. Starting work means opening WezTerm and finding the tabs already set up. Navigation between panes happens without thinking about which shortcuts to use.
The setup represents several years of gradual refinement—adding features that proved genuinely useful while removing clever additions that ultimately created unnecessary complexity than value. I've ended up with less than I had a few years ago.
Terminal configuration can help reduce repetitive tasks while keeping things simple. This was as much about learning WezTerm's capabilities as solving specific productivity challenges, but the result has been useful.
See dotfiles for the full configuration in context.