Session management in Wezterm (without tmux)
I've been using Neovim as my daily IDE/editor for a bit more than a year now. I've also been using tmux to manage sessions, first using t-smart-tmux-session-manager and later sesh, which supersedes the former project. What's great about this is that it doesn't matter which terminal emulator you use, you can still fall back to this kind of session management.
But the draw screen latency (and complexity) of tmux is annoying and I finally took some time to evaluate the session management capabilities of Wezterm (my currently favorite terminal emulator, which you configure with Lua!) called "workspaces", to see if this could replace tmux altogether for me.
Replacing tmux with Wezterm's session management
Well, spoiler alert; it works really well and with the help of smart_workspace_switcher.wezterm, I can get pretty much the same behavior as I am used to with sesh. They both leverage zoxide for fuzzy-searching recently visited folders.
Workspaces setup and management
-- print the workspace name at the upper right
wezterm.on("update-right-status", function(window, pane)
window:set_right_status(window:active_workspace())
end)
-- load plugin
local workspace_switcher = wezterm.plugin.require("https://github.com/MLFlexer/smart_workspace_switcher.wezterm")
-- set path to zoxide
workspace_switcher.zoxide_path = "/opt/homebrew/bin/zoxide"
-- keymaps
table.insert(keys, { key = "s", mods = "CTRL|SHIFT", action = workspace_switcher.switch_workspace() })
table.insert(keys, { key = "t", mods = "CTRL|SHIFT", action = act.ShowLauncherArgs({ flags = "FUZZY|WORKSPACES" }) })
table.insert(keys, { key = "[", mods = "CTRL|SHIFT", action = act.SwitchWorkspaceRelative(1) })
table.insert(keys, { key = "]", mods = "CTRL|SHIFT", action = act.SwitchWorkspaceRelative(-1) })
Full wezterm.lua
source here.
I use Ctrl+Shift+s
to bring up the workspace manager. Then I start typing out
the path I want to open a new session in. Then I can hit that same command again
to jump between workspaces, or I can use Ctrl+Shift+[
or Ctrl+Shift+]
to
jump between them more quickly. If I want to view only the currently opened
workspaces, I can use Ctrl+Shift+t
.
The only thing I'm a little wary about is how wezterm plugins are just read on
the fly from the Internet like this. I might vendor the
smart_workspace_switcher.wezterm
project into my own dotfiles in the long
term.
I've also set up a custom workspace which is loaded on Wezterm startup, which
goes into my dotfiles repository and starts up Neovim. Using a hotkey
Ctrl+Shift+d
I can also always jump directly to this workspace.
Custom workspace on startup
-- set up workspace to be loaded on startup of wezterm
wezterm.on("gui-startup", function(cmd)
local dotfiles_path = wezterm.home_dir .. "/.dotfiles"
local tab, build_pane, window = mux.spawn_window({
workspace = "dotfiles",
cwd = dotfiles_path,
args = args,
})
build_pane:send_text("nvim\n")
mux.set_active_workspace("dotfiles")
end)
-- set up keymap for quickly jumping to this workspace
table.insert(keys, { key = "d", mods = "CTRL|SHIFT", action = act.SwitchToWorkspace({ name = "dotfiles" }) })
Full wezterm.lua
source here.
Be sure to visit the Workspaces/sessions docs for further reading on the different commands at your disposal and recipes.
Bonus: tabs
I primarily use workspaces with Wezterm now, but it's also convenient to have
tabs around (can be used without workspaces). I have a custom setup which
entails adding the following config to wezterm.lua
, which is heavily inspired
by
aaronlifton's wezterm.lua config.
So, when hitting Cmd+T
, a new tab shows and is prefixed by a number. Tabs can
then be selected by hitting Cmd+[number]
.
Custom tabs
config.hide_tab_bar_if_only_one_tab = false
config.use_fancy_tab_bar = false
local function get_current_working_dir(tab)
local current_dir = tab.active_pane and tab.active_pane.current_working_dir or { file_path = "" }
local HOME_DIR = string.format("file://%s", os.getenv("HOME"))
return current_dir == HOME_DIR and "." or string.gsub(current_dir.file_path, "(.*[/\\])(.*)", "%2")
end
wezterm.on("format-tab-title", function(tab, tabs, panes, config, hover, max_width)
local has_unseen_output = false
if not tab.is_active then
for _, pane in ipairs(tab.panes) do
if pane.has_unseen_output then
has_unseen_output = true
break
end
end
end
local cwd = wezterm.format({
{ Attribute = { Intensity = "Bold" } },
{ Text = get_current_working_dir(tab) },
})
local title = string.format(" [%s] %s", tab.tab_index + 1, cwd)
if has_unseen_output then
return {
{ Foreground = { Color = "#8866bb" } },
{ Text = title },
}
end
return {
{ Text = title },
}
end)
Full wezterm.lua
source here.
Conclusion
The benefit of having sessions management built into the terminal emulator itself provides quicker feedback and less complexity overall. But this approach is a lot more dependent on which terminal emulator you're using and what capabilities are available. Tmux with sesh is a great solution to fall back on, if e.g. trying out another terminal emulator.
There are also other ways to jump between projects from within Neovim, such as with telescope-project.nvim, but these kinds of tools rarely solve the problem of supporting e.g. direnv or pkgx/asdf, which are tools that use shell integration, and executes when you enter a folder (i.e. switching sessions). To make such tooling work well, you need to keep track of what your Neovim plugins are doing and which ones need special treatment to play well with this kind of behavior. I tried this setup out in this PR at one point, but I didn't like the added complexity which I have to maintain.
On leaving tmux behind, Wezterm comes out of the box with the notion of windows
and panes, the ability to split horizontally or vertically and most of the
features I would've missed from leaving tmux (although I do most of these things
inside Neovim instead). Hit Ctrl+Shift+P
to bring up the command palette and
explore.
I'm delighted to have a terminal emulator which is configurable with lua, the same language as Neovim itself, as this makes configuring a lot more customizable than e.g. a json/yaml-configured terminal. But I'm also very curious on Ghostty, which seems to be around the corner from being publicly released, and what sessions management capabilities it may bring.