Neovim plugin for Worktrunk — the git worktree manager CLI.
Big fan of Worktrunk! I use it every day and the CLI design is really well thought out. Wanted to bring that experience into Neovim so I can manage worktrees without leaving the editor. The --format=json support on every command made building this a pleasure.
No dependencies required. Telescope makes the picker prettier, but everything works fine without it.
- List & switch worktrees with a picker (
<leader>gwor:Worktrunk list) - Create new worktrees from Neovim (
:Worktrunk create my-feature) - Remove worktrees with confirmation (
:Worktrunk removeor<C-d>in the picker) - Merge current branch into main (
:Worktrunk merge) - Lualine component showing your current branch, status symbols, ahead/behind
- Hook system so you can run your own stuff on switch/create/remove
- When you switch worktrees, your current buffer reopens at the same path in the new tree
- Neovim >= 0.10
- Worktrunk installed (
brew install worktrunkorcargo install worktrunk) - Telescope (optional, for the fancy picker — falls back to
vim.ui.select) - Lualine (optional, for the statusline component)
Run :checkhealth worktrunk to verify everything's set up.
vim.pack.add { "https://github.com/emeren/wt.nvim" }{
"emeren/wt.nvim",
config = function()
require("worktrunk").setup()
end,
}use {
"emeren/wt.nvim",
config = function()
require("worktrunk").setup()
end,
}MiniDeps.add("emeren/wt.nvim")
require("worktrunk").setup()Calling setup() is optional — everything works with defaults. If you want to tweak something:
require("worktrunk").setup({
wt_binary = "wt", -- path to wt if it's not in your PATH
cd_command = "cd", -- "cd", "tcd", or "lcd"
update_on_switch = true, -- reopen current buffer in the new worktree
confirm_remove = true, -- ask before removing a worktree
notifications = true, -- show notifications on switch/create/remove
lualine = {
icon = "", -- icon in the statusline
refresh_interval_ms = 5000,
},
exec = "wt", -- terminal handler for :Worktrunk exec (default), see below
on = {
switch = function(data)
vim.notify("→ " .. data.branch, vim.log.levels.INFO, { title = "worktrunk" })
end,
create = function(data)
vim.notify("+ " .. data.branch, vim.log.levels.INFO, { title = "worktrunk" })
end,
remove = function(data)
vim.notify("✕ " .. data.branch, vim.log.levels.INFO, { title = "worktrunk" })
end,
},
})The plugin doesn't set any keymaps by default. Here's what I use:
vim.keymap.set("n", "<leader>gw", function()
require("worktrunk").list()
end, { desc = "Worktrees" })
vim.keymap.set("n", "<leader>gW", function()
require("worktrunk").create()
end, { desc = "Create worktree" })<Plug> mappings are also available if you prefer that style:
<Plug>(worktrunk-list)— open the picker<Plug>(worktrunk-create)— create a new worktree<Plug>(worktrunk-remove)— remove a worktree<Plug>(worktrunk-merge)— merge current worktree
If you have Telescope installed, :Worktrunk list (or <leader>gw) opens a picker with columns for branch, commit sha, status symbols, and ahead/behind info.
Inside the picker:
| Key | Action |
|---|---|
<CR> |
Switch to worktree |
<C-d> |
Remove worktree |
<C-n> |
Create new worktree |
Load the extension explicitly if you need it before first use:
require("telescope").load_extension("worktrunk")Without Telescope, everything falls back to vim.ui.select — which you can back with dressing.nvim, fzf-lua, or snacks.nvim if you want something nicer than the default.
Add the component to your lualine config:
require("lualine").setup({
sections = {
lualine_b = {
require("worktrunk.lualine").component,
},
},
})Shows something like: feature-auth +! ↑2
It refreshes async and never blocks your statusline.
Just call refresh() and get_text() wherever you build your statusline sections:
local wt = require("worktrunk.lualine")
wt.refresh()
local worktree = wt.get_text() -- e.g. " feature-auth +! ↑2"Drop that string into your MiniStatusline.combine_groups() call next to git info, and you're done.
Pass them in setup():
require("worktrunk").setup({
on = {
switch = function(data)
vim.notify("→ " .. data.branch)
end,
create = function(data)
-- maybe start a dev server
end,
remove = function(data)
-- clean up
end,
},
})Events: switch, create, remove. Each callback gets data with branch, path, and event-specific fields.
You can also register hooks at runtime with require("worktrunk.hooks").register(event, callback).
| Command | What it does |
|---|---|
:Worktrunk list |
Open the worktree picker |
:Worktrunk switch <branch> |
Switch to a worktree |
:Worktrunk create <branch> |
Create a new worktree (prompts if no name given) |
:Worktrunk remove [branch] |
Remove a worktree (current if no name given) |
:Worktrunk merge [target] |
Merge current branch (into main by default) |
:Worktrunk exec [branch] [-- cmd] |
Run a command in a worktree (picker if no branch given) |
In the Telescope picker, <C-x> on a worktree prompts for a command and runs it there.
Controls how :Worktrunk exec launches commands. The default wt handler opens the command in a new terminal window (via tmux when available, falls back to wt switch -x):
require("worktrunk").setup({
exec = "wt", -- default: new terminal window with the command
exec = { "wt", editor = true }, -- new tmux window: nvim (left) + command (right)
})With editor = true, :Worktrunk exec feat-123 -- claude "Fix the bug" opens a new tmux window with Neovim on the left and Claude Code on the right, both in the feat-123 worktree. Your current Neovim stays untouched.
Other built-in handlers:
require("worktrunk").setup({
exec = "tab", -- Neovim tab with termopen
exec = "toggleterm", -- toggleterm.nvim floating terminal
exec = "snacks", -- snacks.nvim terminal
exec = "fterm", -- FTerm.nvim
exec = "floaterm", -- vim-floaterm
exec = "tmux", -- tmux pane (no wt switch integration)
})Or bring your own:
require("worktrunk").setup({
exec = function(path, cmd, ctx)
-- path: worktree directory
-- cmd: list of strings, e.g. {"claude"} or {vim.o.shell}
-- ctx: { branch = "branch-name" }
end,
})Things I'm planning to add:
- snacks.nvim picker — native support, not just through vim.ui.select
- Diff preview in the Telescope picker (show what changed per worktree)
-
wt stepcommands — commit, squash, rebase from Neovim - Auto-close buffers from old worktree on switch
- Per-worktree sessions — save/restore window layout when switching
- CI status in the picker (worktrunk already has
--fullfor this) -
:Worktrunk exec— run a command in a worktree, e.g. open a terminal with Claude Code or your dev server - vimdoc (
:help worktrunk) - AI agent integration — spawn agents in worktrees, see which ones are active in the picker
Ideas? Issues? Open one.
MIT