Skip to content

Commit ab14cde

Browse files
committed
feat: add codecompanion to provide AI chat functionality
1 parent 38ac084 commit ab14cde

File tree

14 files changed

+274
-25
lines changed

14 files changed

+274
-25
lines changed

.github/workflows/lint_code.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ jobs:
77
- uses: actions/checkout@v4
88
- uses: lunarmodules/luacheck@v1
99
with:
10-
args: . --std luajit --max-line-length 150 --no-config --globals vim _debugging _command_panel _flash_esc_or_noh _telescope_collections _toggle_inlayhint _toggle_virtualtext _toggle_lazygit _toggle_yazi _toggle_btop
10+
args: . --std luajit --max-line-length 150 --no-config --globals vim _debugging _command_panel _flash_esc_or_noh _telescope_collections _toggle_inlayhint _toggle_virtualtext _toggle_lazygit _toggle_yazi _toggle_btop _select_chat_model

lua/core/settings.lua

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,40 @@ settings["dashboard_image"] = {
231231
[[⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠿⠿⢿⠿⠷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀]],
232232
}
233233

234+
-- Set it to false if you don't use AI chat functionality.
235+
---@type boolean
236+
settings["use_chat"] = true
237+
238+
-- Set the language to use for AI chat response here.
239+
--- @type string
240+
settings["chat_lang"] = "English"
241+
242+
-- Set environment variable here to read API key for AI chat.
243+
-- or you can set it to a command that reads the API key from your password manager.
244+
-- e.g. "cmd:op read op://personal/OpenAI/credential --no-new
245+
--- @type string
246+
settings["chat_api_key"] = "CODE_COMPANION_KEY"
247+
248+
-- Set the chat models here and use the first entry as default model.
249+
-- We use `openrouter` as the chat model provider by default (No vested interest).
250+
-- You need to register an account on openrouter and generate an api key.
251+
-- We read the api key by reading the env variable: `CODE_COMPANION_KEY`.
252+
-- All available models can be found here: https://openrouter.ai/models.
253+
--- @type string[]
254+
settings["chat_models"] = {
255+
-- free models
256+
"mistralai/devstral-small:free", -- default
257+
-- "qwen/qwen-2.5-coder-32b-instruct:free",
258+
-- "deepseek/deepseek-chat-v3-0324:free",
259+
-- "deepseek/deepseek-r1:free",
260+
"google/gemma-3-27b-it:free",
261+
-- paid models
262+
"openai/codex-mini",
263+
"openai/gpt-4.1-mini",
264+
"google/gemini-2.0-flash-001",
265+
"google/gemini-2.5-flash-preview-05-20",
266+
"anthropic/claude-3.7-sonnet",
267+
"anthropic/claude-sonnet-4",
268+
}
269+
234270
return require("modules.utils").extend_config(settings, "user.settings")

lua/keymap/helpers.lua

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,35 @@ _G._toggle_virtualtext = function()
6969
end
7070
end
7171

72+
_G._select_chat_model = function()
73+
local actions = require("telescope.actions")
74+
local action_state = require("telescope.actions.state")
75+
local finder = require("telescope.finders")
76+
local pickers = require("telescope.pickers")
77+
local type = require("telescope.themes").get_dropdown()
78+
local conf = require("telescope.config").values
79+
local models = require("core.settings").chat_models
80+
local current_model = models[1]
81+
82+
pickers
83+
.new(type, {
84+
prompt_title = "(CodeCompanion) Select Model",
85+
finder = finder.new_table({ results = models }),
86+
sorter = conf.generic_sorter(type),
87+
attach_mappings = function(bufnr)
88+
actions.select_default:replace(function()
89+
actions.close(bufnr)
90+
current_model = action_state.get_selected_entry()[1]
91+
vim.g.current_chat_model = current_model
92+
vim.notify("Model selected: " .. current_model, vim.log.levels.INFO, { title = "CodeCompanion" })
93+
end)
94+
95+
return true
96+
end,
97+
})
98+
:find()
99+
end
100+
72101
---@param program string
73102
local function not_found_notify(program)
74103
vim.notify(string.format("[%s] not found!", program), vim.log.levels.ERROR, { title = "toggleterm.nvim" })

lua/keymap/tool.lua

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,28 @@ local mappings = {
279279
:with_noremap()
280280
:with_silent()
281281
:with_desc("debug: Open REPL"),
282+
283+
--- Plugin: CodeCompanion and edgy
284+
["n|<leader>cs"] = map_callback(function()
285+
_select_chat_model()
286+
end)
287+
:with_noremap()
288+
:with_silent()
289+
:with_desc("tool: Select Chat Model"),
290+
["nv|<leader>cc"] = map_callback(function()
291+
require("edgy").toggle("right")
292+
end)
293+
:with_noremap()
294+
:with_silent()
295+
:with_desc("tool: Toggle CodeCompanion"),
296+
["nv|<leader>ck"] = map_cr("CodeCompanionActions")
297+
:with_noremap()
298+
:with_silent()
299+
:with_desc("tool: CodeCompanion Actions"),
300+
["v|<leader>ca"] = map_cr("CodeCompanionChat Add")
301+
:with_noremap()
302+
:with_silent()
303+
:with_desc("tool: Add selection to CodeCompanion Chat"),
282304
},
283305
}
284306

lua/modules/configs/completion/cmp.lua

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ return function()
3434
and {
3535
require("copilot_cmp.comparators").prioritize,
3636
require("copilot_cmp.comparators").score,
37-
-- require("cmp_tabnine.compare"),
3837
compare.offset, -- Items closer to cursor will have lower priority
3938
compare.exact,
4039
-- compare.scopes,
@@ -49,7 +48,6 @@ return function()
4948
compare.order,
5049
}
5150
or {
52-
-- require("cmp_tabnine.compare"),
5351
compare.offset, -- Items closer to cursor will have lower priority
5452
compare.exact,
5553
-- compare.scopes,
@@ -92,8 +90,6 @@ return function()
9290

9391
-- set up labels for completion entries
9492
vim_item.menu = setmetatable({
95-
cmp_tabnine = "[TN]",
96-
codeium = "[CODEIUM]",
9793
copilot = "[CPLT]",
9894
buffer = "[BUF]",
9995
orgmode = "[ORG]",
@@ -190,8 +186,6 @@ return function()
190186
},
191187
{ name = "latex_symbols" },
192188
{ name = "copilot" },
193-
-- { name = "codeium" },
194-
-- { name = "cmp_tabnine" },
195189
},
196190
-- experimental = {
197191
-- ghost_text = {

lua/modules/configs/completion/codeium.lua

Lines changed: 0 additions & 3 deletions
This file was deleted.

lua/modules/configs/completion/tabnine.lua

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
return function()
2+
local icons = { aichat = require("modules.utils.icons").get("aichat", true) }
3+
local secret_key = require("core.settings").chat_api_key
4+
local chat_lang = require("core.settings").chat_lang
5+
local models = require("core.settings").chat_models
6+
local current_model = models[1]
7+
vim.g.current_chat_model = current_model
8+
9+
require("modules.utils").load_plugin("codecompanion", {
10+
opts = {
11+
language = chat_lang,
12+
},
13+
strategies = {
14+
chat = {
15+
adapter = "openrouter",
16+
roles = {
17+
llm = function(adapter)
18+
return icons.aichat.Copilot .. "CodeCompanion (" .. adapter.formatted_name .. ")"
19+
end,
20+
user = icons.aichat.Me .. "Me",
21+
},
22+
keymaps = {
23+
submit = {
24+
modes = { n = "<CR>" },
25+
description = "Submit",
26+
callback = function(chat)
27+
chat:apply_model(current_model)
28+
chat:submit()
29+
end,
30+
},
31+
},
32+
},
33+
inline = {
34+
adapter = "openrouter",
35+
},
36+
},
37+
adapters = {
38+
openrouter = function()
39+
return require("codecompanion.adapters").extend("openai_compatible", {
40+
env = {
41+
url = "https://openrouter.ai/api",
42+
api_key = secret_key,
43+
chat_url = "/v1/chat/completions",
44+
},
45+
schema = {
46+
model = {
47+
default = vim.g.current_chat_model,
48+
},
49+
},
50+
})
51+
end,
52+
},
53+
display = {
54+
diff = {
55+
enabled = true,
56+
close_chat_at = 240, -- Close an open chat buffer if the total columns of your display are less than...
57+
layout = "vertical", -- vertical|horizontal split for default provider
58+
opts = { "internal", "filler", "closeoff", "algorithm:patience", "followwrap", "linematch:120" },
59+
provider = "default", -- default|mini_diff
60+
},
61+
chat = {
62+
window = {
63+
layout = "vertical", -- float|vertical|horizontal|buffer
64+
position = "right", -- left|right|top|bottom (nil will default depending on vim.opt.plitright|vim.opt.splitbelow)
65+
border = "single",
66+
width = 0.25,
67+
relative = "editor",
68+
full_height = true, -- when set to false, vsplit will be used to open the chat buffer vs. botright/topleft vsplit
69+
},
70+
},
71+
},
72+
extensions = {
73+
history = {
74+
enabled = true,
75+
opts = {
76+
-- Keymap to open history from chat buffer (default: gh)
77+
keymap = "gh",
78+
-- Automatically generate titles for new chats
79+
auto_generate_title = true,
80+
---On exiting and entering neovim, loads the last chat on opening chat
81+
continue_last_chat = false,
82+
---When chat is cleared with `gx` delete the chat from history
83+
delete_on_clearing_chat = false,
84+
-- Picker interface ("telescope", "snacks" or "default")
85+
picker = "telescope",
86+
---Enable detailed logging for history extension
87+
enable_logging = false,
88+
---Directory path to save the chats
89+
dir_to_save = vim.fn.stdpath("data") .. "/codecompanion-history",
90+
-- Save all chats by default
91+
auto_save = true,
92+
-- Keymap to save the current chat manually
93+
save_chat_keymap = "sc",
94+
-- Number of days after which chats are automatically deleted (0 to disable)
95+
expiration_days = 0,
96+
},
97+
},
98+
},
99+
})
100+
end

lua/modules/configs/tool/which-key.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ return function()
44
misc = require("modules.utils.icons").get("misc"),
55
git = require("modules.utils.icons").get("git", true),
66
cmp = require("modules.utils.icons").get("cmp", true),
7+
aichat = require("modules.utils.icons").get("aichat", true),
78
}
89

910
require("modules.utils").load_plugin("which-key", {
@@ -70,6 +71,7 @@ return function()
7071
{ "<leader>l", group = icons.misc.LspAvailable .. " Lsp" },
7172
{ "<leader>f", group = icons.ui.Telescope .. " Fuzzy Find" },
7273
{ "<leader>n", group = icons.ui.FolderOpen .. " Nvim Tree" },
74+
{ "<leader>c", group = icons.aichat.Chat .. " Chat" },
7375
},
7476
})
7577
end
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
local M = require("lualine.component"):extend()
2+
local icons = { aichat = require("modules.utils.icons").get("aichat", true) }
3+
4+
M.processing = false
5+
M.spinner_index = 1
6+
7+
local spinners = {
8+
"",
9+
"",
10+
"",
11+
"",
12+
"",
13+
"",
14+
"",
15+
"",
16+
"",
17+
"",
18+
}
19+
20+
-- Initializer
21+
function M:init(options)
22+
M.super.init(self, options)
23+
24+
local group = vim.api.nvim_create_augroup("CodeCompanionHooks", {})
25+
26+
vim.api.nvim_create_autocmd({ "User" }, {
27+
pattern = "CodeCompanionRequest*",
28+
group = group,
29+
callback = function(request)
30+
if request.match == "CodeCompanionRequestStarted" then
31+
self.processing = true
32+
elseif request.match == "CodeCompanionRequestFinished" then
33+
self.processing = false
34+
end
35+
end,
36+
})
37+
end
38+
39+
-- Function that runs every time statusline is updated
40+
function M:update_status()
41+
if self.processing then
42+
self.spinner_index = (self.spinner_index % #spinners) + 1
43+
return string.format("%s %s", icons.aichat.Copilot, spinners[self.spinner_index])
44+
else
45+
return nil
46+
end
47+
end
48+
49+
return M

0 commit comments

Comments
 (0)