Configuring the Chat Buffer
The chat buffer is where you converse with an LLM or an agent. Its settings live under interactions.chat, and every option is listed in config.lua.
Changing Adapter
The chat buffer uses the copilot adapter by default. To use another HTTP or ACP adapter:
require("codecompanion").setup({
interactions = {
chat = {
adapter = {
name = "anthropic",
model = "claude-haiku-4-5-20251001",
},
},
},
})See ACP adapters and HTTP adapters for the full list.
Completion
CodeCompanion uses blink.cmp, nvim-cmp or coc.nvim, in that order, if one is installed. Otherwise, it falls back to Neovim's native completion. You can override this with:
require("codecompanion").setup({
interactions = {
chat = {
opts = {
completion_provider = "blink", -- Can be "blink", "cmp", "coc" or "default"
},
},
},
})Triggers
The characters that open completion for editor context, slash commands, tools and ACP commands can be changed with:
require("codecompanion").setup({
opts = {
triggers = {
acp_slash_commands = "\\",
editor_context = "#",
slash_commands = "/",
tools = "@",
},
},
})Context Formatters
Some files are a poor fit for an LLM as they are. A Jupyter Notebook is a large JSON document with markdown, code and base64 images embedded in it, which fills the context window quickly. A context formatter changes a file's content before the LLM sees it.
Formatters are keyed by file extension, and apply whether the file is added with /file, /buffer, editor context, a rules file or a sync. CodeCompanion ships one for ipynb files.
A formatter is a function that takes the raw content and the path, and returns the content the LLM should see. It can also be the path to a module that returns a table with a format function:
require("codecompanion").setup({
context = {
formatters = {
sqlite = function(raw, path)
return vim.fn.system({ "sqlite3", path, ".schema" })
end,
},
},
})require("codecompanion").setup({
context = {
formatters = {
sqlite = "my_plugin.context.formatters.sqlite",
},
},
})CodeCompanion passes a formatter's output through as it is. Content without a formatter is wrapped in a code block, and buffers also get line numbers. Neither applies to a sync diff, which is already fenced. If a formatter errors or doesn't return a string, the raw content is used instead.
Editor Context
Editor context, such as #{buffer}, shares part of your Neovim session with the LLM. It's configured under interactions.shared.editor_context, as it's shared with the CLI interaction.
To add your own, give it a callback that returns the content to send:
require("codecompanion").setup({
interactions = {
shared = {
editor_context = {
["git_branch"] = {
description = "Share the current git branch",
callback = function()
return "The current git branch is " .. vim.fn.system("git branch --show-current")
end,
opts = {
contains_code = false,
},
},
},
},
},
})For more control, swap callback for path, pointing to a module built like those in lua/codecompanion/interactions/shared/editor_context/.
Keymaps
Keymaps only apply to the chat buffer. You can change any of the defaults, and set opts to pass extra :map-arguments to vim.keymap.set:
require("codecompanion").setup({
interactions = {
chat = {
keymaps = {
send = {
modes = { n = "<C-s>", i = "<C-s>" },
opts = {},
},
close = {
modes = { n = "<C-c>", i = "<C-c>" },
opts = {},
},
},
},
},
})require("codecompanion").setup({
interactions = {
inline = {
keymaps = {
stop = {
callback = "keymaps.stop",
description = "Stop request",
modes = { n = "q" },
},
},
},
},
})require("codecompanion").setup({
interactions = {
shared = {
keymaps = {
always_accept = {
callback = "keymaps.always_accept",
modes = { n = "g1" },
},
accept_change = {
callback = "keymaps.accept_change",
modes = { n = "g2" },
},
reject_change = {
callback = "keymaps.reject_change",
modes = { n = "g3" },
},
next_hunk = {
callback = "keymaps.next_hunk",
modes = { n = "}" },
},
previous_hunk = {
callback = "keymaps.previous_hunk",
modes = { n = "{" },
},
},
},
},
})The keymaps under interactions.shared apply to diffs and tool approvals in both the chat buffer and the inline interaction. They also include view_diff (gv) and cancel (g4).
To disable a keymap, set it to false:
require("codecompanion").setup({
interactions = {
chat = {
keymaps = {
send = false,
close = false,
},
},
},
})Prompt Decorator
A prompt decorator changes your message before it's sent to the LLM. The GitHub Copilot prompt in VS Code, for example, wraps the user's message in <prompt></prompt> tags to separate it from the rest of the context. To do the same:
require("codecompanion").setup({
interactions = {
chat = {
opts = {
---@param message string
---@param adapter CodeCompanion.HTTPAdapter|CodeCompanion.ACPAdapter
---@param context CodeCompanion.BufferContext
---@return string
prompt_decorator = function(message, adapter, context)
return string.format([[<prompt>%s</prompt>]], message)
end,
},
},
},
})The adapter is a copy of the chat buffer's adapter, and context describes the buffer the chat was opened from. It's refreshed each time you toggle the chat buffer. Regenerating a response skips the decorator.
Slash Commands
Slash commands add context to the chat buffer, such as a file's contents or the date. Each has its own options, so check config.lua for the full list.
Commands that open a picker use the first of Telescope, fzf-lua, mini.pick or Snacks that's installed. Otherwise, they fall back to a built-in default picker.
require("codecompanion").setup({
interactions = {
chat = {
slash_commands = {
["file"] = {
opts = {
provider = "telescope", -- Can be "default", "telescope", "fzf_lua", "mini_pick" or "snacks"
},
},
},
},
},
})require("codecompanion").setup({
interactions = {
chat = {
slash_commands = {
["file"] = {
keymaps = {
modes = {
i = "<C-f>",
n = { "<C-f>", "gf" },
},
},
},
},
},
},
})require("codecompanion").setup({
interactions = {
chat = {
slash_commands = {
["file-from-url"] = {
---@param opts { adapter: CodeCompanion.HTTPAdapter|CodeCompanion.ACPAdapter }
---@return boolean
enabled = function(opts)
return opts.adapter.opts and opts.adapter.opts.vision == true
end,
},
},
},
},
})require("codecompanion").setup({
interactions = {
chat = {
slash_commands = {
["git_files"] = {
description = "List git files",
---@param chat CodeCompanion.Chat
callback = function(chat)
local handle = io.popen("git ls-files")
if handle ~= nil then
local result = handle:read("*a")
handle:close()
chat:add_context({ role = "user", content = result }, { source = "git", id = "<git_files>" })
else
return vim.notify("No git files available", vim.log.levels.INFO, { title = "CodeCompanion" })
end
end,
opts = {
contains_code = false,
},
},
},
},
},
})Credit to @lazymaniac for the inspiration for the custom slash command example.
Syncing
A context item is a snapshot of a buffer or file at the time it was added. Syncing shares its latest content with the LLM on every turn, either in full or as a diff.
#{buffer} syncs by sending a diff. To send the whole buffer instead:
require("codecompanion").setup({
interactions = {
shared = {
editor_context = {
["buffer"] = {
opts = {
default_params = "all", -- Can be "all" or "diff"
},
},
},
},
},
})Some file types are worth syncing as soon as they're added. Extensions listed in sync_diff are synced as a diff whether they're added with /file, /buffer, #{buffer} or #{buffers}:
require("codecompanion").setup({
interactions = {
chat = {
opts = {
sync_diff = {
ipynb = true,
sqlite = true,
},
},
},
},
})Jupyter Notebooks change on disk every time a cell runs, so ipynb is listed by default. To change how a file's content looks to the LLM, see Context Formatters.