commit 7a73f199210a0cd3bd0da958efc3721522acfb1b Author: Daniel Henry Date: Mon Aug 25 18:23:54 2025 -0500 Initial Commit - Add nvim config Signed-off-by: Daniel Henry diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua new file mode 100644 index 0000000..e7e9210 --- /dev/null +++ b/.config/nvim/init.lua @@ -0,0 +1,37 @@ +-- Leaders early so plugins see them +vim.g.mapleader = " " +vim.g.maplocalleader = " " + +-- Bootstrap lazy.nvim (auto-install on first run) +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not vim.loop.fs_stat(lazypath) then + vim.fn.system({ "git", "clone", "--filter=blob:none", + "https://github.com/folke/lazy.nvim", lazypath }) +end +vim.opt.rtp:prepend(lazypath) + +-- Collect plugin specs +local plugins = {} + +-- Core/global plugins +vim.list_extend(plugins, require("core.plugins")) + +-- NOTE: In the future, add language packs here. +-- Example (loads only if the module exists): +-- local ok, c_plugins = pcall(require, "c.plugins") +-- if ok then vim.list_extend(plugins, c_plugins) end + +local ok_c, c_plugins = pcall(require, "c.plugins") +if ok_c then vim.list_extend(plugins, c_plugins) end + +-- Set up plugins +require("lazy").setup(plugins, { + ui = { border = "rounded" }, +}) + +-- Editor config (numbers, etc.) and global keymaps +require("core.config") +require("core.keybindings") + +require("c.config") +require("c.keybindings") diff --git a/.config/nvim/lazy-lock.json b/.config/nvim/lazy-lock.json new file mode 100644 index 0000000..882392d --- /dev/null +++ b/.config/nvim/lazy-lock.json @@ -0,0 +1,23 @@ +{ + "LuaSnip": { "branch": "master", "commit": "de10d8414235b0a8cabfeba60d07c24304e71f5c" }, + "cmp-nvim-lsp": { "branch": "main", "commit": "bd5a7d6db125d4654b50eeae9f5217f24bb22fd3" }, + "cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" }, + "conform.nvim": { "branch": "master", "commit": "9ddab4e14c44196d393a72b958b4da6dab99ecef" }, + "indent-blankline.nvim": { "branch": "master", "commit": "005b56001b2cb30bfa61b7986bc50657816ba4ba" }, + "lazy.nvim": { "branch": "main", "commit": "6c3bda4aca61a13a9c63f1c1d1b16b9d3be90d7a" }, + "lualine.nvim": { "branch": "master", "commit": "b8c23159c0161f4b89196f74ee3a6d02cdc3a955" }, + "neo-tree.nvim": { "branch": "main", "commit": "bbeda076c8a2e7d16614287cd70239f577e5bf55" }, + "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, + "nvim-autopairs": { "branch": "master", "commit": "23320e75953ac82e559c610bec5a90d9c6dfa743" }, + "nvim-cmp": { "branch": "main", "commit": "b5311ab3ed9c846b585c0c15b7559be131ec4be9" }, + "nvim-lspconfig": { "branch": "master", "commit": "ce45ccd6a97be8752ed83d1e14ac2aff1d5a4238" }, + "nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" }, + "nvim-web-devicons": { "branch": "master", "commit": "c2599a81ecabaae07c49ff9b45dcd032a8d90f1a" }, + "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, + "rose-pine": { "branch": "main", "commit": "72befaffeac38db7bdd49e0549eaa2c4806dd878" }, + "telescope-fzf-native.nvim": { "branch": "main", "commit": "1f08ed60cafc8f6168b72b80be2b2ea149813e55" }, + "telescope-ui-select.nvim": { "branch": "master", "commit": "6e51d7da30bd139a6950adf2a47fda6df9fa06d2" }, + "telescope.nvim": { "branch": "master", "commit": "b4da76be54691e854d3e0e02c36b0245f945c2c7" }, + "trouble.nvim": { "branch": "main", "commit": "85bedb7eb7fa331a2ccbecb9202d8abba64d37b3" }, + "which-key.nvim": { "branch": "main", "commit": "370ec46f710e058c9c1646273e6b225acf47cbed" } +} diff --git a/.config/nvim/lua/c/config.lua b/.config/nvim/lua/c/config.lua new file mode 100644 index 0000000..f2a5b9d --- /dev/null +++ b/.config/nvim/lua/c/config.lua @@ -0,0 +1,46 @@ +-- C language pack: editor behavior for C/C++ +-- We keep this file "pure config" (no keymaps) to match your structure. + +-- Use `make` for builds in C/C++ buffers. +-- Quickfix will parse clang/gcc errors by default, so :make populates the list. +vim.api.nvim_create_autocmd("FileType", { + pattern = { "c", "cpp" }, + callback = function(args) + -- Buffer-local so other languages can pick their own build tools later. + vim.bo[args.buf].makeprg = "make -j" + end, +}) + +-- Friendly heads-up if compile_commands.json isn't present. +-- clangd *can* work without it, but accuracy (includes/defines/flags) is much better with it. +do + local warned = false + vim.api.nvim_create_autocmd({ "BufReadPost", "BufNewFile" }, { + pattern = { "*.c", "*.h", "*.cpp", "*.hpp" }, + callback = function() + if warned then return end + local root = vim.fs.root(0, { "compile_commands.json", ".git", "Makefile", "makefile" }) + -- Look specifically for compile_commands.json upward from the file + local found = vim.fs.find("compile_commands.json", { upward = true, path = vim.fs.dirname(vim.api.nvim_buf_get_name(0)) })[1] + if not found and root then + warned = true + vim.schedule(function() + vim.notify( + "clangd: compile_commands.json not found.\n" .. + "Run: bear -- make -j\n" .. + "Tip: re-run after changing flags or adding files.", + vim.log.levels.WARN, + { title = "C/clangd" } + ) + end) + end + end, + }) +end + +-- Treat *.h as C (applies globally; easiest approach for a C-centric workflow) +vim.filetype.add({ + extension = { + h = "c", + }, +}) diff --git a/.config/nvim/lua/c/keybindings.lua b/.config/nvim/lua/c/keybindings.lua new file mode 100644 index 0000000..6d269e6 --- /dev/null +++ b/.config/nvim/lua/c/keybindings.lua @@ -0,0 +1,59 @@ +-- C language pack: keymaps +-- We add LSP-powered maps *only when* clangd attaches, so they are buffer-local to C/C++. + +local function buf_map(buf, mode, lhs, rhs, desc) + vim.keymap.set(mode, lhs, rhs, { buffer = buf, silent = true, noremap = true, desc = desc }) +end + +-- Switch between header/source (create if missing) +vim.keymap.set("n", "ah", function() + require("c.utils").toggle_header_source() +end, { desc = "C: alternate header/source" }) + +-- which-key label (buffer-local) if you like: +local ok, wk = pcall(require, "which-key") +if ok then wk.add({ { "a", group = "Alternate" } }) end + +-- When any LSP attaches, check if it's clangd on a C/C++ buffer and map keys. +vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + local buf = args.buf + local ft = vim.bo[buf].filetype + if not client or client.name ~= "clangd" then return end + if ft ~= "c" and ft ~= "cpp" then return end + + -- === Navigation & refactoring (clangd) === + buf_map(buf, "n", "gd", vim.lsp.buf.definition, "LSP: go to definition") + buf_map(buf, "n", "gD", vim.lsp.buf.declaration, "LSP: go to definition") + buf_map(buf, "n", "gr", vim.lsp.buf.references, "LSP: references") + buf_map(buf, "n", "K", vim.lsp.buf.hover, "LSP: hover docs") + buf_map(buf, "n", "rn", vim.lsp.buf.rename, "LSP: rename symbol") + buf_map(buf, "n", "ca", vim.lsp.buf.code_action,"LSP: code action") + buf_map(buf, "n", "]d", vim.diagnostic.goto_next, "Diag: next") + buf_map(buf, "n", "[d", vim.diagnostic.goto_prev, "Diag: prev") + + -- Format now (uses Conform with clang-format or LSP fallback) + buf_map(buf, "n", "cf", function() + local ok, conform = pcall(require, "conform") + if ok then conform.format({ bufnr = buf, lsp_fallback = true }) end + end, "C: format buffer") + + -- === Build / Quickfix (make) === + buf_map(buf, "n", "mm", "make", "Make: build") + buf_map(buf, "n", "mb", "make bear", "Make: build") + buf_map(buf, "n", "mo", "copen", "Quickfix: open") + buf_map(buf, "n", "mn", "cnext", "Quickfix: next") + buf_map(buf, "n", "mp", "cprev", "Quickfix: prev") + + -- which-key: label these groups for *this buffer only* + local ok, wk = pcall(require, "which-key") + if ok then + wk.add({ + { "c", group = "C / LSP" }, + { "m", group = "Make / Quickfix" }, + }, { buffer = buf }) + end + end, +}) + diff --git a/.config/nvim/lua/c/plugins.lua b/.config/nvim/lua/c/plugins.lua new file mode 100644 index 0000000..462aa4c --- /dev/null +++ b/.config/nvim/lua/c/plugins.lua @@ -0,0 +1,69 @@ +-- C language pack: plugins that only load for C/C++ +-- - clangd (LSP) via nvim-lspconfig +-- - clang-format on save via conform.nvim +return { + + -- LSP framework (just the client; the *server* is clangd you installed with Homebrew) + { + "neovim/nvim-lspconfig", + ft = { "c", "cpp" }, -- load only when editing C/C++ + config = function() + local lsp = require("lspconfig") + + -- Minimal, sensible clangd setup. + -- Tip: clangd finds your project root via compile_commands.json, compile_flags.txt, or .git + lsp.clangd.setup({ + -- You can pass extra flags here later (e.g., to enable clang-tidy) + -- cmd = { "clangd", "--header-insertion=never" }, + -- capabilities = ... (we'll keep defaults until you add completion later) + }) + -- That's it. Keymaps are added in c/keybindings.lua on LspAttach. + end, + }, + + -- Formatting with clang-format (runs on save for C/C++) + -- If you later move Conform to core, keep this block here: it will just *extend* its opts. + { + "stevearc/conform.nvim", + ft = { "c", "cpp" }, + opts = function(_, opts) + opts.formatters_by_ft = opts.formatters_by_ft or {} + opts.formatters_by_ft.c = { "clang-format" } + opts.formatters_by_ft.cpp = { "clang-format" } + return opts + end, + config = function(_, opts) + local conform = require("conform") + conform.setup(opts) + + -- Format C/C++ buffers on save. If clang-format isn't found, + -- Conform will fall back to LSP formatting (safe). + vim.api.nvim_create_autocmd("BufWritePre", { + pattern = { "*.c", "*.h", "*.cpp", "*.hpp" }, + callback = function(args) + conform.format({ bufnr = args.buf, lsp_fallback = true }) + end, + }) + end, + }, + { + "neovim/nvim-lspconfig", + ft = { "c", "cpp" }, + config = function() + local lsp = require("lspconfig") + + -- Advertise cmp’s capabilities to clangd (safe even if cmp isn’t loaded yet) + local capabilities = vim.lsp.protocol.make_client_capabilities() + local ok, cmp_lsp = pcall(require, "cmp_nvim_lsp") + if ok then + capabilities = cmp_lsp.default_capabilities(capabilities) + end + + lsp.clangd.setup({ + capabilities = capabilities, + cmd = { "clangd", "--background-index", "--pch-storage=memory"}, + -- (keep any flags you had here) + }) + end, + }, +} diff --git a/.config/nvim/lua/c/snippets.lua b/.config/nvim/lua/c/snippets.lua new file mode 100644 index 0000000..d3f5031 --- /dev/null +++ b/.config/nvim/lua/c/snippets.lua @@ -0,0 +1,15 @@ +local ls = require('luasnip') +local s, t, i, f = ls.snippet, ls.text_node, ls.insert_node, ls.function_node + +ls.add_snippets("c", { + + s({ trig = "guard", dscr = "C include guard" }, { + + t("#ifndef "), i(1, "MY_HEADER_H"), + t({ "", "#define "}), f(function(args) return args[1][1] end, { 1 }), + t({ "", "", "" }), + i(0), + t({ "", "", "#endif /* " }), f(function(args) return args[1][1] end, {1}), t(" */") + }) + +}) diff --git a/.config/nvim/lua/c/utils.lua b/.config/nvim/lua/c/utils.lua new file mode 100644 index 0000000..b9617ee --- /dev/null +++ b/.config/nvim/lua/c/utils.lua @@ -0,0 +1,87 @@ +-- lua/c/utils.lua +local M = {} + +-- Find the project root (same markers clangd likes) +local function project_root(buf) + return vim.fs.root(buf or 0, { "compile_commands.json", ".git", "Makefile", "makefile" }) +end + +local function relpath(abs, root) + if not abs or not root then return nil end + if abs:sub(1, #root) ~= root then return nil end + local r = abs:sub(#root + 1) + if r:sub(1, 1) == "/" then r = r:sub(2) end + return r +end + +local function dirpart(p) return p:match("^(.*)/") end + +-- Build the alternate path (header <-> source), preserving nested subdirs +local function alternate_rel(rel) + local first = rel:match("^([^/]+)/") + local ext = rel:match("%.([^.]+)$") or "" + local rest = rel:gsub("^" .. (first or "") .. "/", "") + + if first == "include" and ext == "h" then + return "src/" .. rest:gsub("%.h$", ".c") + elseif first == "src" and ext == "c" then + return "include/" .. rest:gsub("%.c$", ".h") + end + + -- Fallbacks if file isn’t directly under include/ or src/ + if ext == "h" then + return ("src/" .. rel):gsub("^src/include/", "src/"):gsub("%.h$", ".c") + elseif ext == "c" then + return ("include/" .. rel):gsub("^include/src/", "include/"):gsub("%.c$", ".h") + end + + return nil +end + +function M.toggle_header_source() + local buf = 0 + local abs = vim.api.nvim_buf_get_name(buf) + if abs == "" then + vim.notify("No file name (unsaved buffer).", vim.log.levels.WARN) + return + end + + local root = project_root(buf) + if not root then + vim.notify("Couldn’t locate project root (no .git/Makefile/compile_commands.json).", vim.log.levels.WARN) + return + end + + local rel = relpath(abs, root) + if not rel then + vim.notify("File not under project root: " .. abs, vim.log.levels.WARN) + return + end + + local alt_rel = alternate_rel(rel) + if not alt_rel then + vim.notify("Not a .c/.h under include/ or src/: " .. rel, vim.log.levels.WARN) + return + end + + local alt_abs = root .. "/" .. alt_rel + if vim.loop.fs_stat(alt_abs) then + vim.cmd.edit(vim.fn.fnameescape(alt_abs)) + return + end + + -- Prompt to create + local choice = vim.fn.confirm(("Create %s?\n\n%s"):format(alt_rel, alt_abs), "&Yes\n&No", 2) + if choice ~= 1 then return end + + -- Ensure dirs exist, then open the new file (unsaved until you write) + local dir = dirpart(alt_abs) + if dir and not vim.loop.fs_stat(dir) then + vim.fn.mkdir(dir, "p") + end + vim.cmd.edit(vim.fn.fnameescape(alt_abs)) + -- Optional: write an empty file immediately. Comment out if you prefer unsaved buffer. + -- vim.cmd.write() +end + +return M diff --git a/.config/nvim/lua/core/config.lua b/.config/nvim/lua/core/config.lua new file mode 100644 index 0000000..c74b24f --- /dev/null +++ b/.config/nvim/lua/core/config.lua @@ -0,0 +1,40 @@ +local o = vim.opt + +-- Numbers/UI +o.number = true +o.relativenumber = true +o.signcolumn = "yes" +o.termguicolors = true +o.updatetime = 300 + +-- Indentation: 2 spaces, never tabs +o.expandtab = true -- insert spaces when you press +o.shiftwidth = 2 -- >> and << shift by 2 +o.tabstop = 2 -- a displays as 2 spaces +o.softtabstop = 2 -- unindents by 2 in insert mode +o.smartindent = true -- basic smart indenting for code + +o.clipboard = "unnamedplus" + +-- Diagnostics: right-side virtual text + nice floats + sorted severity +vim.diagnostic.config({ + virtual_text = { + spacing = 2, + prefix = "●", -- small dot; try "▎" or "" if you like + }, + severity_sort = true, + underline = true, + update_in_insert = false, -- don’t distract while typing + float = { + border = "rounded", + source = "if_many", + header = "", + prefix = "", + }, +}) + +-- Pretty signs in the gutter (left) +for type, icon in pairs({ Error="", Warn="", Hint="", Info="" }) do + local hl = "DiagnosticSign" .. type + vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" }) +end diff --git a/.config/nvim/lua/core/keybindings.lua b/.config/nvim/lua/core/keybindings.lua new file mode 100644 index 0000000..70e8453 --- /dev/null +++ b/.config/nvim/lua/core/keybindings.lua @@ -0,0 +1,58 @@ +-- Global/editor keymaps live here. +-- Tip: keep them conservative so they don't surprise language buffers. + +-- Global/editor keymaps only (language-specific go in e.g. c/keybindings.lua) +local map = vim.keymap.set + +-- Save / quit (kept from earlier) +-- map("n", "w", "write", { desc = "Write file" }) +map("n", "q", "quit", { desc = "Quit window" }) + +-- File explorer (loads Neo-tree on demand) +map("n", "ee", "Neotree toggle", { desc = "Explorer: toggle" }) +map("n", "er", "Neotree reveal", { desc = "Explorer: reveal current file" }) +map("n", "ef", "Neotree focus", { desc = "Explorer: focus" }) + +-- === Splits === +-- Horizontal split (under your left hand) +map("n", "-", "split", { desc = "Split: horizontal" }) +-- Vertical split (pipe looks like a vertical divider) +map("n", "|", "vsplit", { desc = "Split: vertical" }) + +-- === Window navigation (hjkl with Ctrl) === +-- These mirror Vim's native h/j/k/l but are faster to type +map("n", "", "h", { desc = "Window left" }) +map("n", "", "j", { desc = "Window down" }) +map("n", "", "k", { desc = "Window up" }) +map("n", "", "l", { desc = "Window right" }) + +-- which-key groups (v3 list-style spec) +local ok, wk = pcall(require, "which-key") -- this will load the plugin if needed +if ok then + wk.add({ + { "e", group = "Explorer" }, + { "f", group = "Find" }, + { "g", group = "Git" }, + { "w", group = "Write/Windows" }, -- optional grouping for your own keys + }) +end + +-- Find group (which-key already labels f as "Find") +map("n", "ff", "Telescope find_files", { desc = "Find files" }) +map("n", "fg", "Telescope live_grep", { desc = "Grep project" }) +map("n", "fb", "Telescope buffers", { desc = "Switch buffer" }) +map("n", "fh", "Telescope help_tags", { desc = "Help tags" }) +map("n", "fr", "Telescope oldfiles", { desc = "Recent files" }) +map("n", "fs", "Telescope grep_string", { desc = "Grep word under cursor" }) +local tb = require("telescope.builtin") +map("n", "fs", tb.lsp_workspace_symbols, { desc = "Find: workspace symbols (LSP fuzzy)" }) +map("n", "fd", tb.lsp_document_symbols, { desc = "Find: document symbols (LSP fuzzy)" }) + +-- Open a small hover with all diagnostics at the cursor +map("n", "xx", "Trouble diagnostics toggle", { desc="Diagnostics: Trouble list" }) + +-- Toggle right-side virtual text on/off (sometimes nice to declutter) +map("n", "xt", function() + local cfg = vim.diagnostic.config() + vim.diagnostic.config({ virtual_text = not cfg.virtual_text }) +end, { desc = "Diagnostics: toggle virtual text" }) diff --git a/.config/nvim/lua/core/plugins.lua b/.config/nvim/lua/core/plugins.lua new file mode 100644 index 0000000..7f28333 --- /dev/null +++ b/.config/nvim/lua/core/plugins.lua @@ -0,0 +1,231 @@ +-- All generic/global plugins live in this list. +return { + { + "rose-pine/neovim", + name = "rose-pine", + lazy = false, -- load at startup + priority = 1000, -- ensure theme loads before others + config = function() + require("rose-pine").setup({ + -- Variants: "auto" | "main" | "moon" | "dawn" + variant = "auto", + styles = { transparency = false }, + }) + vim.cmd.colorscheme("rose-pine") + end, + }, + -- Subtle indent guides + { + "lukas-reineke/indent-blankline.nvim", + main = "ibl", + event = { "BufReadPost", "BufNewFile" }, + opts = { + indent = { char = "│", tab_char = "│" }, -- faint vertical guides; theme controls color + scope = { enabled = false }, -- keep it subtle (no current-scope highlight) + exclude = { + filetypes = { "help", "neo-tree", "Trouble", "lazy", "mason", "dashboard" }, + buftypes = { "terminal", "nofile", "quickfix", "prompt" }, + }, + }, + }, + + -- Keybinding helper (shows hints as you type …) + { + "folke/which-key.nvim", + event = "VeryLazy", + opts = { + plugins = { spelling = true }, + win = { border = "rounded" }, + delay = 300, -- ms before popup; tweak if it feels slow/fast + }, + }, + + -- Statusline + { + "nvim-lualine/lualine.nvim", + event = "VeryLazy", + opts = { + options = { + theme = "rose-pine", -- or "auto" + icons_enabled = true, + globalstatus = true, + component_separators = { left = "│", right = "│" }, + section_separators = { left = " ", right = " " }, + }, + }, + }, + + -- File explorer (choose Neo-tree; single source of truth lives here) + { + "nvim-neo-tree/neo-tree.nvim", + cmd = "Neotree", -- lazy-load on command or the keymap below + dependencies = { + "nvim-lua/plenary.nvim", + "nvim-tree/nvim-web-devicons", -- optional, but nice + "MunifTanjim/nui.nvim", + }, + opts = { + sources = { "filesystem", "buffers", "git_status" }, + filesystem = { + follow_current_file = { enabled = true }, + hijack_netrw_behavior = "open_default", + filtered_items = { hide_gitignored = false }, + }, + window = { width = 32 }, + }, + }, + -- Telescope core + { + "nvim-telescope/telescope.nvim", + cmd = "Telescope", -- lazy-load on :Telescope (used by your keymaps) + dependencies = { "nvim-lua/plenary.nvim" }, + opts = { + defaults = { + -- small QoL tweaks; change anytime + sorting_strategy = "ascending", + layout_config = { prompt_position = "top" }, + mappings = { i = { [""] = false, [""] = false } }, + }, + pickers = { + find_files = { hidden = true }, -- show dotfiles; respects .gitignore + }, + }, + }, + + -- Faster sorting (native fzf) — optional but great + { + "nvim-telescope/telescope-fzf-native.nvim", + build = "make", + cond = function() return vim.fn.executable("make") == 1 end, + config = function() + pcall(function() require("telescope").load_extension("fzf") end) + end, + }, + + -- Nicer UI for vim.ui.select/inputs (optional) + { + "nvim-telescope/telescope-ui-select.nvim", + event = "VeryLazy", + config = function() + pcall(function() require("telescope").load_extension("ui-select") end) + end, + }, + -- Completion engine + { + "hrsh7th/nvim-cmp", + event = "InsertEnter", -- load when you start typing + dependencies = { + "hrsh7th/cmp-nvim-lsp", -- LSP source for cmp + "saadparwaiz1/cmp_luasnip", + { + "L3MON4D3/LuaSnip", -- snippet engine (needed for LSP snippet expansion) + config = function() + + pcall(require, "c.snippets") + + end + + } + }, + opts = function() + local cmp = require("cmp") + local luasnip = require("luasnip") + + return { + snippet = { + expand = function(args) luasnip.lsp_expand(args.body) end, + }, + window = { + completion = cmp.config.window.bordered(), + documentation = cmp.config.window.bordered(), + }, + mapping = cmp.mapping.preset.insert({ + [""] = cmp.mapping.complete(), -- manually trigger + [""] = cmp.mapping.confirm({ select = false }), -- confirm selected (don’t auto-pick) + [""] = cmp.mapping.abort(), + [""] = cmp.mapping.select_next_item(), + [""] = cmp.mapping.select_prev_item(), + [""] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_next_item() + elseif luasnip.expand_or_jumpable() then + luasnip.expand_or_jump() + else + fallback() + end + end, { "i", "s" }), + [""] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_prev_item() + elseif luasnip.jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end, { "i", "s" }), + }), + sources = { + { name = "nvim_lsp" }, -- LSP completions (clangd, etc.) + { name = "luasnip" }, + -- you can add { name = "path" }, { name = "buffer" } later if you want + }, + } + end, + }, + -- Auto-close (), {}, [], "", '' and more + { + "windwp/nvim-autopairs", + event = "InsertEnter", + opts = { + check_ts = true, -- respects treesitter contexts if you have it + disable_filetype = { "TelescopePrompt", "neo-tree" }, + fast_wrap = {}, -- enables fast wrap (optional) + }, + config = function(_, opts) + local npairs = require("nvim-autopairs") + npairs.setup(opts) + + -- Integrate with nvim-cmp so confirming a function adds () and places cursor inside + local ok_cmp, cmp = pcall(require, "cmp") + if ok_cmp then + local cmp_ap = require("nvim-autopairs.completion.cmp") + cmp.event:on("confirm_done", cmp_ap.on_confirm_done()) + end + + -- OPTIONAL (C quality-of-life): + -- Pair < > only when typing #include <...> + -- (By default, we *don't* pair < > in C to avoid accidental inserts.) + local Rule = require("nvim-autopairs.rule") + npairs.add_rules({ + Rule("<", ">", "c") + :with_pair(function(ctx) + local line = ctx.line:sub(1, ctx.col - 1) + return line:match("^%s*#%s*include%s*$") ~= nil + end), + }) + end, + }, + { + "nvim-treesitter/nvim-treesitter", + build = ":TSUpdate", + event = { "BufReadPre", "BufNewFile" }, + opts = { + ensure_installed = { "c", "lua", "vim", "vimdoc", "query", "bash", "make" }, + highlight = { enable = true }, + indent = { enable = false }, -- keep off; clang-format handles indent on save + incremental_selection = { + enable = true, + keymaps = { + init_selection = "gnn", -- start selection + node_incremental = "grn", -- grow to next node + node_decremental = "grm", -- shrink + scope_incremental = "grc", -- grow to scope + }, + }, + }, + config = function(_, opts) + require("nvim-treesitter.configs").setup(opts) + end, + }, + { "folke/trouble.nvim", cmd = { "Trouble", "TroubleToggle" }, opts = {} } +}