Skip to content

Commit 38b7084

Browse files
ThomasK33claude
andauthored
feat(integrations): support ClaudeCodeTreeAdd in snacks.nvim pickers (#192) (#269)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent 0b24505 commit 38b7084

13 files changed

Lines changed: 724 additions & 3 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ When Anthropic released Claude Code, they only supported VS Code and JetBrains.
6060
"<leader>as",
6161
"<cmd>ClaudeCodeTreeAdd<cr>",
6262
desc = "Add file",
63-
ft = { "NvimTree", "neo-tree", "oil", "minifiles", "netrw" },
63+
ft = { "NvimTree", "neo-tree", "oil", "minifiles", "netrw", "snacks_picker_list" },
6464
},
6565
-- Diff management
6666
{ "<leader>aa", "<cmd>ClaudeCodeDiffAccept<cr>", desc = "Accept diff" },
@@ -212,7 +212,8 @@ Configure the plugin with the detected path:
212212
1. **Launch Claude**: Run `:ClaudeCode` to open Claude in a split terminal
213213
2. **Send context**:
214214
- Select text in visual mode and use `<leader>as` to send it to Claude
215-
- In `nvim-tree`/`neo-tree`/`oil.nvim`/`mini.nvim`, press `<leader>as` on a file to add it to Claude's context
215+
- In `nvim-tree`/`neo-tree`/`oil.nvim`/`mini.nvim`, or a focused snacks picker list / the Snacks Explorer sidebar, press `<leader>as` on a file to add it to Claude's context
216+
- For modal snacks pickers (`Snacks.picker.files()`/`grep()`), which keep focus in the input box, bind a picker action that calls `require("claudecode").send_at_mention(...)` for the selected item(s) — the [claude-fzf.nvim](#-claude-fzfnvim) community extension does the equivalent for `fzf-lua`
216217
3. **Let Claude work**: Claude can now:
217218
- See your current file and selections in real-time
218219
- Open files in your editor

fixtures/snacks-picker/README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# snacks-picker fixture — triage for issue #192
2+
3+
Reproduces and explores [#192](https://github.com/coder/claudecode.nvim/issues/192):
4+
make adding the **highlighted/selected file in a snacks.nvim picker** to Claude's
5+
context work, instead of failing with:
6+
7+
```
8+
[ClaudeCode] [command] [ERROR] ClaudeCodeTreeAdd: Not in a supported tree buffer (current filetype: snacks_picker_list)
9+
```
10+
11+
Unlike the tree-explorer integrations (`nvim-tree`, `neo-tree`, `oil`,
12+
`mini.files`, `netrw`), a snacks **picker** is a modal fuzzy finder. Its window
13+
layout is:
14+
15+
| window | filetype | buftype | usual focus |
16+
| ------ | --------------------- | -------- | --------------------------------- |
17+
| box | `snacks_layout_box` | `nofile` ||
18+
| list | `snacks_picker_list` | `nofile` | only when you `<Tab>`/cycle to it |
19+
| input | `snacks_picker_input` | `prompt` | **default (insert mode)** |
20+
21+
`:ClaudeCodeTreeAdd` only sees `snacks_picker_list` when the **list** window is
22+
focused — but the picker normally keeps focus in the **input** box. That is why
23+
the idiomatic fix is an in-picker _action bound to a key_, not an ex-command.
24+
25+
Note: `Snacks.explorer()` is built on the picker and also uses the
26+
`snacks_picker_list` filetype, so the same code path covers both.
27+
28+
## Run it
29+
30+
```bash
31+
source fixtures/nvim-aliases.sh
32+
vv snacks-picker
33+
```
34+
35+
Then `:ClaudeCodeStart` (or it auto-starts), and:
36+
37+
- `<leader>ff` → files picker, `<leader>fg` → grep picker.
38+
39+
### Path A — zero-change WORKAROUND (works on stock claudecode.nvim)
40+
41+
`lua/plugins/snacks.lua` registers a custom picker action `claude_add` bound to
42+
`<c-o>` in both the input and list windows. From the picker (input box, insert
43+
mode):
44+
45+
1. Type to filter; optionally `<Tab>` to multi-select several files.
46+
2. Press `<c-o>` → the selected files (or the one under the cursor) are sent to
47+
Claude via the public `require("claudecode").send_at_mention()` API, and the
48+
picker closes.
49+
50+
This needs **no changes to claudecode.nvim**. It mirrors how the community
51+
`claude-fzf.nvim` plugin integrates fzf-lua.
52+
53+
### Path B — built-in command path
54+
55+
The in-core `snacks_picker_list` handler (`integrations._get_snacks_picker_selection`)
56+
makes `:ClaudeCodeTreeAdd` work when the list window is focused:
57+
58+
1. Open a picker, `<Tab>` to focus/cycle to the **list** window (filetype becomes
59+
`snacks_picker_list`).
60+
2. `:ClaudeCodeTreeAdd` (or `<leader>at`) → selected/cursor files are added.
61+
62+
## Deterministic, headless proof (no Claude client needed)
63+
64+
The behavior was validated headlessly:
65+
66+
```text
67+
# Without the snacks_picker_list handler (the issue #192 state), list focused:
68+
focused_filetype: snacks_picker_list
69+
dispatch_error: Not in a supported tree buffer (current filetype: snacks_picker_list)
70+
71+
# With the in-core handler, 2 files multi-selected:
72+
handler_basenames: alpha.lua,beta.lua
73+
dispatch_files: 2 err: nil
74+
75+
# Key-bound action (Path A), 2 files multi-selected:
76+
send_at_mention_results: alpha.lua=true,beta.lua=true
77+
```
78+
79+
To reproduce headlessly, a small throwaway harness (not committed) opens a picker
80+
with `Snacks.picker.files({ cwd = ... })`, `vim.wait`s until
81+
`Snacks.picker.get()` returns a picker with items, focuses the list window, then
82+
calls `require("claudecode.integrations").get_selected_files_from_tree()` and
83+
inspects the returned paths. The committed unit test
84+
`tests/unit/snacks_picker_integration_spec.lua` covers the same logic
85+
deterministically with mocked snacks.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- example/alpha.lua — a file to highlight/select in the snacks picker.
2+
local M = {}
3+
4+
function M.greet(name)
5+
return "hello, " .. (name or "world")
6+
end
7+
8+
return M
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- example/beta.lua — a second file, to test multi-select (<Tab>) in the picker.
2+
local M = {}
3+
4+
function M.add(a, b)
5+
return a + b
6+
end
7+
8+
return M

fixtures/snacks-picker/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require("config.lazy")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
3+
"snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" },
4+
"tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" }
5+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
-- Bootstrap lazy.nvim
2+
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
3+
if not (vim.uv or vim.loop).fs_stat(lazypath) then
4+
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
5+
local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
6+
if vim.v.shell_error ~= 0 then
7+
vim.api.nvim_echo({
8+
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
9+
{ out, "WarningMsg" },
10+
{ "\nPress any key to exit..." },
11+
}, true, {})
12+
vim.fn.getchar()
13+
os.exit(1)
14+
end
15+
end
16+
vim.opt.rtp:prepend(lazypath)
17+
18+
vim.g.mapleader = " "
19+
vim.g.maplocalleader = "\\"
20+
21+
-- Resolve the claudecode.nvim checkout that owns this fixture.
22+
-- XDG_CONFIG_HOME is set to the `fixtures/` dir by the `vv` launcher, so the
23+
-- repository root is its parent. This makes the fixture load the local plugin
24+
-- copy (including git worktrees) without relying on lazy's default dev path.
25+
local repo_root = vim.fn.fnamemodify(vim.env.XDG_CONFIG_HOME or vim.fn.getcwd(), ":h")
26+
vim.g.claudecode_dev_dir = repo_root
27+
28+
require("lazy").setup({
29+
spec = {
30+
{ import = "plugins" },
31+
},
32+
install = { colorscheme = { "habamax" } },
33+
checker = { enabled = false },
34+
})
35+
36+
vim.keymap.set("n", "<leader>l", "<cmd>Lazy<cr>", { desc = "Lazy Plugin Manager" })
37+
vim.keymap.set("t", "<Esc><Esc>", "<C-\\><C-n>", { desc = "Exit terminal mode (double esc)" })
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-- Development configuration for claudecode.nvim with the snacks.nvim picker.
2+
-- Loads the local plugin checkout (resolved in lua/config/lazy.lua) via `dir`
3+
-- so it works from a normal checkout or a git worktree.
4+
return {
5+
"coder/claudecode.nvim",
6+
dir = vim.g.claudecode_dev_dir,
7+
dependencies = { "folke/snacks.nvim" },
8+
keys = {
9+
{ "<leader>a", nil, desc = "AI/Claude Code" },
10+
{ "<leader>ac", "<cmd>ClaudeCode<cr>", desc = "Toggle Claude" },
11+
{ "<leader>af", "<cmd>ClaudeCodeFocus<cr>", desc = "Focus Claude" },
12+
{ "<leader>aS", "<cmd>ClaudeCodeStart<cr>", desc = "Start Claude Server" },
13+
{ "<leader>aQ", "<cmd>ClaudeCodeStop<cr>", desc = "Stop Claude Server" },
14+
-- Built-in command path: focus the picker LIST window, then run this.
15+
{ "<leader>at", "<cmd>ClaudeCodeTreeAdd<cr>", desc = "Tree/Picker Add" },
16+
},
17+
---@type PartialClaudeCodeConfig
18+
opts = {
19+
-- Keep behavior predictable for reproduction; start the server explicitly.
20+
log_level = "debug",
21+
},
22+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Basic plugin configuration
2+
return {
3+
{
4+
"folke/tokyonight.nvim",
5+
lazy = false,
6+
priority = 1000,
7+
config = function()
8+
vim.cmd([[colorscheme tokyonight]])
9+
end,
10+
},
11+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
-- snacks.nvim picker fixture for issue #192.
2+
--
3+
-- Demonstrates BOTH integration paths for adding a picker's selected/highlighted
4+
-- file(s) to Claude Code's context:
5+
--
6+
-- 1. WORKAROUND (works TODAY, no claudecode.nvim change): a custom snacks picker
7+
-- `action` (`claude_add`) bound to <c-o> in the input AND list windows. This
8+
-- is the idiomatic snacks way and works from the input box in insert mode.
9+
-- It mirrors how the community claude-fzf.nvim plugin integrates fzf-lua.
10+
--
11+
-- 2. Built-in command path: `:ClaudeCodeTreeAdd` also works when the picker
12+
-- LIST window is focused (vim.bo.filetype == "snacks_picker_list"), via the
13+
-- in-core snacks_picker_list handler in lua/claudecode/integrations.lua.
14+
return {
15+
"folke/snacks.nvim",
16+
priority = 1000,
17+
lazy = false,
18+
---@type snacks.Config
19+
opts = {
20+
picker = {
21+
enabled = true,
22+
---@type table<string, snacks.picker.Action.spec>
23+
actions = {
24+
-- WORKAROUND action: send the selected (Tab) items, or the item under
25+
-- the cursor when nothing is selected, to Claude as @-mentions.
26+
claude_add = function(picker)
27+
local items = picker:selected({ fallback = true })
28+
local claudecode = require("claudecode")
29+
local count = 0
30+
for _, item in ipairs(items) do
31+
local path = Snacks.picker.util.path(item)
32+
if path and path ~= "" then
33+
local ok = claudecode.send_at_mention(path, nil, nil, "snacks-picker")
34+
if ok then
35+
count = count + 1
36+
end
37+
end
38+
end
39+
picker:close()
40+
vim.schedule(function()
41+
vim.notify(("[claude_add] Added %d file(s) to Claude context"):format(count), vim.log.levels.INFO)
42+
end)
43+
end,
44+
},
45+
win = {
46+
input = {
47+
keys = {
48+
["<c-o>"] = { "claude_add", mode = { "i", "n" }, desc = "Add to Claude Code" },
49+
},
50+
},
51+
list = {
52+
keys = {
53+
["<c-o>"] = { "claude_add", desc = "Add to Claude Code" },
54+
},
55+
},
56+
},
57+
},
58+
},
59+
keys = {
60+
{
61+
"<leader>ff",
62+
function()
63+
Snacks.picker.files()
64+
end,
65+
desc = "Find Files (snacks picker)",
66+
},
67+
{
68+
"<leader>fg",
69+
function()
70+
Snacks.picker.grep()
71+
end,
72+
desc = "Grep (snacks picker)",
73+
},
74+
},
75+
}

0 commit comments

Comments
 (0)