Skip to content

Commit 06c6b4b

Browse files
committed
feat: Add configurable buffer names for Agentic panels
Signed-off-by: Chance Zibolski <chance.zibolski@gmail.com>
1 parent f360926 commit 06c6b4b

5 files changed

Lines changed: 177 additions & 16 deletions

File tree

README.md

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,8 @@ https://github.com/user-attachments/assets/c6653a8b-20ef-49c8-b644-db0df1b342f0
202202
- For security reasons, this plugin doesn't install or manage binaries for
203203
you. You must install them manually.
204204

205-
**We recommend using `pnpm`**
206-
`pnpm` uses a constant, static global path, that's resilient to updates.
205+
**We recommend using `pnpm`**
206+
`pnpm` uses a constant, static global path, that's resilient to updates.
207207
While `npm` loses global packages every time you change Node versions using
208208
tools like `nvm`, `fnm`, etc...
209209

@@ -222,14 +222,14 @@ tools like `nvm`, `fnm`, etc...
222222
| [cline][cline] | `pnpm add -g cline`<br/> **OR** `npm i -g cline`<br/> **OR** See [Cline docs][cline-docs] |
223223
| [goose][goose] | `brew install block-goose-cli`<br/> **OR** See [Goose docs][goose-docs] |
224224

225-
> [!WARNING]
225+
> [!WARNING]
226226
> These install commands are here for convenience, please always refer to the
227227
> official installation instructions from the respective ACP provider.
228228
229-
> [!NOTE]
229+
> [!NOTE]
230230
> Why install ACP provider CLIs globally?
231231
> [shai-hulud](https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack)
232-
> should be reason enough. 📌 Pin your versions!
232+
> should be reason enough. 📌 Pin your versions!
233233
> But frontend projects with strict package management policies will fail to
234234
> start when using `npx ...`
235235
@@ -307,8 +307,8 @@ here for ease access and reference:
307307
You can customize the built-in providers or add any new ACP-compatible provider
308308
by configuring the `acp_providers` property:
309309

310-
> [!NOTE]
311-
> You don't have to override anything or include these in your setup!
310+
> [!NOTE]
311+
> You don't have to override anything or include these in your setup!
312312
> These are just examples of how you can customize the commands, env, etc.
313313
314314
```lua
@@ -356,7 +356,7 @@ by configuring the `acp_providers` property:
356356
- `initial_model` (string, optional) - Default model ID to set on session
357357
creation (e.g., `"haiku"`)
358358

359-
> [!NOTE]
359+
> [!NOTE]
360360
> Customizing a provider only requires specifying the fields you want to
361361
> override, not the entire configuration.
362362
@@ -502,6 +502,47 @@ header parts:
502502
}
503503
```
504504

505+
### Customizing Buffer Names
506+
507+
By default, buffer names mirror the window header titles. You can override them
508+
independently via the `buffer_name` field in each window's config, for example
509+
to show cleaner names in your statusline or buffer switcher:
510+
511+
```lua
512+
{
513+
"carlos-algms/agentic.nvim",
514+
opts = {
515+
windows = {
516+
chat = { buffer_name = "Agentic Chat" },
517+
input = { buffer_name = "Agentic Prompt" },
518+
code = { buffer_name = "Code Snippets" },
519+
files = { buffer_name = "Files" },
520+
diagnostics = { buffer_name = "Diagnostics" },
521+
todos = { buffer_name = "Tasks" },
522+
},
523+
},
524+
}
525+
```
526+
527+
You can also use a function that receives the header parts and returns a string:
528+
529+
```lua
530+
{
531+
"carlos-algms/agentic.nvim",
532+
opts = {
533+
windows = {
534+
chat = {
535+
buffer_name = function(parts)
536+
return "AI: " .. parts.title
537+
end,
538+
},
539+
},
540+
},
541+
}
542+
```
543+
544+
When a panel has no `buffer_name` set, it falls back to the header title.
545+
505546
### Folding
506547

507548
Long tool call outputs are automatically folded to keep the chat buffer
@@ -720,7 +761,7 @@ are provided by your ACP provider.
720761

721762
### File Picker
722763

723-
You can reference and add files to the context by typing `@` in the Prompt.
764+
You can reference and add files to the context by typing `@` in the Prompt.
724765
It will trigger the native Neovim completion menu with a list of all files in
725766
the current workspace.
726767

