DEV Community

Cover image for Make Wezterm Mimic Tmux
Lovelin
Lovelin

Posted on

Make Wezterm Mimic Tmux

I have been using the Alacritty + Tmux setup for a while, and it's one of those things I would call "that just stuck".

My setup is so minimal that I could happily live with just a terminal and an emulator. I need to manage a lot of processes, like running an npm server, editing a couple of Neovim buffers, overseeing a Docker image build, etc. This is where Tmux comes in handy, and life has been good ever since.

A month ago, I came across WezTerm, a new GPU-accelerated, cross-platform terminal emulator written in Rust (and I’m not a Rust fanboy, for real!). It piqued my interest, so I decided to give it a try.

Wezterm matched my expectations. It's feels fast and looks modern, with support for ligatures, tab + pane support and image rendering out of the box, something I have been sorely missing out in Alacritty.

In my opinion, the biggest selling point of WezTerm is its full customization through Lua. The documentation is robust for a project of its maturity, and with sensible Lua defaults, the configuration process feels almost zen.

I’m tired of working with the domain-specific configuration languages of Tmux and Kitty, so I thought, why not make WezTerm mimic Tmux as much as possible?

So, I sat down for an hour and came up with the configuration below. There are numerous keybindings based on my personal preferences, so feel free to modify them to suit your needs.

The configuration

Wezterm's config lives as .wezterm.lua in our home directory.

local wezterm = require("wezterm")
local config = wezterm.config_builder()
-- our config overrides goes in here
return config
Enter fullscreen mode Exit fullscreen mode

So let's do some basic config

config.font_size = 16

config.window_decorations = "RESIZE"

config.window_frame = {
    font_size = 13.0,
}

config.window_padding = {
    top = 10,
    bottom = 10,
    left = 10,
    right = 10,
}

config.term = "xterm-256color"
Enter fullscreen mode Exit fullscreen mode

We changed the font size, window padding, and set the window_decorations as "RESIZE" which disables the title bar but enable the resizable border.

Setting term as "xterm-256color" allow wezterm to utilize 256 colors for text output

Also, I would like to launch wezterm in fullscreen mode by default.

wezterm.on("gui-startup", function(cmd)
    local tab, pane, window = mux.spawn_window(cmd or {})
    window:gui_window():maximize()
end)
Enter fullscreen mode Exit fullscreen mode

Alright, now the niceties are done, let's make wezterm mimic tmux.

Leader Key

We will be setting up a leader key in wezterm, just like we have one in tmux. Our Leader Key here, is CTRL + A. You can use anything you feel comfortable.

config.leader = { key = "a", mods = "CTRL", timeout_milliseconds = 2000 }
Enter fullscreen mode Exit fullscreen mode

Splitting Panes

We will add keybindings to split panes horizontally and vertically.

local action = wezterm.action
config.keys = {
    {
        key = "-",
        mods = "LEADER",
        action = action.SplitVertical({ domain = "CurrentPaneDomain" }),
    },

    {
        key = "\\",
        mods = "LEADER",
        action = action.SplitHorizontal({ domain = "CurrentPaneDomain" }),
    },
}
Enter fullscreen mode Exit fullscreen mode

Triggering LEADER + \ will split panes horizontally while LEADER + - will split panes vertically.

Adjusting pane size

Let's add some keybindings to adjust a pane's size

config.keys = {
    {
        key = "h",
        mods = "CTRL|SHIFT",
        action = action.AdjustPaneSize({ "Left", 5 }),
    },
    {
        key = "l",
        mods = "CTRL|SHIFT",
        action = action.AdjustPaneSize({ "Right", 5 }),
    },
    {
        key = "j",
        mods = "CTRL|SHIFT",
        action = action.AdjustPaneSize({ "Down", 5 }),
    },
    {
        key = "k",
        mods = "CTRL|SHIFT",
        action = action.AdjustPaneSize({ "Up", 5 }),
    },
        {
        key = "m",
        mods = "LEADER",
        action = action.TogglePaneZoomState,
    }
}
Enter fullscreen mode Exit fullscreen mode

I used Vim motions to adjust the size of the focused pane. Pressing CTRL + SHIFT + h|j|k|l will increase the pane's size by 5 units in the corresponding Vim motion direction.

Additionally, pressing LEADER + m will zoom the pane to take up the full window. Clicking it again will return the pane to its previous state.

Tab configuration

We will be using wezterm tabs as an equivalent to tmux windows.

config.key = {
    {
        key = "c",
        mods = "LEADER",
        action = action.SpawnTab("CurrentPaneDomain"),
    },

    {
        key = "p",
        mods = "LEADER",
        action = action.ActivateTabRelative(-1),
    },
    {
        key = "n",
        mods = "LEADER",
        action = action.ActivateTabRelative(1),
    }
}
Enter fullscreen mode Exit fullscreen mode

The keybinding LEADER + c will create a new tab, while LEADER + p and LEADER + n will shift the focus to the previous tab and the next tab, respectively.

for i = 1, 9 do
    table.insert(config.keys, {
        key = tostring(i),
        mods = "LEADER",
        action = action.ActivateTab(i - 1),
    })
