Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
71b0929
Add tmux list-panes source.
Nov 26, 2025
dc11f79
Add tmux panes formatter.
Nov 26, 2025
72f81e9
Keep "%" in pane_id.
Nov 26, 2025
93caff5
Basic pane preview.
Nov 26, 2025
88837df
Add select-pane action.
Nov 26, 2025
1cbdf3b
Add full tmux pane source configuration.
Nov 26, 2025
2d6e949
Check that formatted properties exist.
Nov 26, 2025
251fb72
Add tmux windows source.
Nov 26, 2025
7306832
Fix wrong icon.
Nov 26, 2025
68e3cc9
Add handling for tmux windows in formatter.
Nov 26, 2025
5eddd2d
Remove unneeded session_id.
Nov 26, 2025
93ad2db
Rename tmux format to indicate its flexibility.
Nov 26, 2025
3100776
Change tmux select action name to indicate its flexibility.
Nov 26, 2025
8eeb960
Add tmux windows source configuration.
Nov 26, 2025
d6c3fb1
Fix window command output matching.
Nov 26, 2025
dff6ae5
Fix "active" logic.
Nov 26, 2025
ac39af3
Add tmux sessions source.
Nov 26, 2025
99583eb
Handle formatting for tmux sessions.
Nov 26, 2025
0c464eb
Add tmux sessions config.
Nov 26, 2025
3458dd8
Close tmux picker on successful switch.
Nov 26, 2025
aef7cf0
Improve session attachment formatting.
Nov 26, 2025
066e3db
Use vim.system instead of proc for more flexibility.
Nov 28, 2025
db7289b
Use type property instead of existence of identifiers for formatting
Nov 28, 2025
52b16a0
Define tmux tree finder.
Nov 28, 2025
ee7a4ab
Add basic tree formatting.
Nov 29, 2025
7e8114a
Rework tmux formatter to allow for full tmux tree.
Dec 6, 2025
bb8188f
Correct condition for position icon.
Dec 6, 2025
3fb1e64
Add text to all tmux items to support matching.
Dec 7, 2025
f189841
Add tmux type to item text.
Dec 7, 2025
0e22f4c
Add more padding after id and remove active parens.
Dec 7, 2025
5977fb2
Correct logic for including "active" in text.
Dec 7, 2025
1dc9ccb
Improve "client" search text on sessions.
Dec 7, 2025
ea6fb1f
Fully flesh out tmux previewer.
Dec 8, 2025
fa368f1
Remove unused window operations.
Dec 8, 2025
09ae2b1
Add tmux (tree) source.
Dec 8, 2025
d907c2e
Define and use highlight groups.
Dec 15, 2025
a7cb99a
Add tmux clients source.
Dec 15, 2025
ee3e3d5
Format a few more fields relevant to tmux clients.
Dec 15, 2025
250ec51
Support client preview (preview focused pane).
Dec 15, 2025
bbf33eb
Add tmux clients source.
Dec 15, 2025
e577472
Add tmux keymap examples.
Dec 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/examples/picker.lua
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ M.examples.general = {
{ "<leader>sR", function() Snacks.picker.resume() end, desc = "Resume" },
{ "<leader>su", function() Snacks.picker.undo() end, desc = "Undo History" },
{ "<leader>uC", function() Snacks.picker.colorschemes() end, desc = "Colorschemes" },
-- tmux
{ "<leader>tt", function() Snacks.picker.tmux() end, desc = "Tmux Tree"},
{ "<leader>tp", function() Snacks.picker.tmux() end, desc = "Tmux Panes"},
{ "<leader>tw", function() Snacks.picker.tmux() end, desc = "Tmux Windows"},
{ "<leader>ts", function() Snacks.picker.tmux() end, desc = "Tmux Sessions"},
{ "<leader>tc", function() Snacks.picker.tmux() end, desc = "Tmux Clients"},
-- LSP
{ "gd", function() Snacks.picker.lsp_definitions() end, desc = "Goto Definition" },
{ "gD", function() Snacks.picker.lsp_declarations() end, desc = "Goto Declaration" },
Expand Down
19 changes: 19 additions & 0 deletions lua/snacks/picker/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -841,4 +841,23 @@ function M.list_scroll_up(picker)
picker.list:scroll(-picker.list.state.scroll)
end

function M.tmux_select(picker)
local items = picker:selected({ fallback = true })
local first = items[1]
if not first then
Snacks.notify.error("Can't find tmux target", { title = "Snacks Picker" })
return
end
local target = first.pane_id or first.window_id or first.session_name
if not target then
Snacks.notify.error("Can't find tmux target", { title = "Snacks Picker" })
return
end
local cmd = { "tmux", "switch-client", "-t", target }
Snacks.picker.util.cmd(cmd, function()
Snacks.notify("Switched to tmux target " .. target, { title = "Snacks Picker" })
picker:close()
end)
end

return M
8 changes: 8 additions & 0 deletions lua/snacks/picker/config/highlights.lua
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ Snacks.util.set_hl({
IconTypeParameter = "@lsp.type.typeParameter",
IconVariable = "@variable",
Rule = "@punctuation.special.markdown",
TmuxActivity = "Changed",
TmuxAddr = "Number",
TmuxDelim = "Delimiter",
TmuxExtra = "Special",
TmuxIcon = "Special",
TmuxId = "Label",
TmuxName = "Title",
TmuxUser = "Constant",
}, { prefix = "SnacksPicker", default = true })

return M
41 changes: 41 additions & 0 deletions lua/snacks/picker/config/sources.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,47 @@ M.tags = {
format = "lsp_symbol",
}

M.tmux = {
finder = "tmux_tree",
sort = { fields = { "session_name", "window_index", "pane_index" } },
format = "tmux",
preview = "tmux",
matcher = { sort_empty = true },
confirm = "tmux_select",
}

-- Search tmux clients
M.tmux_clients = {
finder = "tmux_clients",
format = "tmux",
preview = "tmux",
confirm = "tmux_select",
}

-- Search tmux panes
M.tmux_panes = {
finder = "tmux_panes",
format = "tmux",
preview = "tmux",
confirm = "tmux_select",
}

-- Search tmux sessions
M.tmux_sessions = {
finder = "tmux_sessions",
format = "tmux",
preview = "tmux",
confirm = "tmux_select",
}

-- Search tmux windows
M.tmux_windows = {
finder = "tmux_windows",
format = "tmux",
preview = "tmux",
confirm = "tmux_select",
}

---@class snacks.picker.treesitter.Config: snacks.picker.Config
---@field filter table<string, string[]|boolean>? symbol kind filter
---@field tree? boolean show symbol tree
Expand Down
102 changes: 102 additions & 0 deletions lua/snacks/picker/format.lua
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,108 @@ function M.text(item, picker)
return ret
end

function M.tmux(item, picker)
local a = Snacks.picker.util.align
local active_window_icons = {
top = "󰁞",
bottom = "󰁆",
left = "󰁎",
right = "󰁕",
["top-left"] = "󰧄",
["top-right"] = "󰧆",
["bottom-left"] = "󰦸",
["bottom-right"] = "󰦺",
none = "",
}
local inactive_window_icons = {
top = "󰧇",
bottom = "󰦿",
left = "󰧀",
right = "󰧂",
["top-left"] = "󰧃",
["top-right"] = "󰧅",
["bottom-left"] = "󰦷",
["bottom-right"] = "󰦹",
none = "",
}
local ret = {} ---@type snacks.picker.Highlight[]
local element

element = item.position and (item.window_active and active_window_icons or inactive_window_icons)[item.position]
or (item.tree and "")
if element then
ret[#ret + 1] = { a(element, 2), "SnacksPickerTmuxIcon" }
end

if item.type == "client" and item.client_name then
ret[#ret + 1] = { a(item.client_name, 12), "SnacksPickerTmuxId" }
end

if item.tree then
vim.list_extend(ret, M.tree(item, picker))
if item.type == "session" and item.session_name then
ret[#ret + 1] = { a(item.session_name .. ":", 9, { truncate = true }), "SnacksPickerTmuxAddr" }
elseif item.type == "window" and item.window_index then
ret[#ret + 1] = { a(tostring(item.window_index) .. ".", 7, { truncate = true }), "SnacksPickerTmuxAddr" }
elseif item.type == "pane" and item.pane_index then
ret[#ret + 1] = { a(tostring(item.pane_index), 5, { truncate = true }), "SnacksPickerTmuxAddr" }
end
else
if item.session_name then
ret[#ret + 1] = { a(item.session_name, 8, { truncate = true, align = "right" }), "SnacksPickerTmuxAddr" }
ret[#ret + 1] = { " :", "SnacksPickerTmuxDelim" }
if item.window_index and item.window_index >= 0 then
ret[#ret + 1] = { a(tostring(item.window_index), 3, { align = "center" }), "SnacksPickerTmuxAddr" }
ret[#ret + 1] = { ". ", "SnacksPickerTmuxDelim" }
if item.pane_index and item.pane_index >= 0 then
ret[#ret + 1] = { a(tostring(item.pane_index), 3), "SnacksPickerTmuxAddr" }
end
else
ret[#ret + 1] = { " ", "SnacksPickerTmuxDelim" }
end
end
end

element = item.current_command or item.window_name or (item.tree and "")
if element then
ret[#ret + 1] = { a(element, 8, { truncate = true }), "SnacksPickerTmuxName" }
end

if item.type and item[item.type .. "_id"] then
ret[#ret + 1] = { a(item[item.type .. "_id"], 4), "SnacksPickerTmuxId" }
end

element = nil
if item.type == "session" and item.session_windows then
element = tostring(item.session_windows) .. " window" .. (item.session_windows == 1 and " " or "s")
elseif item.type == "window" and item.window_panes then
element = tostring(item.window_panes) .. " pane" .. (item.window_panes == 1 and " " or "s")
elseif item.tree then
element = ""
end
if element then
ret[#ret + 1] = { a(element, 11), "SnacksPickerTmuxExtra" }
end

element = nil
if item.type == "session" and item.session_attached then
element = tostring(item.session_attached) .. " client" .. (item.session_attached == 1 and " " or "s")
elseif (item.type == "pane" and item.pane_active) or (item.type == "window" and item.window_active) then
element = "active"
elseif item.tree then
element = ""
end
if element then
ret[#ret + 1] = { a(element, 11), "SnacksPickerTmuxActivity" }
end

if item.client_user then
ret[#ret + 1] = { a("<" .. item.client_user .. ">", 12), "SnacksPickerTmuxUser" }
end

return ret
end

function M.command(item)
local ret = {} ---@type snacks.picker.Highlight[]
ret[#ret + 1] = { item.cmd, "SnacksPickerCmd" .. (item.cmd:find("^[a-z]") and "Builtin" or "") }
Expand Down
25 changes: 25 additions & 0 deletions lua/snacks/picker/preview.lua
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,29 @@ function M.man(ctx)
})
end

---@param ctx snacks.picker.preview.ctx
function M.tmux(ctx)
local main_type = ctx.item.type:gsub("^%l", string.upper)
local child_type = (ctx.item.type == "session" and "windows") or (ctx.item.type == "window" and "panes") or "preview"
local addr = ctx.item.type == "client" and ctx.item.client_name
or (ctx.item.session_name or "")
.. (ctx.item.window_index >= 0 and ":" .. tostring(ctx.item.window_index) or "")
.. (ctx.item.pane_index >= 0 and "." .. tostring(ctx.item.pane_index) or "")
ctx.preview:set_title(("%s %s %s"):format(main_type, addr, child_type))
if ctx.item.type == "session" or ctx.item.type == "window" then
ctx.preview:scratch()
local children = vim.tbl_map(
function(val)
return Snacks.picker.format.tmux(val, ctx.picker)
end,
vim.tbl_filter(function(val)
return val[ctx.item.type .. "_id"] == ctx.item[ctx.item.type .. "_id"]
end, require("snacks.picker.source.tmux")[child_type](_, ctx))
)
Snacks.picker.highlight.render(ctx.buf, ns, children, { append = true })
elseif ctx.item.type == "pane" or ctx.item.type == "client" and ctx.item.pane_id then
M.cmd(("setterm -linewrap off; tmux capture-pane -pe -t %s"):format(ctx.item.pane_id), ctx)
end
end

return M
Loading
Loading