@@ -1147,7 +1188,7 @@ Enable debug logging to troubleshoot issues:
11471188

11481189
View debug logs with `:messages` (lost after restarting Neovim)
11491190

1150-
View messages exchanged with the ACP provider in the log file at:
1191+
View messages exchanged with the ACP provider in the log file at:
11511192
(persistent until you delete it)
11521193

11531194
- `~/.cache/nvim/agentic_debug.log`
@@ -1158,7 +1199,7 @@ View messages exchanged with the ACP provider in the log file at:
11581199

11591200
## 📄 License
11601201

1161-
[MIT License](LICENSE.txt)
1202+
[MIT License](LICENSE.txt)
11621203
Feel free to copy, modify, and distribute, just be a good samaritan and include
11631204
the the acknowledgments 😊.
11641205

doc/agentic.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ windows ~
271271
`height` Height when position is `"bottom"`. String
272272
percentage or number. Default: `"30%"`.
273273
`stack_width_ratio` Ratio for stacked panels. Default: `0.4`.
274+
`buffer_name` Name of the window buffer. String title
275+
or function returning string title.
276+
Default: window header title.
277+
See agentic-config-headers.
274278

275279
Sub-panel options (each accepts `win_opts` table):
276280

lua/agentic/config_default.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,34 @@
6464
--- Overrides default options (wrap, linebreak, winfixheight)
6565
--- @alias agentic.UserConfig.WinOpts table<string, any>
6666

67+
--- @alias agentic.UserConfig.BufferNameFn fun(header: agentic.ui.ChatWidget.HeaderParts): string|nil
68+
6769
--- @class agentic.UserConfig.Windows.Chat
70+
--- @field buffer_name? string|agentic.UserConfig.BufferNameFn
6871
--- @field win_opts? agentic.UserConfig.WinOpts
6972

7073
--- @class agentic.UserConfig.Windows.Input
74+
--- @field buffer_name? string|agentic.UserConfig.BufferNameFn
7175
--- @field height number
7276
--- @field win_opts? agentic.UserConfig.WinOpts
7377

7478
--- @class agentic.UserConfig.Windows.Code
79+
--- @field buffer_name? string|agentic.UserConfig.BufferNameFn
7580
--- @field max_height number
7681
--- @field win_opts? agentic.UserConfig.WinOpts
7782

7883
--- @class agentic.UserConfig.Windows.Files
84+
--- @field buffer_name? string|agentic.UserConfig.BufferNameFn
7985
--- @field max_height number
8086
--- @field win_opts? agentic.UserConfig.WinOpts
8187

8288
--- @class agentic.UserConfig.Windows.Diagnostics
89+
--- @field buffer_name? string|agentic.UserConfig.BufferNameFn
8390
--- @field max_height number
8491
--- @field win_opts? agentic.UserConfig.WinOpts
8592

8693
--- @class agentic.UserConfig.Windows.Todos
94+
--- @field buffer_name? string|agentic.UserConfig.BufferNameFn
8795
--- @field display boolean
8896
--- @field max_height number
8997
--- @field win_opts? agentic.UserConfig.WinOpts

lua/agentic/ui/window_decoration.lua

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,73 @@ local function set_winbar(winid, text)
195195
vim.api.nvim_set_option_value("winbar", winbar_text, { win = winid })
196196
end
197197

