Created
March 9, 2026 12:26
-
-
Save cdecompilador/cf07db88b3a6156874de3a482d19d568 to your computer and use it in GitHub Desktop.
neovim config 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| vim.cmd("syntax on") | |
| -- General | |
| vim.opt.swapfile = false | |
| vim.opt.number = true | |
| vim.opt.relativenumber = true | |
| vim.opt.ruler = true | |
| vim.opt.colorcolumn = "100" | |
| -- Searching | |
| vim.opt.hlsearch = true | |
| vim.opt.incsearch = true | |
| vim.opt.ignorecase = true | |
| vim.opt.smartcase = true | |
| vim.opt.showmatch = false | |
| -- Whitespace | |
| vim.opt.wrap = true | |
| vim.opt.tabstop = 4 | |
| vim.opt.shiftwidth = 4 | |
| vim.opt.softtabstop = 4 | |
| vim.opt.expandtab = false | |
| vim.opt.shiftround = false | |
| -- Rendering | |
| vim.opt.ttyfast = true | |
| vim.opt.visualbell = false | |
| -- Indenting | |
| vim.opt.smartindent = true | |
| -- C/C++ indent style | |
| vim.api.nvim_create_autocmd({ "BufNewFile", "BufRead" }, { | |
| pattern = { "*.c", "*.cpp", "*.h" }, | |
| callback = function() | |
| vim.opt_local.cindent = true | |
| vim.opt_local.cinoptions = ":0,g0,t0" | |
| end, | |
| }) | |
| -- Mouse | |
| vim.opt.mouse = "a" | |
| -- Disable auto comment leader on 'o' | |
| vim.api.nvim_create_autocmd("FileType", { | |
| pattern = "*", | |
| callback = function() | |
| vim.opt_local.formatoptions:remove("o") | |
| end, | |
| }) | |
| -- Remaps | |
| vim.g.mapleader = " " | |
| -- Disable search highlight with <space><space> | |
| vim.keymap.set("n", "<space><space>", ":noh<cr>", { silent = true }) | |
| -- Python: swap ' and " | |
| vim.api.nvim_create_autocmd({ "BufNewFile", "BufRead" }, { | |
| pattern = "*.py", | |
| callback = function() | |
| vim.keymap.set("i", "'", '"', { buffer = true }) | |
| vim.keymap.set("i", '"', "'", { buffer = true }) | |
| end, | |
| }) | |
| -- Colors | |
| vim.api.nvim_set_hl(0, "Search", { ctermbg = "LightGray" }) | |
| vim.api.nvim_set_hl(0, "Visual", { ctermfg = "LightGray" }) | |
| vim.api.nvim_set_hl(0, "QuickFixLine", { ctermfg = "LightGray" }) | |
| -- Tags: auto-regenerate on save | |
| vim.api.nvim_create_autocmd("BufWritePost", { | |
| pattern = "*", | |
| callback = function() | |
| vim.fn.jobstart({ "ctags", "-R", ".", "--sort=yes", "--sort=foldcase" }) | |
| end, | |
| }) | |
| vim.opt.cursorline = true | |
| vim.cmd.colorscheme("vim") | |
| vim.opt.tags = "tags" | |
| vim.keymap.set("n", "gd", "<C-]>") | |
| vim.keymap.set("n", "gi", "<C-]>") | |
| -- vim.keymap.set("n", "gr", ":vimgrep <C-R><C-W> **/*.{c,cpp} <CR>:copen<CR>") | |
| vim.api.nvim_create_autocmd("VimEnter", { | |
| callback = function() | |
| local build_file = vim.fn.getcwd() .. "/build.bat" | |
| if vim.fn.filereadable(build_file) == 0 then return end | |
| vim.opt.makeprg = "build.bat" | |
| local content = table.concat(vim.fn.readfile(build_file), "\n"):lower() | |
| if content:find("clang") or content:find("gcc") or content:find("g++") then | |
| vim.opt.errorformat = "%f:%l:%c: %t%*[^:]: %m,%f:%l:%c: %m" | |
| elseif content:find("cl ") or content:find("cl.exe") then | |
| vim.opt.errorformat = "%f(%l): %t%*[^:]: %m,%f(%l,%c): %t%*[^:]: %m" | |
| end | |
| end, | |
| }) | |
| vim.o.grepprg = "rg --glob \"!tags\" --vimgrep --smart-case --fixed-strings" | |
| vim.o.grepformat = "%f:%l:%c:%m" | |
| local function get_main_win() | |
| local best_win = nil | |
| local best_height = -1 | |
| for _, win in ipairs(vim.api.nvim_list_wins()) do | |
| local cfg = vim.api.nvim_win_get_config(win) | |
| if cfg.relative == "" then | |
| local h = vim.api.nvim_win_get_height(win) | |
| if h > best_height then | |
| best_height = h | |
| best_win = win | |
| end | |
| end | |
| end | |
| return best_win | |
| end | |
| -- format_line(item) puede devolver un string o una tabla de strings (para | |
| -- errores de build que ocupan varias líneas). | |
| -- preview_fn(item, win) es opcional; si no se pasa, se abre el fichero. | |
| local function show_qf_with_preview(qflist, title, format_line, preview_fn, on_buf_ready, height) | |
| local main_win = get_main_win() | |
| local original_buf = vim.api.nvim_win_get_buf(main_win) | |
| local original_pos = vim.api.nvim_win_get_cursor(main_win) | |
| -- Guardar alturas antes de abrir el split | |
| local win_heights = {} | |
| for _, win in ipairs(vim.api.nvim_list_wins()) do | |
| local cfg = vim.api.nvim_win_get_config(win) | |
| if cfg.relative == "" then | |
| win_heights[win] = vim.api.nvim_win_get_height(win) | |
| end | |
| end | |
| vim.api.nvim_set_current_win(main_win) | |
| vim.cmd("botright " .. (height or 1) .. "split") | |
| local qf_win = vim.api.nvim_get_current_win() | |
| local qf_buf = vim.api.nvim_create_buf(false, true) | |
| vim.api.nvim_win_set_buf(qf_win, qf_buf) | |
| -- Restaurar alturas tras el split | |
| for win, h in pairs(win_heights) do | |
| if vim.api.nvim_win_is_valid(win) and win ~= qf_win then | |
| pcall(vim.api.nvim_win_set_height, win, h) | |
| end | |
| end | |
| vim.api.nvim_win_set_height(qf_win, height or 1) | |
| vim.wo[qf_win].statusline = " " .. title | |
| vim.wo[qf_win].number = false | |
| vim.wo[qf_win].relativenumber = false | |
| vim.wo[qf_win].cursorline = true | |
| vim.wo[qf_win].wrap = true | |
| vim.wo[qf_win].breakindent = true | |
| vim.wo[qf_win].breakindentopt = "shift:5" | |
| vim.bo[qf_buf].buftype = "nofile" | |
| vim.bo[qf_buf].bufhidden = "wipe" | |
| -- Construir líneas del buffer y mapa línea -> índice de item | |
| local buf_lines = {} | |
| local line_to_item = {} | |
| for i, item in ipairs(qflist) do | |
| local formatted = format_line(item) | |
| local block = type(formatted) == "table" and formatted or { formatted } | |
| for _, l in ipairs(block) do | |
| table.insert(buf_lines, l) | |
| line_to_item[#buf_lines] = i | |
| end | |
| end | |
| vim.api.nvim_buf_set_lines(qf_buf, 0, -1, false, buf_lines) | |
| vim.bo[qf_buf].modifiable = false | |
| if on_buf_ready then | |
| on_buf_ready(qf_buf, line_to_item, qflist) | |
| end | |
| local function current_item() | |
| if not vim.api.nvim_win_is_valid(qf_win) then return nil end | |
| local cursor_line = vim.api.nvim_win_get_cursor(qf_win)[1] | |
| local idx = line_to_item[cursor_line] | |
| return idx and qflist[idx] or nil | |
| end | |
| local function show_preview() | |
| local item = current_item() | |
| if not item then return end | |
| if preview_fn then | |
| preview_fn(item, main_win) | |
| else | |
| local bufnr = (item.bufnr and item.bufnr ~= 0) and item.bufnr | |
| or vim.fn.bufadd(item.filename) | |
| vim.fn.bufload(bufnr) | |
| vim.api.nvim_win_set_buf(main_win, bufnr) | |
| pcall(vim.api.nvim_win_set_cursor, main_win, { item.lnum, (item.col or 1) - 1 }) | |
| vim.api.nvim_win_call(main_win, function() vim.cmd("normal! zz") end) | |
| end | |
| end | |
| show_preview() | |
| local augroup = vim.api.nvim_create_augroup("QfPreview", { clear = true }) | |
| vim.api.nvim_create_autocmd("CursorMoved", { | |
| group = augroup, | |
| buffer = qf_buf, | |
| callback = show_preview, | |
| }) | |
| local function save_heights() | |
| local heights = {} | |
| for _, win in ipairs(vim.api.nvim_list_wins()) do | |
| local cfg = vim.api.nvim_win_get_config(win) | |
| if cfg.relative == "" and win ~= qf_win then | |
| heights[win] = vim.api.nvim_win_get_height(win) | |
| end | |
| end | |
| return heights | |
| end | |
| local function restore_heights(heights) | |
| for win, h in pairs(heights) do | |
| if vim.api.nvim_win_is_valid(win) then | |
| pcall(vim.api.nvim_win_set_height, win, h) | |
| end | |
| end | |
| end | |
| local function close_qf() | |
| vim.api.nvim_del_augroup_by_name("QfPreview") | |
| if vim.api.nvim_win_is_valid(qf_win) then | |
| vim.api.nvim_win_close(qf_win, true) | |
| end | |
| end | |
| -- <CR>: confirmar selección | |
| vim.keymap.set("n", "<CR>", function() | |
| local item = current_item() | |
| close_qf() | |
| -- sin restore_heights aquí | |
| if item then | |
| local bufnr = (item.bufnr and item.bufnr ~= 0) and item.bufnr | |
| or vim.fn.bufadd(item.filename) | |
| vim.fn.bufload(bufnr) | |
| vim.api.nvim_set_current_win(main_win) | |
| vim.api.nvim_win_set_buf(main_win, bufnr) | |
| pcall(vim.api.nvim_win_set_cursor, main_win, { item.lnum, (item.col or 1) - 1 }) | |
| vim.cmd("normal! zz") | |
| end | |
| end, { buffer = qf_buf }) | |
| -- <Esc> / q: cancelar y restaurar | |
| local function cancel() | |
| close_qf() | |
| vim.api.nvim_win_set_buf(main_win, original_buf) | |
| vim.api.nvim_set_current_win(main_win) | |
| pcall(vim.api.nvim_win_set_cursor, main_win, original_pos) | |
| vim.cmd("normal! zz") | |
| end | |
| vim.keymap.set("n", "<Esc>", cancel, { buffer = qf_buf }) | |
| vim.keymap.set("n", "q", cancel, { buffer = qf_buf }) | |
| end | |
| vim.keymap.set("n", "<leader>b", function() | |
| local build_file = vim.fn.getcwd() .. "/build.bat" | |
| local cmd, parse_header | |
| if vim.fn.filereadable(build_file) == 1 then | |
| cmd = "build.bat 2>&1" | |
| parse_header = function(line) | |
| local fname, lnum, col, kind = line:match("^([^:]+):(%d+):(%d+): (%w+):") | |
| if fname and (kind == "error" or kind == "warning") then | |
| return fname, lnum, col, kind | |
| end | |
| end | |
| elseif vim.bo.filetype == "python" then | |
| cmd = "ruff check --output-format concise " .. vim.fn.shellescape(vim.fn.expand("%")) .. " 2>&1" | |
| parse_header = function(line) | |
| local fname, lnum, col, msg = line:match("^(.+):(%d+):(%d+): (.+)$") | |
| if fname then | |
| return fname, lnum, col, "warning" | |
| end | |
| end | |
| else | |
| print("no build.bat found") | |
| return | |
| end | |
| local output = vim.fn.systemlist(cmd) | |
| local items = {} | |
| local current = nil | |
| local function flush() | |
| if current then table.insert(items, current) end | |
| current = nil | |
| end | |
| for _, raw_line in ipairs(output) do | |
| local line = raw_line:gsub("\r", "") | |
| local fname, lnum, col, kind = parse_header(line) | |
| if fname then | |
| flush() | |
| current = { | |
| filename = fname, | |
| lnum = tonumber(lnum), | |
| col = tonumber(col), | |
| kind = kind, | |
| lines = { line }, | |
| } | |
| elseif current then | |
| table.insert(current.lines, line) | |
| end | |
| end | |
| flush() | |
| if #items == 0 then | |
| print(vim.bo.filetype == "python" and "lint ok" or "build ok") | |
| return | |
| end | |
| local title = (vim.bo.filetype == "python" and "lint: " or "build: ") .. #items .. " issues" | |
| show_qf_with_preview( | |
| items, | |
| title, | |
| function(item) | |
| local fname = vim.fn.fnamemodify(item.filename, ":.") | |
| local msg = item.lines[1]:match(": (.+)$") or "" | |
| local result = { string.format("%s:%d %s", fname, item.lnum, msg) } | |
| for i = 2, #item.lines do | |
| table.insert(result, " " .. item.lines[i]) | |
| end | |
| return result | |
| end, | |
| nil, | |
| function(buf, line_to_item, qflist) | |
| vim.api.nvim_set_hl(0, "QfError", { ctermfg = "Red", fg = "#ff5555" }) | |
| vim.api.nvim_set_hl(0, "QfWarning", { ctermfg = "Yellow", fg = "#f1fa8c" }) | |
| for line_idx, item_idx in pairs(line_to_item) do | |
| local item = qflist[item_idx] | |
| local fname = vim.fn.fnamemodify(item.filename, ":.") | |
| local buf_line = vim.api.nvim_buf_get_lines(buf, line_idx - 1, line_idx, false)[1] or "" | |
| if buf_line:match("^" .. vim.pesc(fname)) then | |
| local hl = item.kind == "error" and "QfError" or "QfWarning" | |
| local prefix_len = #fname + 1 + #tostring(item.lnum) | |
| vim.api.nvim_buf_add_highlight(buf, -1, hl, line_idx - 1, 0, prefix_len) | |
| end | |
| end | |
| end, | |
| 20 | |
| ) | |
| end, { silent = true }) | |
| -- Find file | |
| vim.keymap.set("n", "<leader>f", function() | |
| local query = vim.fn.input("find file: ") | |
| if query == "" then return end | |
| local results = vim.fn.systemlist("fd --type f " .. vim.fn.shellescape(query)) | |
| if #results == 0 then | |
| print("no files found") | |
| return | |
| end | |
| local qflist = {} | |
| for _, file in ipairs(results) do | |
| table.insert(qflist, { filename = file, lnum = 1, col = 1, text = "" }) | |
| end | |
| show_qf_with_preview(qflist, "find: " .. query, function(item) | |
| return vim.fn.fnamemodify(item.filename, ":.") | |
| end) | |
| end) | |
| local function get_visual_selection() | |
| local lines = vim.fn.getregion(vim.fn.getpos("v"), vim.fn.getpos(".")) | |
| return table.concat(lines, "\n") | |
| end | |
| local function do_grep(default) | |
| local query = vim.fn.input("file grep: ", default or "") | |
| if query == "" then return end | |
| vim.cmd("silent grep! " .. vim.fn.shellescape(query)) | |
| local qflist = vim.fn.getqflist() | |
| if #qflist == 0 then | |
| print("No results for: " .. query) | |
| return | |
| end | |
| show_qf_with_preview(qflist, "grep: " .. query, function(item) | |
| local fname = vim.fn.fnamemodify( | |
| item.bufnr ~= 0 and vim.fn.bufname(item.bufnr) or item.filename, ":.") | |
| local text = item.text:gsub("^%s+", "") | |
| return string.format("%-40s %4d %s", fname, item.lnum, text) | |
| end) | |
| end | |
| vim.keymap.set("n", "<leader>g", function() do_grep() end) | |
| vim.keymap.set("v", "<leader>g", function() do_grep(get_visual_selection()) end) | |
| vim.keymap.set("n", "gr", function() | |
| local word = vim.fn.expand("<cword>") | |
| if word == "" then return end | |
| vim.cmd("silent grep! " .. vim.fn.shellescape(word)) | |
| local qflist = vim.fn.getqflist() | |
| if #qflist == 0 then | |
| print("no references for: " .. word) | |
| return | |
| end | |
| show_qf_with_preview(qflist, "refs: " .. word, function(item) | |
| local fname = vim.fn.fnamemodify( | |
| item.bufnr ~= 0 and vim.fn.bufname(item.bufnr) or item.filename, ":.") | |
| local text = item.text:gsub("^%s+", "") | |
| return string.format("%-40s %4d %s", fname, item.lnum, text) | |
| end) | |
| end) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment