-
Notifications
You must be signed in to change notification settings - Fork 0
feat(neovim): add opencode.nvim for AI assistant integration #575
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,70 @@ | ||||||||||||||||||||||||||||||
| -- Integrates an AI workspace with chat, prompts, and actions inside Neovim. | ||||||||||||||||||||||||||||||
| -- AI integration for Neovim | ||||||||||||||||||||||||||||||
| -- Includes sidekick.nvim and opencode.nvim (opencode AI assistant integration) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| -- ==================================================================================== | ||||||||||||||||||||||||||||||
| -- SIDEKICK (AI workspace with chat, prompts, and actions) | ||||||||||||||||||||||||||||||
| -- From: https://github.com/folke/sidekick.nvim | ||||||||||||||||||||||||||||||
| -- ==================================================================================== | ||||||||||||||||||||||||||||||
| require("sidekick").setup({}) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| -- ==================================================================================== | ||||||||||||||||||||||||||||||
| -- SNACKS (Required for opencode.nvim) | ||||||||||||||||||||||||||||||
| -- From: https://github.com/folke/snacks.nvim | ||||||||||||||||||||||||||||||
| -- ==================================================================================== | ||||||||||||||||||||||||||||||
| require("snacks").setup({ | ||||||||||||||||||||||||||||||
| input = {}, | ||||||||||||||||||||||||||||||
| picker = {}, | ||||||||||||||||||||||||||||||
| terminal = {}, | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| -- ==================================================================================== | ||||||||||||||||||||||||||||||
| -- OPENCODE.NVIM (opencode AI assistant integration) | ||||||||||||||||||||||||||||||
| -- From: https://github.com/NickvanDyke/opencode.nvim | ||||||||||||||||||||||||||||||
| -- ==================================================================================== | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| -- Configuration options | ||||||||||||||||||||||||||||||
| ---@type opencode.Opts | ||||||||||||||||||||||||||||||
| vim.g.opencode_opts = { | ||||||||||||||||||||||||||||||
| -- Use default configuration | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| -- Required for opts.events.reload | ||||||||||||||||||||||||||||||
| vim.o.autoread = true | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Setting While necessary for opencode's reload functionality, this could cause unexpected behavior if users are working with files that are also being modified by external processes. Consider documenting this side effect more prominently, perhaps with a comment explaining the global impact. Prompt for Agent |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+33
|
||||||||||||||||||||||||||||||
| -- Required for opts.events.reload | |
| vim.o.autoread = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The keymaps for opencode should also be wrapped in error handling. If opencode.nvim fails to load, these keymaps will cause runtime errors when triggered. Consider:
local opencode_ok, opencode = pcall(require, "opencode")
if opencode_ok then
vim.keymap.set({ "n", "x" }, "<C-a>", function()
opencode.ask("@this: ", { submit = true })
end, { desc = "Ask opencode" })
-- ... other keymaps
endThis ensures graceful degradation if the plugin is unavailable.
Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: shunkakinoki/dotfiles#575
File: home-manager/programs/neovim/lua/ai.lua#L36
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.
Feedback:
The keymaps for opencode should also be wrapped in error handling. If opencode.nvim fails to load, these keymaps will cause runtime errors when triggered. Consider:
```lua
local opencode_ok, opencode = pcall(require, "opencode")
if opencode_ok then
vim.keymap.set({ "n", "x" }, "<C-a>", function()
opencode.ask("@this: ", { submit = true })
end, { desc = "Ask opencode" })
-- ... other keymaps
end
This ensures graceful degradation if the plugin is unavailable.
</details>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This introduces a keymap <C-.> that conflicts with an existing keymap for sidekick defined in keymaps.lua:257. The sidekick mapping is for modes n, t, i, x, while this new mapping is for n, t. Since ai.lua is loaded after keymaps.lua, this mapping will override the sidekick one in normal and terminal modes, but not in insert or visual mode. This leads to inconsistent behavior where <C-.> does different things depending on the mode. This conflict should be resolved by choosing a different keymap for one of the actions or by removing the conflicting one if it's no longer needed.
Copilot
AI
Jan 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The go keymap conflicts with the existing buffer-local go keymap defined in autocmds.lua (line 217) for fugitive buffers. This creates a keybinding collision where the opencode keymap will be shadowed by the fugitive buffer-local mapping. Consider using a different key combination like gO or <leader>go to avoid conflicts.
| vim.keymap.set({ "n", "x" }, "go", function() | |
| return require("opencode").operator("@this ") | |
| end, { expr = true, desc = "Add range to opencode" }) | |
| vim.keymap.set("n", "goo", function() | |
| return require("opencode").operator("@this ") .. "_" | |
| end, { expr = true, desc = "Add line to opencode" }) | |
| vim.keymap.set({ "n", "x" }, "gO", function() | |
| return require("opencode").operator("@this ") | |
| end, { expr = true, desc = "Add range to opencode (gO)" }) | |
| vim.keymap.set("n", "gOO", function() | |
| return require("opencode").operator("@this ") .. "_" | |
| end, { expr = true, desc = "Add line to opencode (gOO)" }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Ctrl+Shift-U/D mappings likely collapse to Ctrl-U/D and override default half-page scroll keys globally, breaking normal navigation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/programs/neovim/lua/ai.lua, line 60:
<comment>Ctrl+Shift-U/D mappings likely collapse to Ctrl-U/D and override default half-page scroll keys globally, breaking normal navigation.</comment>
<file context>
@@ -1,3 +1,70 @@
+end, { expr = true, desc = "Add line to opencode" })
+
+-- Scroll keymaps for opencode session
+vim.keymap.set("n", "<S-C-u>", function()
+ require("opencode").command("session.half.page.up")
+end, { desc = "opencode half page up" })
</file context>
Copilot
AI
Jan 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The <S-C-u> and <S-C-d> keymaps may not work reliably in all terminal emulators. Shift+Ctrl combinations with letter keys are often not distinguishable from their non-shifted counterparts in terminal Neovim. Consider using alternative keymaps that work consistently across terminals, such as <leader>u and <leader>d or using the standard <C-u> and <C-d> with a prefix key.
| vim.keymap.set("n", "<S-C-u>", function() | |
| require("opencode").command("session.half.page.up") | |
| end, { desc = "opencode half page up" }) | |
| vim.keymap.set("n", "<S-C-d>", function() | |
| vim.keymap.set("n", "<leader>u", function() | |
| require("opencode").command("session.half.page.up") | |
| end, { desc = "opencode half page up" }) | |
| vim.keymap.set("n", "<leader>d", function() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The keymaps for opencode.nvim are defined here in ai.lua. However, there is a central keymaps.lua file where other keymaps, including those for sidekick, are defined. To maintain consistency and have a single source of truth for keybindings, it would be better to move these opencode.nvim keymaps to keymaps.lua, under a new OPENCODE section. This would also make the <C-.> conflict with sidekick more apparent and easier to manage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These keymaps override the default behavior of + and - in normal mode, which are standard Vim motions for moving to the next and previous line respectively. While this is documented in the pull request description, it's a significant departure from default Vim behavior that could be disruptive and surprising. Have you considered using different keys for increment/decrement to avoid overriding these built-in motions? For example, <leader>+ and <leader>- could be alternatives that don't clash with default functionality.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,7 +84,9 @@ vim.pack.add({ | |
| { src = "https://github.com/yioneko/nvim-vtsls" }, | ||
|
|
||
| -- AI | ||
| { src = "https://github.com/folke/snacks.nvim" }, -- Required for opencode.nvim | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: New plugins added without corresponding lock entries—snacks.nvim and opencode.nvim are unpinned, undermining reproducibility. Prompt for AI agents |
||
| { src = "https://github.com/folke/sidekick.nvim" }, | ||
| { src = "https://github.com/NickvanDyke/opencode.nvim" }, | ||
|
|
||
| -- TERMINAL | ||
| { src = "https://github.com/akinsho/toggleterm.nvim" }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| -- Tests for lua/ai.lua | ||
| -- Tests AI/sidekick integration (plugin not loaded in minimal test env) | ||
| -- Tests AI integrations: sidekick and opencode.nvim | ||
|
|
||
| describe("ai", function() | ||
| describe("sidekick API", function() | ||
|
|
@@ -10,6 +10,67 @@ describe("ai", function() | |
| end) | ||
| end) | ||
|
|
||
| describe("snacks API", function() | ||
| it("should have expected snacks components structure", function() | ||
| -- snacks.nvim provides input, picker, and terminal | ||
| local expected_components = { "input", "picker", "terminal" } | ||
| for _, component in ipairs(expected_components) do | ||
| assert.is_string(component) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: AI integration test is tautological: only asserts hardcoded strings are strings, so it cannot fail even if snacks/opencode integrations are broken. Prompt for AI agents |
||
| end | ||
| end) | ||
|
Comment on lines
+14
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test |
||
| end) | ||
|
|
||
| describe("opencode.nvim API", function() | ||
| it("should support opencode_opts global variable", function() | ||
| -- opencode.nvim uses vim.g.opencode_opts for configuration | ||
| vim.g.opencode_opts = {} | ||
| assert.is_table(vim.g.opencode_opts) | ||
| vim.g.opencode_opts = nil | ||
| end) | ||
|
|
||
| it("should support autoread option for reload events", function() | ||
| -- Required for opencode reload functionality | ||
| vim.o.autoread = true | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Test sets vim.o.autoread globally without restoring the prior value, causing state leakage between tests. Prompt for AI agents |
||
| assert.equals(true, vim.o.autoread) | ||
| end) | ||
|
|
||
| it("should support context placeholders pattern", function() | ||
| -- opencode.nvim uses context placeholders like @this, @buffer, etc. | ||
| local placeholders = { | ||
| "@this", | ||
| "@buffer", | ||
| "@buffers", | ||
| "@visible", | ||
| "@diagnostics", | ||
| "@quickfix", | ||
| "@diff", | ||
| "@marks", | ||
| } | ||
| for _, placeholder in ipairs(placeholders) do | ||
| assert.is_string(placeholder) | ||
| assert.is_true(placeholder:sub(1, 1) == "@") | ||
| end | ||
| end) | ||
|
Comment on lines
+37
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test for context placeholders is tautological. It defines a local table |
||
|
|
||
| it("should support prompt library pattern", function() | ||
| -- opencode.nvim includes built-in prompts | ||
| local prompts = { | ||
| "diagnostics", | ||
| "diff", | ||
| "document", | ||
| "explain", | ||
| "fix", | ||
| "implement", | ||
| "optimize", | ||
| "review", | ||
| "test", | ||
| } | ||
| for _, prompt in ipairs(prompts) do | ||
| assert.is_string(prompt) | ||
| end | ||
| end) | ||
|
Comment on lines
+55
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to other new tests in this file, this test for the prompt library is tautological. It verifies a hardcoded list of strings. This doesn't ensure that |
||
| end) | ||
|
|
||
| describe("AI keymaps expected", function() | ||
| it("should support leader-based AI keymaps pattern", function() | ||
| -- Verify keymap API works for AI-style mappings | ||
|
|
@@ -18,5 +79,75 @@ describe("ai", function() | |
| assert.is_true(keymap ~= "") | ||
| vim.keymap.del("n", "<leader>ai_test") | ||
| end) | ||
|
|
||
| it("should support opencode ask keymap pattern", function() | ||
| -- Test <C-a> style keymap for ask | ||
| vim.keymap.set({ "n", "x" }, "<C-a>", function() end, { desc = "Ask opencode" }) | ||
| local keymap_n = vim.fn.maparg("<C-a>", "n") | ||
| local keymap_x = vim.fn.maparg("<C-a>", "x") | ||
| assert.is_true(keymap_n ~= "") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Keymap existence check is unreliable: maparg() returns empty string for Lua callback mappings, so these assertions can fail even when the mapping exists. Prompt for AI agents |
||
| assert.is_true(keymap_x ~= "") | ||
| vim.keymap.del("n", "<C-a>") | ||
| vim.keymap.del("x", "<C-a>") | ||
| end) | ||
|
|
||
| it("should support opencode select keymap pattern", function() | ||
| -- Test <C-x> style keymap for select | ||
| vim.keymap.set({ "n", "x" }, "<C-x>", function() end, { desc = "Execute opencode action" }) | ||
| local keymap_n = vim.fn.maparg("<C-x>", "n") | ||
| local keymap_x = vim.fn.maparg("<C-x>", "x") | ||
| assert.is_true(keymap_n ~= "") | ||
| assert.is_true(keymap_x ~= "") | ||
| vim.keymap.del("n", "<C-x>") | ||
| vim.keymap.del("x", "<C-x>") | ||
| end) | ||
|
|
||
| it("should support opencode toggle keymap pattern", function() | ||
| -- Test <C-.> style keymap for toggle | ||
| vim.keymap.set({ "n", "t" }, "<C-.>", function() end, { desc = "Toggle opencode" }) | ||
| local keymap_n = vim.fn.maparg("<C-.>", "n") | ||
| local keymap_t = vim.fn.maparg("<C-.>", "t") | ||
| assert.is_true(keymap_n ~= "") | ||
| assert.is_true(keymap_t ~= "") | ||
| vim.keymap.del("n", "<C-.>") | ||
| vim.keymap.del("t", "<C-.>") | ||
| end) | ||
|
|
||
| it("should support operator mode keymaps", function() | ||
| -- Test operator mode keymaps (go, goo) | ||
| vim.keymap.set({ "n", "x" }, "go", function() | ||
| return "" | ||
| end, { expr = true, desc = "Add range to opencode" }) | ||
| local keymap = vim.fn.maparg("go", "n") | ||
| assert.is_true(keymap ~= "") | ||
| vim.keymap.del("n", "go") | ||
| vim.keymap.del("x", "go") | ||
| end) | ||
|
|
||
| it("should support remapped increment/decrement", function() | ||
| -- When <C-a> and <C-x> are used for opencode, + and - replace them | ||
| vim.keymap.set("n", "+", "<C-a>", { desc = "Increment", noremap = true }) | ||
| vim.keymap.set("n", "-", "<C-x>", { desc = "Decrement", noremap = true }) | ||
| local keymap_plus = vim.fn.maparg("+", "n") | ||
| local keymap_minus = vim.fn.maparg("-", "n") | ||
| assert.is_true(keymap_plus ~= "") | ||
| assert.is_true(keymap_minus ~= "") | ||
| vim.keymap.del("n", "+") | ||
| vim.keymap.del("n", "-") | ||
| end) | ||
| end) | ||
|
|
||
| describe("opencode command pattern", function() | ||
| it("should support session scroll commands", function() | ||
| -- opencode supports command() for session control | ||
| local scroll_commands = { | ||
| "session.half.page.up", | ||
| "session.half.page.down", | ||
| } | ||
| for _, cmd in ipairs(scroll_commands) do | ||
| assert.is_string(cmd) | ||
| assert.is_true(cmd:find("^session%.") ~= nil) | ||
| end | ||
| end) | ||
| end) | ||
| end) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for snacks.nvim setup. If the plugin isn't installed, this will cause Neovim to fail on startup. Consider wrapping in pcall() for graceful degradation:
This pattern is already used elsewhere in the codebase (e.g., utils.lua line 64 for nvim-web-devicons).
Agent: 🏛 Architecture •
• 
Prompt for Agent
This pattern is already used elsewhere in the codebase (e.g., utils.lua line 64 for nvim-web-devicons).