198+
--- Resolves the buffer name from config, supporting string or function values
199+
--- @param window_name string Window name for Config.windows[name].buffer_name lookup
200+
--- @param header_parts agentic.ui.ChatWidget.HeaderParts Header parts passed to function-type buffer_name
201+
--- @param fallback string|nil Fallback name (resolved header text) when buffer_name is not set
202+
--- @return string|nil name
203+
local function resolve_buffer_name(window_name, header_parts, fallback)
204+
local win_cfg = Config.windows[window_name]
205+
local buffer_name = win_cfg and win_cfg.buffer_name
206+
207+
if buffer_name == nil then
208+
return fallback
209+
end
210+
211+
if type(buffer_name) == "string" then
212+
return buffer_name
213+
end
214+
215+
if type(buffer_name) == "function" then
216+
local ok, result = pcall(buffer_name, header_parts)
217+
if not ok then
218+
Logger.notify(
219+
string.format(
220+
"Error in buffer_name function for '%s': %s",
221+
window_name,
222+
result
223+
)
224+
)
225+
return fallback
226+
end
227+
if result ~= nil and type(result) ~= "string" then
228+
Logger.notify(
229+
string.format(
230+
"buffer_name function for '%s' must return string|nil, got %s",
231+
window_name,
232+
type(result)
233+
)
234+
)
235+
return fallback
236+
end
237+
return result
238+
end
239+
240+
Logger.notify(
241+
string.format(
242+
"buffer_name for '%s' must be string|function|nil, got %s",
243+
window_name,
244+
type(buffer_name)
245+
)
246+
)
247+
return fallback
248+
end
249+
198250
--- Sets the buffer name based on header text and tab count
199251
--- @param bufnr integer Buffer number
200252
--- @param header_text string|nil Resolved header text
201253
--- @param tab_page_id integer Tab page ID for suffix
202-
local function set_buffer_name(bufnr, header_text, tab_page_id)
203-
if not header_text or header_text == "" then
254+
--- @param window_name string Window name for Config.windows[name].buffer_name lookup
255+
--- @param header_parts agentic.ui.ChatWidget.HeaderParts Header parts for function-type buffer_name
256+
local function set_buffer_name(
257+
bufnr,
258+
header_text,
259+
tab_page_id,
260+
window_name,
261+
header_parts
262+
)
263+
local name = resolve_buffer_name(window_name, header_parts, header_text)
264+
if not name or name == "" then
204265
return
205266
end
206267

@@ -210,9 +271,9 @@ local function set_buffer_name(bufnr, header_text, tab_page_id)
210271
--- @type string|nil
211272
local buf_name
212273
if total_tabs > 1 then
213-
buf_name = string.format("%s (Tab %d)", header_text, tab_page_id)
274+
buf_name = string.format("%s (Tab %d)", name, tab_page_id)
214275
else
215-
buf_name = header_text
276+
buf_name = name
216277
end
217278

218279
vim.api.nvim_buf_set_name(bufnr, buf_name)
@@ -263,7 +324,13 @@ function WindowDecoration.render_header(bufnr, window_name, context)
263324
local text = (header_text and header_text ~= "") and header_text or ""
264325

265326
set_winbar(winid, text)
266-
set_buffer_name(bufnr, header_text, tab_page_id)
327+
set_buffer_name(
328+
bufnr,
329+
header_text,
330+
tab_page_id,
331+
window_name,
332+
dynamic_header
333+
)
267334
end)
268335
end
269336

tests/integration/test_buffer_naming.lua

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,47 @@ end)()
7878
assert.equal(5, session_count)
7979
end)
8080

81+
it("uses custom buffer_name from windows config when set", function()
82+
child.lua([[
83+
require("agentic").setup({ windows = { chat = { buffer_name = "My Chat" } } })
84+
require("agentic").toggle()
85+
]])
86+
child.flush()
87+
88+
local basename = get_panel_basename("chat")
89+
assert.is_true(vim.startswith(basename, "My Chat"))
90+
end)
91+
92+
it("uses buffer_name function to derive name from header parts", function()
93+
child.lua([[
94+
require("agentic").setup({
95+
windows = {
96+
chat = {
97+
buffer_name = function(parts)
98+
return "Custom: " .. parts.title
99+
end,
100+
},
101+
},
102+
})
103+
require("agentic").toggle()
104+
]])
105+
child.flush()
106+
107+
local basename = get_panel_basename("chat")
108+
assert.is_true(vim.startswith(basename, "Custom: 󰻞 Agentic Chat"))
109+
end)
110+
111+
it("falls back to header title when buffer_name not set", function()
112+
child.lua([[
113+
require("agentic").setup({})
114+
require("agentic").toggle()
115+
]])
116+
child.flush()
117+
118+
local basename = get_panel_basename("chat")
119+
assert.is_true(vim.startswith(basename, "󰻞 Agentic Chat"))
120+
end)
121+
81122
it("each panel has distinct buffer name prefix", function()
82123
child.lua([[ require("agentic").toggle() ]])
83124
child.flush()

0 commit comments

Comments
 (0)