diff --git a/README.md b/README.md index 02f489dd..79af67e6 100644 --- a/README.md +++ b/README.md @@ -470,6 +470,47 @@ header parts: } ``` +### Customizing Buffer Names + +By default, buffer names mirror the window header titles. You can override them +independently via the `buffer_name` field in each window's config, for example +to show cleaner names in your statusline or buffer switcher: + +```lua +{ + "carlos-algms/agentic.nvim", + opts = { + windows = { + chat = { buffer_name = "Agentic Chat" }, + input = { buffer_name = "Agentic Prompt" }, + code = { buffer_name = "Code Snippets" }, + files = { buffer_name = "Files" }, + diagnostics = { buffer_name = "Diagnostics" }, + todos = { buffer_name = "Tasks" }, + }, + }, +} +``` + +You can also use a function that receives the header parts and returns a string: + +```lua +{ + "carlos-algms/agentic.nvim", + opts = { + windows = { + chat = { + buffer_name = function(parts) + return "AI: " .. parts.title + end, + }, + }, + }, +} +``` + +When a panel has no `buffer_name` set, it falls back to the header title. + ### Folding Completed tool call outputs are automatically folded to keep the chat buffer diff --git a/doc/agentic.txt b/doc/agentic.txt index ad6c4f67..3f2c793c 100644 --- a/doc/agentic.txt +++ b/doc/agentic.txt @@ -303,6 +303,10 @@ windows ~ `height` Height when position is `"bottom"`. String percentage or number. Default: `"30%"`. `stack_width_ratio` Ratio for stacked panels. Default: `0.4`. + `buffer_name` Name of the window buffer. String title + or function returning string title. + Default: window header title. + See |agentic-config-headers|. Sub-panel options (each accepts `win_opts` table): diff --git a/lua/agentic/config_default.lua b/lua/agentic/config_default.lua index 223cff70..49f60c21 100644 --- a/lua/agentic/config_default.lua +++ b/lua/agentic/config_default.lua @@ -85,26 +85,34 @@ --- Overrides default options (wrap, linebreak, winfixheight) --- @alias agentic.UserConfig.WinOpts table +--- @alias agentic.UserConfig.BufferNameFn fun(header: agentic.ui.ChatWidget.HeaderParts): string|nil + --- @class agentic.UserConfig.Windows.Chat +--- @field buffer_name? string|agentic.UserConfig.BufferNameFn --- @field win_opts? agentic.UserConfig.WinOpts --- @class agentic.UserConfig.Windows.Input +--- @field buffer_name? string|agentic.UserConfig.BufferNameFn --- @field height number --- @field win_opts? agentic.UserConfig.WinOpts --- @class agentic.UserConfig.Windows.Code +--- @field buffer_name? string|agentic.UserConfig.BufferNameFn --- @field max_height number --- @field win_opts? agentic.UserConfig.WinOpts --- @class agentic.UserConfig.Windows.Files +--- @field buffer_name? string|agentic.UserConfig.BufferNameFn --- @field max_height number --- @field win_opts? agentic.UserConfig.WinOpts --- @class agentic.UserConfig.Windows.Diagnostics +--- @field buffer_name? string|agentic.UserConfig.BufferNameFn --- @field max_height number --- @field win_opts? agentic.UserConfig.WinOpts --- @class agentic.UserConfig.Windows.Todos +--- @field buffer_name? string|agentic.UserConfig.BufferNameFn --- @field display boolean --- @field max_height number --- @field win_opts? agentic.UserConfig.WinOpts diff --git a/lua/agentic/ui/window_decoration.lua b/lua/agentic/ui/window_decoration.lua index 1ca6f072..8a8419fd 100644 --- a/lua/agentic/ui/window_decoration.lua +++ b/lua/agentic/ui/window_decoration.lua @@ -251,12 +251,76 @@ function WindowDecoration._set_buffer_name(bufnr, buf_name) vim.api.nvim_buf_set_name(bufnr, buf_name) end +--- Resolves the buffer name from config, supporting string or function values +--- @param window_name string Window name for Config.windows[name].buffer_name lookup +--- @param header_parts agentic.ui.ChatWidget.HeaderParts Header parts passed to function-type buffer_name +--- @param fallback string|nil Fallback name (resolved header text) when buffer_name is not set +--- @return string|nil name +local function resolve_buffer_name(window_name, header_parts, fallback) + local win_cfg = Config.windows[window_name] + local buffer_name = win_cfg and win_cfg.buffer_name + + if buffer_name == nil then + return fallback + end + + if type(buffer_name) == "string" then + return buffer_name + end + + if type(buffer_name) == "function" then + local ok, result = pcall(buffer_name, header_parts) + if not ok then + Logger.notify( + string.format( + "Error in buffer_name function for '%s': %s", + window_name, + result + ) + ) + return fallback + end + if result == nil then + return fallback + end + if type(result) ~= "string" then + Logger.notify( + string.format( + "buffer_name function for '%s' must return string|nil, got %s", + window_name, + type(result) + ) + ) + return fallback + end + return result + end + + Logger.notify( + string.format( + "buffer_name for '%s' must be string|function|nil, got %s", + window_name, + type(buffer_name) + ) + ) + return fallback +end + --- Sets the buffer name based on header text and tab count --- @param bufnr integer Buffer number --- @param header_text string|nil Resolved header text --- @param tab_page_id integer Tab page ID for suffix -local function set_buffer_name(bufnr, header_text, tab_page_id) - if not header_text or header_text == "" then +--- @param window_name string Window name for Config.windows[name].buffer_name lookup +--- @param header_parts agentic.ui.ChatWidget.HeaderParts Header parts for function-type buffer_name +local function set_buffer_name( + bufnr, + header_text, + tab_page_id, + window_name, + header_parts +) + local name = resolve_buffer_name(window_name, header_parts, header_text) + if not name or name == "" then return end @@ -266,9 +330,9 @@ local function set_buffer_name(bufnr, header_text, tab_page_id) --- @type string local buf_name if total_tabs > 1 then - buf_name = string.format("%s (Tab %d)", header_text, tab_page_id) + buf_name = string.format("%s (Tab %d)", name, tab_page_id) else - buf_name = header_text + buf_name = name end WindowDecoration._set_buffer_name(bufnr, buf_name) @@ -319,7 +383,13 @@ function WindowDecoration.render_header(bufnr, window_name, context) local text = (header_text and header_text ~= "") and header_text or "" set_winbar(winid, text) - set_buffer_name(bufnr, header_text, tab_page_id) + set_buffer_name( + bufnr, + header_text, + tab_page_id, + window_name, + dynamic_header + ) end) end diff --git a/tests/integration/test_buffer_naming.lua b/tests/integration/test_buffer_naming.lua index c87c1d8d..e0de1d93 100644 --- a/tests/integration/test_buffer_naming.lua +++ b/tests/integration/test_buffer_naming.lua @@ -78,6 +78,111 @@ end)() assert.equal(5, session_count) end) + it("uses custom buffer_name from windows config when set", function() + child.lua([[ + require("agentic").setup({ windows = { chat = { buffer_name = "My Chat" } } }) + require("agentic").toggle() + ]]) + child.flush() + + local basename = get_panel_basename("chat") + assert.is_true(vim.startswith(basename, "My Chat")) + end) + + it("uses buffer_name function to derive name from header parts", function() + child.lua([[ + require("agentic").setup({ + windows = { + chat = { + buffer_name = function(parts) + return "Custom: " .. parts.title + end, + }, + }, + }) + require("agentic").toggle() + ]]) + child.flush() + + local basename = get_panel_basename("chat") + assert.is_true(vim.startswith(basename, "Custom: 󰻞 Agentic Chat")) + end) + + it("falls back to header title when buffer_name not set", function() + child.lua([[ + require("agentic").setup({}) + require("agentic").toggle() + ]]) + child.flush() + + local basename = get_panel_basename("chat") + assert.is_true(vim.startswith(basename, "󰻞 Agentic Chat")) + end) + + it("falls back to header title when buffer_name function throws", function() + child.lua([[ + require("agentic").setup({ + windows = { + chat = { + buffer_name = function() + error("intentional error") + end, + }, + }, + }) + require("agentic").toggle() + ]]) + child.flush() + + local basename = get_panel_basename("chat") + assert.is_true(vim.startswith(basename, "󰻞 Agentic Chat")) + end) + + it( + "falls back to header title when buffer_name function returns nil", + function() + child.lua([[ + require("agentic").setup({ + windows = { + chat = { + buffer_name = function() + return nil + end, + }, + }, + }) + require("agentic").toggle() + ]]) + child.flush() + + local basename = get_panel_basename("chat") + assert.is_true(vim.startswith(basename, "󰻞 Agentic Chat")) + end + ) + + it( + "assigns unique names when two panels share the same buffer_name", + function() + child.lua([[ + require("agentic").setup({ + windows = { + chat = { buffer_name = "Shared Name" }, + input = { buffer_name = "Shared Name" }, + }, + }) + require("agentic").toggle() + ]]) + child.flush() + + local chat_basename = get_panel_basename("chat") + local input_basename = get_panel_basename("input") + + assert.is_true(vim.startswith(chat_basename, "Shared Name")) + assert.is_true(vim.startswith(input_basename, "Shared Name")) + assert.is_not.equal(chat_basename, input_basename) + end + ) + it("each panel has distinct buffer name prefix", function() child.lua([[ require("agentic").toggle() ]]) child.flush()