Skip to content

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

wt.nvim

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.

What it does

  • List & switch worktrees with a picker (<leader>gw or :Worktrunk list)
  • Create new worktrees from Neovim (:Worktrunk create my-feature)
  • Remove worktrees with confirmation (:Worktrunk remove or <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

Requirements

  • Neovim >= 0.10
  • Worktrunk installed (brew install worktrunk or cargo 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.

Install

vim.pack (Neovim 0.12+, built-in)

vim.pack.add { "https://github.com/emeren/wt.nvim" }

lazy.nvim

{
  "emeren/wt.nvim",
  config = function()
    require("worktrunk").setup()
  end,
}

packer.nvim

use {
  "emeren/wt.nvim",
  config = function()
    require("worktrunk").setup()
  end,
}

mini.deps

MiniDeps.add("emeren/wt.nvim")
require("worktrunk").setup()

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,
  },
})

Keymaps

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

Telescope picker

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.

Lualine

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.

mini.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.

Hooks

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).

Commands

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.

Exec handler (experimental)

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,
})

What's next

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 step commands — 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 --full for 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.

License

MIT

About

Neovim plugin for Worktrunk

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages