Configuring the Chat Buffer
By default, CodeCompanion provides a chat interaction that uses a dedicated Neovim buffer for conversational interaction with your chosen LLM. This buffer can be customized according to your preferences.
Please refer to the config.lua file for a full list of all configuration options.
Changing Adapter
By default, CodeCompanion sets the copilot adapter for the chat interaction. You can change this to be a ACP or HTTP adapter:
require("codecompanion").setup({
interactions = {
chat = {
adapter = {
name = "anthropic",
model = "claude-haiku-4-5-20251001"
},
},
},
})See the section on ACP and HTTP for more information.
Completion
By default, CodeCompanion will determine if you have one of blink.cmp, nvim-cmp, or coc.nvim installed, selecting it as the default provider. Failing this, the default completion engine will be used.
You can override this with:
require("codecompanion").setup({
interactions = {
chat = {
opts = {
completion_provider = "blink", -- blink|cmp|coc|default
}
}
}
})Prefixes
You can also customize the prefixes that trigger completions for editor context, slash commands, and tools:
require("codecompanion").setup({
opts = {
triggers = {
acp_slash_commands = "\\",
editor_context = "#",
slash_commands = "/",
tools = "@",
},
},
})Context Formatters
You can customise how a buffer and file's content is shared with an LLM with context formatters.
Example: A Jupyter Notebook is a large JSON document with markdown, code and sometimes base64 images embedded in it. They ca be large files which quickly erode an LLM's context window.
A context formatter modifies a file's content before it is shared with an LLM. This is the case whether the file was attached with /file, opened as a buffer and attached with /buffer, pulled in by a rules file, or re-read to produce a sync diff.
You can define your own formatter by ensuring your you implement a format(raw, path) function which returns the content the LLM should see, or the path to a module which returns one:
require("codecompanion").setup({
context = {
formatters = {
sqlite = function(raw, path)
-- Return the content the LLM should see for this file
end,
},
},
})require("codecompanion").setup({
context = {
formatters = {
-- The path to any module, or file, which returns a table with a `format` function.
sqlite = "my_plugin.context.formatters.sqlite",
},
},
})Formatters are responsible for their own formatting, so content they return is passed through as-is. Content they do not touch is wrapped in a code fence when attached to the chat, and buffers additionally get line numbers. Neither is applied when content is re-read for a sync diff, as the diff itself is fenced.
Editor Context
Editor context can be inserted into the chat buffer using # (by default). It provides contextual code or information about the current Neovim state. For instance, the built-in #{buffer} editor context sends the current buffer’s contents to the LLM.
You can even define your own context:
require("codecompanion").setup({
interactions = {
chat = {
editor_context = {
["my_editor_context_item"] = {
---Ensure the file matches the CodeCompanion.EditorContext class
---@return string|fun(): nil
callback = "/Users/Oli/Code/my_editor_context_item.lua",
description = "Explain what your does",
opts = {
contains_code = false,
--has_params = true, -- Set this if your editor context item supports parameters
--default_params = nil, -- Set default parameters
},
},
},
},
},
})Syncing
Neovim buffers can be synced with the chat buffer. That is, on each turn their content can be shared with the LLM. This is useful if you're modifying a buffer and want the LLM to always have the latest changes.
For the built-in #buffer editor context, this is enabled by default. However, you can change it with:
require("codecompanion").setup({
interactions = {
chat = {
editor_context = {
["buffer"] = {
opts = {
-- Always sync the buffer by sharing its "diff"
-- Or choose "all" to share the entire buffer
default_params = "all",
},
},
},
},
},
})Keymaps
NOTE
The plugin scopes CodeCompanion specific keymaps to the chat buffer only.
You can define or override the default keymaps to send messages, regenerate responses, close the buffer, etc.
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 = "{" },
},
},
},
},
})For the chat interaction, the keymaps are mapped to <C-s> for sending a message and <C-c> for closing in both normal and insert modes. To set other :map-arguments, you can use the optional opts table which will be fed to vim.keymap.set.
To disable a keymap, you can set it to false in your configuration:
require("codecompanion").setup({
interactions = {
chat = {
keymaps = {
send = false,
close = false
}
}
}
})Prompt Decorator
It can be useful to decorate your prompt with additional information, prior to sending to an LLM. For example, the GitHub Copilot prompt in VS Code, wraps a user's prompt between <prompt></prompt> tags, presumably to differentiate the user's ask from additional context. This can also be achieved in CodeCompanion:
require("codecompanion").setup({
interactions = {
chat = {
opts = {
---Decorate the user message before it's sent to the LLM
---@param message string
---@param adapter CodeCompanion.Adapter
---@param context table
---@return string
prompt_decorator = function(message, adapter, context)
return string.format([[<prompt>%s</prompt>]], message)
end,
}
}
}
})The decorator function also has access to the adapter in the chat buffer alongside the context table (which refreshes when a user toggles the chat buffer).
Slash Commands
IMPORTANT
Each slash command may have their own unique configuration so be sure to check out the config.lua file
Slash Commands (invoked with / by default) let you dynamically insert context into the chat buffer, such as file contents or date/time.
The plugin supports providers like telescope, mini_pick, fzf_lua and snacks.nvim. By default, the plugin will automatically detect if you have any of those plugins installed and duly set them as the default provider. Failing that, the in-built default provider will be used. Please see the Chat Buffer usage section for information on how to use Slash Commands.
require("codecompanion").setup({
interactions = {
chat = {
slash_commands = {
["file"] = {
-- Use Telescope as the provider for the /file command
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 = {
["image"] = {
---@param opts { adapter: CodeCompanion.HTTPAdapter }
---@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 }, "git", "<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 Buffers/Files
Context items hold the data of a file or buffer at a point in time.
Depending on the file type, it may be worthwhile continuously syncing their content with an LLM. Extensions listed in sync_diff are watched from the moment they're added to the chat buffer, whether that's with /file, /buffer, #{buffer} or #{buffers}:
require("codecompanion").setup({
interactions = {
chat = {
opts = {
sync_diff = {
ipynb = true, -- Notebooks change on disk whenever a cell is run
sqlite = true,
},
},
},
},
})To change how a file's content is shaped before the LLM sees it, see Context Formatters.