end
Enter fullscreen mode Exit fullscreen mode

This loop would add keybindings which will allow us to activate a specific tab by its number from 1 to 9. For instance, if I am on the fifth tab and I need to go to the first tab, I can just click LEADER + 1.

Setup VI Mode

The VI mode, or copy mode, allows you to copy content in your terminal using Vim keybindings.

config.key = {
    { key = "[", mods = "LEADER", action = action.ActivateCopyMode },
}
Enter fullscreen mode Exit fullscreen mode

With this configuration, WezTerm will enter VI mode when you trigger LEADER + [. You can now navigate WezTerm like a Vim buffer, selecting and yanking lines using Vim keybindings. Pressing CTRL + C will exit VI mode.

Navigating between nvim splits and wezterm splits

eovim split or a WezTerm split. If it's a Neovim split, the keypress will be passed to it; if it's a normal WezTerm split, WezTerm will take control.

To make this work, you should install the smart-splits plugin for neovim, with the this configuration

local direction_keys = {
    h = "Left",
    j = "Down",
    k = "Up",
    l = "Right",
}

local function split_nav(key)
    return {
        key = key,
        mods = "CTRL",
        action = wezterm.action_callback(function(win, pane)
            if pane:Get_users_vars().IS_NVIM == "true" then
                -- pass the keys through to vim/nvim
                win:perform_action({
                    SendKey = { key = key, mods = "CTRL" },
                }, pane)
            else
                win:perform_action({ ActivatePaneDirection = direction_keys[key] }, pane)
            end
        end),
    }
end

config.keys = {
    split_nav("h"),
    split_nav("j"),
    split_nav("k"),
    split_nav("l"),
}
Enter fullscreen mode Exit fullscreen mode

Now we can navigate through neovim splits and wezterm panes seamlessly using CTRL + h|j|k|l.

Here is how the whole configuration should look like

local wezterm = require("wezterm")
local mux = wezterm.mux

wezterm.on("gui-startup", function(cmd)
    local tab, pane, window = mux.spawn_window(cmd or {})
    window:gui_window():maximize()
end)

local config = wezterm.config_builder()

config.term = "xterm-256color"

local direction_keys = {
    h = "Left",
    j = "Down",
    k = "Up",
    l = "Right",
}

local function split_nav(key)
    return {
        key = key,
        mods = "CTRL",
        action = wezterm.action_callback(function(win, pane)
            if pane:Get_users_vars().IS_NVIM == "true" then
                -- pass the keys through to vim/nvim
                win:perform_action({
                    SendKey = { key = key, mods = "CTRL" },
                }, pane)
            else
                win:perform_action({ ActivatePaneDirection = direction_keys[key] }, pane)
            end
        end),
    }
end

config.font_size = 16

config.window_decorations = "RESIZE"

config.window_padding = {
    top = 10,
    bottom = 10,
    left = 10,
    right = 10,
}

config.leader = { key = "a", mods = "CTRL", timeout_milliseconds = 2000 }

local action = wezterm.action

config.keys = {
    {
        key = "\\",
        mods = "LEADER",
        action = action.SplitHorizontal({ domain = "CurrentPaneDomain" }),
    },
    split_nav("h"),
    split_nav("j"),
    split_nav("k"),
    split_nav("l"),
    {
        key = "h",
        mods = "CTRL|SHIFT",
        action = action.AdjustPaneSize({ "Left", 5 }),
    },
    {
        key = "l",
        mods = "CTRL|SHIFT",
        action = action.AdjustPaneSize({ "Right", 5 }),
    },
    {
        key = "j",
        mods = "CTRL|SHIFT",
        action = action.AdjustPaneSize({ "Down", 5 }),
    },
    {
        key = "k",
        mods = "CTRL|SHIFT",
        action = action.AdjustPaneSize({ "Up", 5 }),
    },
    {
        key = "-",
        mods = "LEADER",
        action = action.SplitVertical({ domain = "CurrentPaneDomain" }),
    },
    {
        key = "m",
        mods = "LEADER",
        action = action.TogglePaneZoomState,
    },
    { key = "[", mods = "LEADER", action = action.ActivateCopyMode },
    {
        key = "c",
        mods = "LEADER",
        action = action.SpawnTab("CurrentPaneDomain"),
    },

    {
        key = "p",
        mods = "LEADER",
        action = action.ActivateTabRelative(-1),
    },
    {
        key = "n",
        mods = "LEADER",
        action = action.ActivateTabRelative(1),
    },
}

for i = 1, 9 do
    table.insert(config.keys, {
        key = tostring(i),
        mods = "LEADER",
        action = action.ActivateTab(i - 1),
    })
end

return config
Enter fullscreen mode Exit fullscreen mode

Conclusion

WezTerm is cool! I was able to ditch the overhead of Tmux by using WezTerm. While it may not match all of Tmux's capabilities, it works perfectly for my simple use cases. There’s much more to explore in WezTerm’s configuration, which you can find on their official website. Here is my dotfiles in case if you want to peek. See ya!

Cover Image from wezfurlong.org/wezterm

Top comments (0)