diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6605845 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,164 @@ +# AGENTS.md + +This document is the operating guide for coding agents working in this Neovim +configuration repository. + +## Repository Snapshot + +- Project type: personal Neovim config (Lua) +- Entry point: `init.lua` -> `lua/core/*` -> `lua/plugins/*` +- Plugin manager: `lazy.nvim` +- No dedicated CI, test suite, formatter config, or lint config is committed +- Primary language: Lua (Neovim runtime APIs) + +## Directory Layout + +- `init.lua`: bootstrap into core config +- `lua/core/options.lua`: editor options and globals +- `lua/core/keymaps.lua`: global and command keymaps +- `lua/core/lazy.lua`: lazy.nvim bootstrap and setup +- `lua/plugins/init.lua`: plugin spec list +- `lua/plugins/config/*.lua`: per-plugin config modules +- `lua/utils/*.lua`: utility helpers (for example `reload.lua`) + +## Build / Lint / Test Commands + +There is no Makefile/package manager script layer, so use direct commands. + +### Environment checks + +- Verify Neovim can start this config: + - `nvim --headless -u /home/alican/.config/nvim/init.lua +qa` +- Verify plugin bootstrap and lockfile resolution: + - `nvim --headless -u /home/alican/.config/nvim/init.lua '+Lazy! sync' +qa` +- Optional runtime health checks: + - `nvim --headless -u /home/alican/.config/nvim/init.lua '+checkhealth' +qa` + +### Formatting + +- Preferred formatter for Lua: `stylua` +- Format whole repo: + - `stylua /home/alican/.config/nvim` +- Format a single file: + - `stylua /home/alican/.config/nvim/lua/plugins/init.lua` + +If `stylua` is not installed, do not mass-reformat; preserve existing style in +touched regions only. + +### Linting + +- No lint config is committed (`.luacheckrc` not found). +- If `luacheck` is available, run ad hoc: + - `luacheck /home/alican/.config/nvim/lua` +- Treat lint as advisory unless the repository later adds pinned lint rules. + +### Testing + +- No test framework is currently configured in-repo. +- Use headless startup as the minimum smoke test: + - `nvim --headless -u /home/alican/.config/nvim/init.lua +qa` + +### Running a single test (important) + +There are no existing unit/integration test files, so there is no real +single-test command today. + +If you add Plenary-based tests, use this pattern for a single test file: + +- `nvim --headless -u /home/alican/.config/nvim/init.lua -c "PlenaryBustedFile tests/_spec.lua" -c qa` + +If you add a plain Lua test runner later, document exact single-test invocations +here and keep this section updated. + +## Code Style Guidelines + +Follow local conventions observed in current files first, then these rules. + +### Imports and module structure + +- Use `require("...")` with explicit module paths. +- Keep imports at top of file unless lazy-loading is intentional. +- Prefer locals for required modules (example: `local cmp = require("cmp")`). +- Return module tables (`local M = {}; ...; return M`) for utility modules. +- Plugin config modules should avoid side effects outside setup behavior. + +### Formatting and layout + +- Preserve existing indentation style per file (repo currently mixes tabs/spaces). +- Keep lines reasonably short (existing config uses `colorcolumn = "80"`). +- Use trailing commas in multiline Lua tables. +- Keep one logical statement per line. +- Group related options/settings in contiguous blocks. +- Avoid large unrelated reformatting in feature/fix commits. + +### Types and annotations + +- Lua is dynamically typed here; no strict type checker is configured. +- Keep useful EmmyLua annotations when present (for example `---@type ...`). +- Add annotations only when they improve editor/LSP inference. +- Do not add noisy type comments for obvious locals. + +### Naming conventions + +- Module/file names: lowercase, path-based (`core.options`, `plugins.config.lsp`). +- Local variables: `snake_case`. +- Exported module tables: `M`. +- User commands: `PascalCase` where already used (`ReloadConfig`). +- Avoid introducing new global functions/variables. +- Exception: if global is required by Vim command callbacks, prefix carefully and + document intent. + +### Neovim API usage + +- Prefer modern APIs: + - `vim.keymap.set` over `vim.api.nvim_set_keymap` + - `vim.api.nvim_create_user_command` for user commands +- Use buffer-local mappings/options when behavior is buffer-scoped. +- Keep plugin setup isolated in `lua/plugins/config/.lua` when practical. +- Keep core bootstrap files (`core/*`) minimal and composable. + +### Error handling and resilience + +- Fail softly for optional dependencies when reasonable (`pcall(require, ...)`). +- Avoid crashing startup for non-critical plugin behavior. +- Use `vim.notify(..., vim.log.levels.)` for actionable runtime feedback. +- For external commands in Lua, check return status if failure is meaningful. + +### Plugin and dependency changes + +- Add/modify plugin specs only in `lua/plugins/init.lua` unless refactoring. +- Keep plugin options in dedicated config modules when they grow beyond trivial. +- For plugins with build hooks (example: markdown preview), do not assume Node/npm + is available in all environments; mention prerequisites in PR notes. +- Preserve lockfile intent (`lazy-lock.json`) when updating plugin versions. + +### Commit hygiene for agents + +- Make focused changes with minimal scope. +- Do not fix unrelated style issues opportunistically. +- Include validation notes in your final response (what you ran, what you could + not run). +- If commands are unavailable locally, state that clearly and provide exact + follow-up commands. + +## Cursor / Copilot Rules + +Checked paths: + +- `.cursorrules` +- `.cursor/rules/` +- `.github/copilot-instructions.md` + +Current status: no Cursor or Copilot instruction files were found in this +repository. + +If these files are added later, agents must treat them as higher-priority +repository policy and this document should be updated to summarize them. + +## Agent Workflow Checklist + +- Read nearby files before editing; match local patterns. +- Keep edits minimal and reversible. +- Run at least one headless Neovim smoke check after non-trivial changes. +- For plugin changes, run `'+Lazy! sync'` headless when possible. +- Update this file when tooling, test framework, or style policy changes. diff --git a/lua/core/keymaps.lua b/lua/core/keymaps.lua index de44c63..5583535 100644 --- a/lua/core/keymaps.lua +++ b/lua/core/keymaps.lua @@ -1,19 +1,174 @@ -- Use native Neovim Lua keymap API local keymap = vim.keymap -keymap.set("n", "h", "h", { noremap = true, silent = true }) -keymap.set("n", "j", "j", { noremap = true, silent = true }) -keymap.set("n", "k", "k", { noremap = true, silent = true }) -keymap.set("n", "l", "l", { noremap = true, silent = true }) +keymap.set("n", "h", "h", { noremap = true, silent = true, desc = "Window left" }) +keymap.set("n", "j", "j", { noremap = true, silent = true, desc = "Window down" }) +keymap.set("n", "k", "k", { noremap = true, silent = true, desc = "Window up" }) +keymap.set("n", "l", "l", { noremap = true, silent = true, desc = "Window right" }) local reload = require("utils.reload") vim.api.nvim_create_user_command("ReloadConfig", reload.reload, {}) -vim.keymap.set("n", "r", reload.reload, { noremap = true, silent = true }) +vim.keymap.set("n", "r", reload.reload, { noremap = true, silent = true, desc = "Reload config" }) -keymap.set("n", "ff", "lua require('telescope.builtin').find_files()", {noremap = true, silent = true}) -keymap.set("n", "fg", "lua require('telescope.builtin').live_grep()" , {noremap = true, silent = true}) -keymap.set("n", "fb", "lua require('telescope.builtin').buffers()" , {noremap = true, silent = true}) -keymap.set("n", "fh", "lua require('telescope.builtin').help_tags()" , {noremap = true, silent = true}) +keymap.set("n", "?", function() + local ok, wk = pcall(require, "which-key") + if not ok then + vim.notify("which-key is not available", vim.log.levels.WARN) + return + end + + local ok_show = pcall(wk.show, { keys = "", mode = "n" }) + if not ok_show then + vim.cmd("WhichKey ") + end +end, { noremap = true, silent = true, desc = "Leader keymaps" }) + +keymap.set("n", "ff", "lua require('telescope.builtin').find_files()", {noremap = true, silent = true, desc = "Find files"}) +keymap.set("n", "fg", "lua require('telescope.builtin').live_grep()", {noremap = true, silent = true, desc = "Find text"}) +keymap.set("n", "fb", "lua require('telescope.builtin').buffers()", {noremap = true, silent = true, desc = "Find buffers"}) +keymap.set("n", "fh", "lua require('telescope.builtin').help_tags()", {noremap = true, silent = true, desc = "Find help"}) + +keymap.set("n", "xx", "Trouble diagnostics toggle", { noremap = true, silent = true, desc = "Problems toggle" }) +keymap.set("n", "xw", "Trouble diagnostics toggle", { noremap = true, silent = true, desc = "Problems workspace diagnostics" }) +keymap.set("n", "xb", "Trouble diagnostics toggle filter.buf=0", { noremap = true, silent = true, desc = "Problems buffer diagnostics" }) +keymap.set("n", "xq", "Trouble qflist toggle", { noremap = true, silent = true, desc = "Problems quickfix" }) +keymap.set("n", "xl", "Trouble loclist toggle", { noremap = true, silent = true, desc = "Problems location list" }) +keymap.set("n", "xr", "Trouble lsp_references toggle", { noremap = true, silent = true, desc = "Problems references" }) +keymap.set("n", "xs", "Trouble symbols toggle focus=false", { noremap = true, silent = true, desc = "Problems document symbols" }) +keymap.set("n", "xS", "Trouble lsp toggle focus=false win.position=right", { noremap = true, silent = true, desc = "Problems workspace symbols" }) vim.keymap.set("n", "gn", vim.diagnostic.goto_next, { noremap = true, silent = true }) + +local function with_dap(fn) + local ok, dap = pcall(require, "dap") + if not ok then + vim.notify("nvim-dap is not available", vim.log.levels.WARN) + return + end + fn(dap) +end + +local function with_dapui(fn) + local ok, dapui = pcall(require, "dapui") + if not ok then + vim.notify("nvim-dap-ui is not available", vim.log.levels.WARN) + return + end + fn(dapui) +end + +keymap.set("n", "dc", function() + with_dap(function(dap) + dap.continue() + end) +end, { noremap = true, silent = true, desc = "Debug continue" }) +keymap.set("n", "dr", function() + with_dap(function(dap) + dap.restart() + end) +end, { noremap = true, silent = true, desc = "Debug restart" }) +keymap.set("n", "dt", function() + with_dap(function(dap) + dap.terminate() + end) +end, { noremap = true, silent = true, desc = "Debug terminate" }) +keymap.set("n", "dp", function() + with_dap(function(dap) + dap.pause() + end) +end, { noremap = true, silent = true, desc = "Debug pause" }) +keymap.set("n", "do", function() + with_dap(function(dap) + dap.step_over() + end) +end, { noremap = true, silent = true, desc = "Debug step over" }) +keymap.set("n", "di", function() + with_dap(function(dap) + dap.step_into() + end) +end, { noremap = true, silent = true, desc = "Debug step into" }) +keymap.set("n", "dO", function() + with_dap(function(dap) + dap.step_out() + end) +end, { noremap = true, silent = true, desc = "Debug step out" }) +keymap.set("n", "du", function() + with_dap(function(dap) + dap.run_to_cursor() + end) +end, { noremap = true, silent = true, desc = "Debug run to cursor" }) + +keymap.set("n", "db", function() + with_dap(function(dap) + dap.toggle_breakpoint() + end) +end, { noremap = true, silent = true, desc = "Debug toggle breakpoint" }) +keymap.set("n", "dB", function() + with_dap(function(dap) + dap.set_breakpoint(vim.fn.input("Breakpoint condition: ")) + end) +end, { noremap = true, silent = true, desc = "Debug conditional breakpoint" }) +keymap.set("n", "dl", function() + with_dap(function(dap) + dap.set_breakpoint(nil, nil, vim.fn.input("Log point message: ")) + end) +end, { noremap = true, silent = true, desc = "Debug logpoint" }) +keymap.set("n", "dx", function() + with_dap(function(dap) + dap.clear_breakpoints() + end) +end, { noremap = true, silent = true, desc = "Debug clear breakpoints" }) + +keymap.set("n", "dd", function() + with_dapui(function(dapui) + dapui.toggle() + end) +end, { noremap = true, silent = true, desc = "Debug toggle UI" }) +keymap.set("n", "de", function() + with_dapui(function(dapui) + dapui.eval() + end) +end, { noremap = true, silent = true, desc = "Debug eval" }) +keymap.set("n", "dh", function() + with_dap(function() + require("dap.ui.widgets").hover() + end) +end, { noremap = true, silent = true, desc = "Debug hover" }) +keymap.set("n", "dq", function() + with_dap(function(dap) + dap.repl.open() + end) +end, { noremap = true, silent = true, desc = "Debug open REPL" }) +keymap.set("n", "dQ", function() + with_dap(function(dap) + dap.repl.toggle() + end) +end, { noremap = true, silent = true, desc = "Debug toggle REPL" }) +keymap.set("n", "ds", function() + with_dap(function(dap) + local session = dap.session() + if session then + vim.notify("DAP session active", vim.log.levels.INFO) + else + vim.notify("No active DAP session", vim.log.levels.WARN) + end + end) +end, { noremap = true, silent = true, desc = "Debug session status" }) + +keymap.set("n", "df", "Telescope dap frames", { noremap = true, silent = true, desc = "Debug frames" }) +keymap.set("n", "dC", "Telescope dap commands", { noremap = true, silent = true, desc = "Debug commands" }) +keymap.set("n", "dv", "Telescope dap variables", { noremap = true, silent = true, desc = "Debug variables" }) +keymap.set("n", "dR", "Telescope dap configurations", { noremap = true, silent = true, desc = "Debug configurations" }) +keymap.set("n", "dK", "Telescope dap list_breakpoints", { noremap = true, silent = true, desc = "Debug breakpoints" }) + +keymap.set("n", "cg", "CMakeGenerate", { noremap = true, silent = true, desc = "CMake generate" }) +keymap.set("n", "cb", "CMakeBuild", { noremap = true, silent = true, desc = "CMake build" }) +keymap.set("n", "cB", "CMakeBuild!", { noremap = true, silent = true, desc = "CMake clean build" }) +keymap.set("n", "cc", "CMakeClean", { noremap = true, silent = true, desc = "CMake clean" }) +keymap.set("n", "ct", "CMakeSelectBuildTarget", { noremap = true, silent = true, desc = "CMake select build target" }) +keymap.set("n", "cT", "CMakeSelectBuildType", { noremap = true, silent = true, desc = "CMake select build type" }) +keymap.set("n", "cr", "CMakeRun", { noremap = true, silent = true, desc = "CMake run" }) +keymap.set("n", "cd", "CMakeDebug", { noremap = true, silent = true, desc = "CMake debug" }) +keymap.set("n", "cL", "CMakeSelectLaunchTarget", { noremap = true, silent = true, desc = "CMake select launch target" }) +keymap.set("n", "cS", "CMakeSettings", { noremap = true, silent = true, desc = "CMake settings" }) diff --git a/lua/plugins/config/cmake.lua b/lua/plugins/config/cmake.lua new file mode 100644 index 0000000..1f826eb --- /dev/null +++ b/lua/plugins/config/cmake.lua @@ -0,0 +1,14 @@ +require("cmake-tools").setup({ + cmake_build_directory = "build", + cmake_generate_options = { "-DCMAKE_EXPORT_COMPILE_COMMANDS=1" }, + cmake_build_options = {}, + cmake_soft_link_compile_commands = true, + cmake_compile_commands_from_lsp = false, + cmake_dap_configuration = { + name = "cpp", + type = "codelldb", + request = "launch", + stopOnEntry = false, + runInTerminal = false, + }, +}) diff --git a/lua/plugins/config/conform.lua b/lua/plugins/config/conform.lua new file mode 100644 index 0000000..da995c7 --- /dev/null +++ b/lua/plugins/config/conform.lua @@ -0,0 +1,34 @@ +local conform = require("conform") + +conform.setup({ + formatters_by_ft = { + c = { "clang_format" }, + cpp = { "clang_format" }, + h = { "clang_format" }, + hpp = { "clang_format" }, + }, + format_on_save = function(bufnr) + local ft = vim.bo[bufnr].filetype + local cpp_filetypes = { + c = true, + cpp = true, + h = true, + hpp = true, + } + + if cpp_filetypes[ft] then + return { + timeout_ms = 1000, + lsp_format = "fallback", + } + end + end, +}) + +vim.api.nvim_create_user_command("Format", function() + conform.format({ + async = false, + timeout_ms = 1000, + lsp_format = "fallback", + }) +end, {}) diff --git a/lua/plugins/config/dap.lua b/lua/plugins/config/dap.lua new file mode 100644 index 0000000..f7250ca --- /dev/null +++ b/lua/plugins/config/dap.lua @@ -0,0 +1,113 @@ +local dap = require("dap") +local dapui = require("dapui") + +local function resolve_codelldb() + local mason_codelldb = vim.fn.stdpath("data") .. "/mason/bin/codelldb" + if vim.fn.executable(mason_codelldb) == 1 then + return mason_codelldb + end + + local system_codelldb = vim.fn.exepath("codelldb") + if system_codelldb ~= "" then + return system_codelldb + end + + return "codelldb" +end + +require("nvim-dap-virtual-text").setup({ + commented = true, +}) + +dap.adapters.codelldb = { + type = "server", + port = "${port}", + executable = { + command = resolve_codelldb(), + args = { "--port", "${port}" }, + }, +} + +dap.configurations.cpp = { + { + name = "Launch file", + type = "codelldb", + request = "launch", + program = function() + return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/build/", "file") + end, + cwd = "${workspaceFolder}", + stopOnEntry = false, + args = function() + local input = vim.fn.input("Program args: ") + if input == "" then + return {} + end + return vim.split(input, " ") + end, + runInTerminal = false, + }, +} +dap.configurations.c = dap.configurations.cpp + +vim.fn.sign_define("DapBreakpoint", { + text = "●", + texthl = "DiagnosticError", + linehl = "", + numhl = "", +}) +vim.fn.sign_define("DapBreakpointCondition", { + text = "◆", + texthl = "DiagnosticWarn", + linehl = "", + numhl = "", +}) +vim.fn.sign_define("DapLogPoint", { + text = "◉", + texthl = "DiagnosticInfo", + linehl = "", + numhl = "", +}) +vim.fn.sign_define("DapStopped", { + text = "▶", + texthl = "DiagnosticOk", + linehl = "Visual", + numhl = "DiagnosticOk", +}) + +dapui.setup({ + layouts = { + { + elements = { + { id = "scopes", size = 0.30 }, + { id = "breakpoints", size = 0.20 }, + { id = "stacks", size = 0.25 }, + { id = "watches", size = 0.25 }, + }, + size = 45, + position = "left", + }, + { + elements = { + { id = "repl", size = 0.50 }, + { id = "console", size = 0.50 }, + }, + size = 12, + position = "bottom", + }, + }, +}) + +dap.listeners.after.event_initialized["dapui_config"] = function() + dapui.open() +end +dap.listeners.before.event_terminated["dapui_config"] = function() + dapui.close() +end +dap.listeners.before.event_exited["dapui_config"] = function() + dapui.close() +end + +pcall(function() + require("telescope").load_extension("dap") +end) diff --git a/lua/plugins/config/lsp.lua b/lua/plugins/config/lsp.lua index fa95d49..7ef015b 100644 --- a/lua/plugins/config/lsp.lua +++ b/lua/plugins/config/lsp.lua @@ -17,7 +17,7 @@ local on_attach = function(client, bufnr) bufmap("n", "rn", "lua vim.lsp.buf.rename()") bufmap("n", "ca", "lua vim.lsp.buf.code_action()") bufmap("n", "gr", "lua vim.lsp.buf.references()") - bufmap("n", "f", "lua vim.lsp.buf.formatting()") + bufmap("n", "f", "lua vim.lsp.buf.format()") end -- List of servers to setup @@ -31,11 +31,17 @@ local servers = { "texlab", } +local server_overrides = { + clangd = { + cmd = { "clangd", "--clang-tidy" }, + }, +} + for _, server in ipairs(servers) do - vim.lsp.config(server, { + vim.lsp.config(server, vim.tbl_deep_extend("force", { on_attach = on_attach, capabilities = capabilities, - }) + }, server_overrides[server] or {})) -- Enable the server configuration vim.lsp.enable(server) end diff --git a/lua/plugins/config/mason.lua b/lua/plugins/config/mason.lua new file mode 100644 index 0000000..3a8c7fe --- /dev/null +++ b/lua/plugins/config/mason.lua @@ -0,0 +1,14 @@ +require("mason").setup() + +require("mason-lspconfig").setup({ + ensure_installed = { + "pyright", + "clangd", + "zls", + "lua_ls", + "vimls", + "marksman", + "texlab", + }, + automatic_installation = true, +}) diff --git a/lua/plugins/config/nvimtree.lua b/lua/plugins/config/nvimtree.lua index 5045678..4393ccb 100644 --- a/lua/plugins/config/nvimtree.lua +++ b/lua/plugins/config/nvimtree.lua @@ -1,9 +1,8 @@ require("nvim-tree").setup({ - sort = { sorter = "case_sensitive" }, + sort_by = "case_sensitive", view = { width = 30 }, renderer = { group_empty = true }, filters = { dotfiles = true }, }) vim.keymap.set("n", "", ":NvimTreeToggle", { noremap = true, silent = true }) - diff --git a/lua/plugins/config/opencode.lua b/lua/plugins/config/opencode.lua new file mode 100644 index 0000000..3f35550 --- /dev/null +++ b/lua/plugins/config/opencode.lua @@ -0,0 +1,11 @@ +---@type opencode.Opts +vim.g.opencode_opts = { + -- Your configuration, if any. +} + +-- Required for `opts.events.reload`. +vim.o.autoread = true + +vim.keymap.set({ "n", "x" }, "oa", function() + require("opencode").ask("@this: ", { submit = true }) +end, { desc = "Ask opencode with selection" }) diff --git a/lua/plugins/config/trouble.lua b/lua/plugins/config/trouble.lua new file mode 100644 index 0000000..646d884 --- /dev/null +++ b/lua/plugins/config/trouble.lua @@ -0,0 +1,3 @@ +require("trouble").setup({ + focus = false, +}) diff --git a/lua/plugins/config/whichkey.lua b/lua/plugins/config/whichkey.lua new file mode 100644 index 0000000..1d3610c --- /dev/null +++ b/lua/plugins/config/whichkey.lua @@ -0,0 +1,36 @@ +local wk = require("which-key") + +wk.setup({ + plugins = { + presets = { + operators = false, + motions = false, + text_objects = false, + windows = false, + nav = false, + z = false, + g = false, + }, + }, + triggers = { + { "", mode = { "n", "v" } }, + }, +}) + +if wk.add then + wk.add({ + { "c", group = "CMake" }, + { "d", group = "Debug" }, + { "f", group = "Find" }, + { "o", group = "OpenCode" }, + { "x", group = "Problems" }, + }) +else + wk.register({ + c = { name = "+CMake" }, + d = { name = "+Debug" }, + f = { name = "+Find" }, + o = { name = "+OpenCode" }, + x = { name = "+Problems" }, + }, { prefix = "" }) +end diff --git a/lua/plugins/init.lua b/lua/plugins/init.lua index 8907471..850d8bd 100644 --- a/lua/plugins/init.lua +++ b/lua/plugins/init.lua @@ -40,8 +40,13 @@ return { }, }, { - "williamboman/nvim-lsp-installer", - + "williamboman/mason.nvim", + config = function() + require("plugins.config.mason") + end, + }, + { + "williamboman/mason-lspconfig.nvim", }, }, config = function() @@ -63,10 +68,47 @@ return { require("plugins.config.cmp") end, }, + { + "stevearc/conform.nvim", + config = function() + require("plugins.config.conform") + end, + }, { 'nvim-telescope/telescope.nvim', tag = '0.1.8', dependencies = { 'nvim-lua/plenary.nvim' } }, + { + "folke/trouble.nvim", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + require("plugins.config.trouble") + end, + }, + { + "folke/which-key.nvim", + config = function() + require("plugins.config.whichkey") + end, + }, + { + "mfussenegger/nvim-dap", + dependencies = { + "rcarriga/nvim-dap-ui", + "nvim-neotest/nvim-nio", + "theHamsta/nvim-dap-virtual-text", + "nvim-telescope/telescope-dap.nvim", + }, + config = function() + require("plugins.config.dap") + end, + }, + { + "Civitasv/cmake-tools.nvim", + config = function() + require("plugins.config.cmake") + end, + }, { 'nvimdev/indentmini.nvim', config = function() @@ -121,5 +163,16 @@ return { } end, }, + { + "nickjvandyke/opencode.nvim", + dependencies = { + -- Recommended for `ask()` and `select()`. + -- Required for `snacks` provider. + ---@module 'snacks' <- Loads `snacks.nvim` types for configuration intellisense. + { "folke/snacks.nvim", opts = { input = {}, picker = {}, terminal = {} } }, + }, + config = function() + require("plugins.config.opencode") + end, + } } -