WezTerm Configuration 1: Projects Selector

In this post I’ll add my configuration for WezTerm. At the end, it will look like this:

WezTerm
WezTerm

Setting up keybindings

The first step is to add a key binding. In our case, it will be LEADER + p where LEADER will be mapped to CTRL + a.

In your .wezterm.lua file add:

 1local wezterm = require("wezterm")
 2local config = wezterm.config_builder()
 3
 4config.leader = { key = "a", mods = "CTRL", timeout_milliseconds = 2000 }
 5config.keys = {
 6  {
 7    mods = "LEADER",
 8    key = "p",
 9    action = wezterm.action_callback(function(window, pane)
10    end),
11  },
12}

With this configuration, now you’ll be able to type CTRL + a then p and it will… not do nothing.

In order for this configuration to actually do something, we need more code. Add a new file in the same folder where your .wezterm.lua file is located called utils.lua. In this file, add the following code:

  1local M = {}
  2
  3M.get_directories = function(path, level)
  4 local directories = {}
  5 level = level or 0
  6 if level > 1 then
  7  return directories
  8 end
  9
 10 -- Use safer path handling
 11 local sanitized_path = string.gsub(path, '"', '\\"')
 12 local p = io.popen('find "' .. sanitized_path .. '" -maxdepth 1 -mindepth 1 \\( -type d -o -type l \\)')
 13
 14 if not p then
 15  wezterm.log_warn("Failed to open directory: " .. path)
 16  return directories
 17 end
 18
 19 for file in p:lines() do
 20  local dir_name = file:match("([^/]+)$")
 21  if dir_name then
 22   table.insert(directories, {
 23    label = file,
 24    id = dir_name,
 25   })
 26
 27   -- Recursively get subdirectories
 28   local subdirs = M.get_directories(file, level + 1)
 29   for _, subdir in ipairs(subdirs) do
 30    table.insert(directories, subdir)
 31   end
 32  end
 33 end
 34
 35 p:close()
 36 return directories
 37end
 38
 39M.open_project = function(window, _, id, label)
 40 local mux = wezterm.mux
 41
 42 if not label then
 43  wezterm.log_info("Project selection cancelled")
 44  return
 45 end
 46
 47 wezterm.emit("tab-changed", window)
 48
 49 local workspace = label:match("([^/]+)/[^/]+$")
 50
 51 -- Find existing window for workspace
 52 local target_window = nil
 53 for _, win in ipairs(mux.all_windows()) do
 54  if win:get_workspace() == workspace then
 55   target_window = win
 56   break
 57  end
 58 end
 59
 60 -- Create new window if needed
 61 if not target_window then
 62  local _, _, new_window = mux.spawn_window({
 63   cwd = label,
 64   workspace = workspace,
 65  })
 66  target_window = new_window
 67  target_window:set_title(workspace)
 68
 69  -- Set title for initial tab
 70  local tabs = target_window:tabs()
 71  for _, tab in ipairs(tabs) do
 72   tab:set_title(id)
 73   wezterm.log_info("Created new tab:", tab:get_title())
 74  end
 75 end
 76
 77 -- Find existing tab or create new one
 78 local target_tab = nil
 79 for _, tab in ipairs(target_window:tabs()) do
 80  if tab:get_title() == id then
 81   target_tab = tab
 82   wezterm.log_info("Found existing tab")
 83   break
 84  end
 85 end
 86
 87 if not target_tab then
 88  target_tab = target_window:spawn_tab({ cwd = label })
 89  target_tab:set_title(id)
 90 end
 91
 92 target_tab:activate()
 93 mux.set_active_workspace(workspace)
 94end
 95
 96M.show_projects = function(window, pane)
 97 local choices_work = M.get_directories("/Users/username/work", 0)
 98 local choices_personal = M.get_directories("/Users/username/personal", 0)
 99
100 local choices = {}
101
102 for _, v in ipairs(choices_work) do
103  table.insert(choices, v)
104 end
105
106 for _, v in ipairs(choices_personal) do
107  table.insert(choices, v)
108 end
109
110 window:perform_action(
111  wezterm.action.InputSelector({
112   action = wezterm.action_callback(M.open_project),
113   choices = choices,
114   fuzzy = true,
115   title = "💡 Choose a project",
116  }),
117  pane
118 )
119end
120
121return M

And update the keys to:

 1local wezterm = require("wezterm")
 2local config = wezterm.config_builder()
 3local utils = require("utils")
 4
 5config.leader = { key = "a", mods = "CTRL", timeout_milliseconds = 2000 }
 6config.keys = {
 7  {
 8    mods = "LEADER",
 9    key = "p",
10    action = wezterm.action_callback(utils.show_projects),
11  },
12}

Now you’ll be able to automatically open your projects in different tabs in different windows separated by workspaces.

However, how to go back to the one you already opened? This code will automatically move you to the one you had opened if it exists. But, if you want to see which tabs you have open, that’ll be done in the next entry.