-- Bootstrap lazy.nvim local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" if not vim.uv.fs_stat(lazypath) then vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath, }) end vim.opt.rtp:prepend(lazypath) -- Leader vim.g.mapleader = " " vim.g.maplocalleader = " " -- ============================================================ -- Options (adjusted for a raw TTY) -- ============================================================ local opt = vim.opt -- Line numbers opt.number = true opt.relativenumber = true -- Indentation opt.expandtab = true opt.tabstop = 4 opt.shiftwidth = 4 opt.softtabstop = 4 -- Visual opt.cursorline = true opt.scrolloff = 20 opt.signcolumn = "yes" opt.list = true opt.listchars = { tab = " ", trail = ".", nbsp = "_" } opt.fillchars:append({ fold = "-" }) opt.termguicolors = false opt.colorcolumn = "" -- Behaviour opt.swapfile = false opt.undofile = true opt.updatetime = 250 opt.timeoutlen = 300 opt.splitbelow = true opt.splitright = true opt.ignorecase = true opt.smartcase = true opt.inccommand = "split" opt.hlsearch = true opt.breakindent = true -- Folding (nvim-ufo, see plugin list below) opt.foldcolumn = "0" opt.foldlevel = 99 opt.foldlevelstart = 99 opt.foldenable = true local map = vim.keymap.set -- ============================================================ -- Keymaps -- ============================================================ -- Disable Space in normal/visual (used as leader) map({ "n", "v" }, "", "", { silent = true }) -- Save map({ "n", "i" }, "", "w", { desc = "Save" }) -- Clear search highlight map("n", "", "nohlsearch") -- Exit terminal mode map("t", "", "", { desc = "Exit terminal" }) -- Better up/down on wrapped lines map({ "n", "v" }, "j", "gj") map({ "n", "v" }, "k", "gk") -- Buffer navigation map("n", "", "bprevious", { desc = "Prev buffer" }) map("n", "", "bnext", { desc = "Next buffer" }) map("n", "bd", "bdelete", { desc = "Delete buffer" }) -- Splits map("n", "sv", "v", { desc = "Split vertical" }) map("n", "sh", "s", { desc = "Split horizontal" }) map("n", "se", "=", { desc = "Equal splits" }) map("n", "sx", "close", { desc = "Close split" }) -- Move lines map("n", "", "m .+1==", { desc = "Move line down" }) map("n", "", "m .-2==", { desc = "Move line up" }) map("v", "", ":m '>+1gv=gv", { desc = "Move selection down" }) map("v", "", ":m '<-2gv=gv", { desc = "Move selection up" }) -- Stay in visual after indent map("v", "<", "", ">gv") -- ============================================================ -- Completion helper -- ============================================================ local function expand_menu() local cfg = require("blink.cmp.config").completion cfg.menu.max_height = 10 cfg.documentation.auto_show = true pcall(function() require("blink.cmp.completion.windows.menu").update_position() end) end vim.api.nvim_create_autocmd("User", { pattern = "BlinkCmpMenuClose", callback = function() local ok, cfg = pcall(function() return require("blink.cmp.config").completion end) if not ok then return end cfg.menu.max_height = 5 cfg.documentation.auto_show = false end, }) -- ============================================================ -- Fold text helper -- ============================================================ local function ufo_ascii_foldtext(virtText, lnum, endLnum, width, truncate) local newVirtText = {} local suffix = (" ... %d lines "):format(endLnum - lnum) local sufWidth = vim.fn.strdisplaywidth(suffix) local targetWidth = width - sufWidth local curWidth = 0 for _, chunk in ipairs(virtText) do local chunkText = chunk[1] local chunkWidth = vim.fn.strdisplaywidth(chunkText) if targetWidth > curWidth + chunkWidth then table.insert(newVirtText, chunk) else chunkText = truncate(chunkText, targetWidth - curWidth) local hlGroup = chunk[2] table.insert(newVirtText, { chunkText, hlGroup }) chunkWidth = vim.fn.strdisplaywidth(chunkText) if curWidth + chunkWidth < targetWidth then suffix = suffix .. (" "):rep(targetWidth - curWidth - chunkWidth) end break end curWidth = curWidth + chunkWidth end table.insert(newVirtText, { suffix, "Comment" }) return newVirtText end -- ============================================================ -- Plugins: leap, oil, treesitter, nvim-ufo, clangd, blink.cmp -- ============================================================ require("lazy").setup({ -- Motion: s + two chars to jump anywhere { url = "https://codeberg.org/andyg/leap.nvim", keys = { { "s", "(leap)", mode = { "n", "x", "o" }, desc = "Leap" }, { "S", "(leap-from-window)", mode = { "n", "o" }, desc = "Leap from window" }, }, }, -- Buffer-based file explorer { "stevearc/oil.nvim", lazy = false, keys = { { "e", "Oil", desc = "File explorer" }, { "-", "Oil", desc = "Open parent dir" }, }, opts = { default_file_explorer = true, view_options = { show_hidden = true }, constrain_cursor = false, }, }, -- Treesitter (syntax highlighting + textobjects) { "nvim-treesitter/nvim-treesitter", branch = "main", lazy = false, build = ":TSUpdate", dependencies = { { "nvim-treesitter/nvim-treesitter-textobjects", branch = "main" }, }, config = function() require("nvim-treesitter").setup({}) require("nvim-treesitter").install({ "c" }) vim.api.nvim_create_autocmd("FileType", { callback = function(args) local lang = vim.treesitter.language.get_lang(vim.bo[args.buf].filetype) if lang and vim.treesitter.language.add(lang) then pcall(vim.treesitter.start, args.buf, lang) vim.bo[args.buf].indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" end end, }) local ok_to, to = pcall(require, "nvim-treesitter-textobjects") if not ok_to then return end to.setup({ select = { lookahead = true }, move = { set_jumps = true }, }) local sel = require("nvim-treesitter-textobjects.select") local move = require("nvim-treesitter-textobjects.move") local swap = require("nvim-treesitter-textobjects.swap") local tmap = vim.keymap.set local objs = { { "af", "@function.outer", "around function" }, { "if", "@function.inner", "inside function" }, { "ac", "@class.outer", "around class" }, { "ic", "@class.inner", "inside class" }, { "aa", "@parameter.outer", "around argument" }, { "ia", "@parameter.inner", "inside argument" }, { "ab", "@block.outer", "around block" }, { "ib", "@block.inner", "inside block" }, { "ai", "@conditional.outer", "around conditional" }, { "ii", "@conditional.inner", "inside conditional" }, { "al", "@loop.outer", "around loop" }, { "il", "@loop.inner", "inside loop" }, } for _, o in ipairs(objs) do local lhs, q, d = o[1], o[2], o[3] tmap({ "x", "o" }, lhs, function() sel.select_textobject(q, "textobjects") end, { desc = d }) end local nmo = { "n", "x", "o" } tmap(nmo, "]m", function() move.goto_next_start("@function.outer", "textobjects") end, { desc = "next func start" }) tmap(nmo, "[m", function() move.goto_previous_start("@function.outer", "textobjects") end, { desc = "prev func start" }) tmap(nmo, "]M", function() move.goto_next_end("@function.outer", "textobjects") end, { desc = "next func end" }) tmap(nmo, "[M", function() move.goto_previous_end("@function.outer", "textobjects") end, { desc = "prev func end" }) tmap(nmo, "]]", function() move.goto_next_start("@class.outer", "textobjects") end, { desc = "next class start" }) tmap(nmo, "[[", function() move.goto_previous_start("@class.outer", "textobjects") end, { desc = "prev class start" }) tmap(nmo, "][", function() move.goto_next_end("@class.outer", "textobjects") end, { desc = "next class end" }) tmap(nmo, "[]", function() move.goto_previous_end("@class.outer", "textobjects") end, { desc = "prev class end" }) tmap("n", "a", function() swap.swap_next("@parameter.inner") end, { desc = "Swap arg with next" }) tmap("n", "A", function() swap.swap_previous("@parameter.inner") end, { desc = "Swap arg with prev" }) end, }, -- Folding { "kevinhwang91/nvim-ufo", dependencies = { "kevinhwang91/promise-async" }, event = "BufReadPost", keys = { { "zR", function() require("ufo").openAllFolds() end, desc = "Open all folds", }, { "zM", function() require("ufo").closeAllFolds() end, desc = "Close all folds", }, }, opts = { provider_selector = function() return { "treesitter", "indent" } end, fold_virt_text_handler = ufo_ascii_foldtext, }, }, -- Completion engine { "saghen/blink.cmp", version = "*", dependencies = { "L3MON4D3/LuaSnip", "rafamadriz/friendly-snippets" }, opts = { keymap = { preset = "none", [""] = { "show", "show_documentation", "hide_documentation" }, [""] = { "hide" }, [""] = { "accept", "fallback" }, [""] = { expand_menu, "select_next", "fallback" }, [""] = { "select_prev", "fallback" }, [""] = { "scroll_documentation_up", "fallback" }, [""] = { "scroll_documentation_down", "fallback" }, [""] = { "select_next", "fallback" }, [""] = { "select_prev", "fallback" }, [""] = { "snippet_forward", "fallback" }, [""] = { "snippet_backward", "fallback" }, }, sources = { default = { "lsp", "path", "snippets", "buffer" }, }, snippets = { preset = "luasnip" }, completion = { documentation = { auto_show = false, auto_show_delay_ms = 200 }, ghost_text = { enabled = false }, menu = { auto_show = true, max_height = 5, draw = { -- no kind_icon column -- plain text only, no nerd font glyphs columns = { { "label", "label_description", gap = 1 } }, }, }, }, }, }, { "L3MON4D3/LuaSnip", event = "InsertEnter", dependencies = { "rafamadriz/friendly-snippets" }, config = function() require("luasnip.loaders.from_vscode").lazy_load() end, }, -- LSP { "neovim/nvim-lspconfig", event = { "BufReadPre", "BufNewFile" }, dependencies = { "saghen/blink.cmp" }, config = function() vim.lsp.config("*", { capabilities = require("blink.cmp").get_lsp_capabilities(), }) vim.lsp.enable({ "clangd" }) vim.api.nvim_create_autocmd("LspAttach", { callback = function(ev) local m = function(keys, func, desc) vim.keymap.set("n", keys, func, { buffer = ev.buf, desc = "LSP: " .. desc }) end if vim.lsp.inlay_hint then vim.lsp.inlay_hint.enable(true, { bufnr = ev.buf }) end m("gd", vim.lsp.buf.definition, "Goto definition") m("gD", vim.lsp.buf.declaration, "Goto declaration") m("gr", vim.lsp.buf.references, "Goto references") m("gI", vim.lsp.buf.implementation, "Goto implementation") m("K", vim.lsp.buf.hover, "Hover docs") m("rn", vim.lsp.buf.rename, "Rename") m("[d", vim.diagnostic.goto_prev, "Prev diagnostic") m("]d", vim.diagnostic.goto_next, "Next diagnostic") m("e", vim.diagnostic.open_float, "Show diagnostic") end, }) vim.diagnostic.config({ virtual_text = true, signs = { text = { [vim.diagnostic.severity.ERROR] = "E", [vim.diagnostic.severity.WARN] = "W", [vim.diagnostic.severity.INFO] = "I", [vim.diagnostic.severity.HINT] = "H", }, }, underline = true, update_in_insert = false, }) end, }, }, { install = { colorscheme = { "vim" } }, checker = { enabled = false }, change_detection = { notify = false }, }) vim.cmd.colorscheme("vim")