-- Plugins 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.git", "--branch=stable", lazypath, }) end vim.opt.rtp:prepend(lazypath) require("lazy").setup({ { 'nvim-telescope/telescope.nvim', dependencies = { 'nvim-lua/plenary.nvim', { 'nvim-telescope/telescope-fzf-native.nvim', build = 'make' }, }, keys = { { 'ff', 'Telescope find_files' }, { 'fg', 'Telescope live_grep' }, { 'fb', 'Telescope buffers' }, }, }, { "rcarriga/nvim-dap-ui", dependencies = { "mfussenegger/nvim-dap", "nvim-neotest/nvim-nio" }, }, { 'lewis6991/gitsigns.nvim', event = { 'BufReadPre', 'BufNewFile' }, opts = { signs = { add = { text = '+' }, change = { text = '~' }, delete = { text = '_' }, topdelete = { text = '-' }, changedelete = { text = '~' }, }, }, }, { "iamcco/markdown-preview.nvim", cmd = { "MarkdownPreviewToggle", "MarkdownPreview", "MarkdownPreviewStop" }, build = "cd app && npm install", init = function() vim.g.mkdp_filetypes = { "markdown" } end, ft = { "markdown" }, }, }) -- theme & transparency vim.cmd.colorscheme("unokai") vim.api.nvim_set_hl(0, "Normal", { bg = "none" }) vim.api.nvim_set_hl(0, "NormalNC", { bg = "none" }) vim.api.nvim_set_hl(0, "EndOfBuffer", { bg = "none" }) -- Basic settings vim.opt.number = true -- Line numbers vim.opt.relativenumber = true -- Relative line numbers vim.opt.cursorline = true -- Highlight current line vim.opt.wrap = false -- Don't wrap lines vim.opt.scrolloff = 10 -- Keep 10 lines above/below cursor vim.opt.list = true -- Enable list mode for overflow indicators vim.opt.listchars = { extends = '…', precedes = '…', tab = ' ' } vim.opt.sidescrolloff = 1 -- Keep 1 column for the overflow indicator -- Indentation vim.opt.tabstop = 2 -- Tab width vim.opt.shiftwidth = 2 -- Indent width vim.opt.softtabstop = 2 -- Soft tab stop vim.opt.expandtab = true -- Use spaces instead of tabs vim.opt.smartindent = true -- Smart auto-indenting vim.opt.autoindent = true -- Copy indent from current line -- Search settings vim.opt.ignorecase = true -- Case insensitive search vim.opt.smartcase = true -- Case sensitive if uppercase in search vim.opt.hlsearch = true -- Highlight search results vim.opt.incsearch = true -- Show matches as you type -- Visual settings vim.opt.termguicolors = true -- Enable 24-bit colors vim.opt.signcolumn = "yes" -- Always show sign column vim.opt.colorcolumn = "100" -- Show column at 100 characters vim.opt.showmatch = true -- Highlight matching brackets vim.opt.matchtime = 2 -- How long to show matching bracket vim.opt.cmdheight = 1 -- Command line height vim.opt.completeopt = "menuone,noinsert,noselect" -- Completion options vim.opt.showmode = false -- Don't show mode in command line vim.opt.pumheight = 10 -- Popup menu height vim.opt.pumblend = 10 -- Popup menu transparency vim.opt.winblend = 0 -- Floating window transparency vim.opt.conceallevel = 0 -- Don't hide markup vim.opt.concealcursor = "" -- Don't hide cursor line markup vim.opt.lazyredraw = true -- Don't redraw during macros vim.opt.synmaxcol = 300 -- Syntax highlighting limit -- File handling vim.opt.backup = false -- Don't create backup files vim.opt.writebackup = false -- Don't create backup before writing vim.opt.swapfile = false -- Don't create swap files vim.opt.undofile = true -- Persistent undo vim.opt.undodir = vim.fn.expand("~/.vim/undodir") -- Undo directory vim.opt.updatetime = 300 -- Faster completion vim.opt.timeoutlen = 500 -- Key timeout duration vim.opt.ttimeoutlen = 0 -- Key code timeout vim.opt.autoread = true -- Auto reload files changed outside vim vim.opt.autowrite = false -- Don't auto save -- Behavior settings vim.opt.hidden = true -- Allow hidden buffers vim.opt.errorbells = false -- No error bells vim.opt.backspace = "indent,eol,start" -- Better backspace behavior vim.opt.autochdir = false -- Don't auto change directory vim.opt.iskeyword:append("-") -- Treat dash as part of word vim.opt.path:append("**") -- include subdirectories in search vim.opt.selection = "exclusive" -- Selection behavior vim.opt.mouse = "a" -- Enable mouse support vim.opt.clipboard:append("unnamedplus") -- Use system clipboard vim.opt.modifiable = true -- Allow buffer modifications vim.opt.encoding = "UTF-8" -- Set encoding -- Cursor settings vim.opt.guicursor = "n-v-c:block,i-ci-ve:block,r-cr:hor20,o:hor50,a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor,sm:block-blinkwait175-blinkoff150-blinkon175" -- Normal mode mappings vim.keymap.set("n", "c", ":nohlsearch", { desc = "Clear search highlights" }) -- Delete without yanking vim.keymap.set({ "n", "v" }, "d", '"_d', { desc = "Delete without yanking" }) -- Move lines up/down vim.keymap.set("n", "", ":m .+1==", { desc = "Move line down" }) vim.keymap.set("n", "", ":m .-2==", { desc = "Move line up" }) vim.keymap.set("v", "", ":m '>+1gv=gv", { desc = "Move selection down" }) vim.keymap.set("v", "", ":m '<-2gv=gv", { desc = "Move selection up" }) -- Better indenting in visual mode vim.keymap.set("v", "<", "", ">gv", { desc = "Indent right and reselect" }) -- Quick file navigation vim.keymap.set("n", "e", ":Explore", { desc = "Open file explorer" }) -- Netrw: h/l to navigate directory tree vim.api.nvim_create_autocmd("FileType", { group = augroup, pattern = "netrw", callback = function() local opts = { buffer = true, remap = true } vim.keymap.set("n", "h", "-", opts) -- go up a directory vim.keymap.set("n", "l", "", opts) -- open file/directory end, }) -- Better J behavior vim.keymap.set("n", "J", "mzJ`z", { desc = "Join lines and keep cursor position" }) -- ============================================================================ -- USEFUL FUNCTIONS -- ============================================================================ -- Copy Full File-Path vim.keymap.set("n", "pa", function() local path = vim.fn.expand("%:p") vim.fn.setreg("+", path) print("file:", path) end) -- Basic autocommands local augroup = vim.api.nvim_create_augroup("UserConfig", {}) -- Highlight yanked text vim.api.nvim_create_autocmd("TextYankPost", { group = augroup, callback = function() vim.highlight.on_yank() end, }) -- Return to last edit position when opening files vim.api.nvim_create_autocmd("BufReadPost", { group = augroup, callback = function() local mark = vim.api.nvim_buf_get_mark(0, '"') local lcount = vim.api.nvim_buf_line_count(0) if mark[1] > 0 and mark[1] <= lcount then pcall(vim.api.nvim_win_set_cursor, 0, mark) end end, }) -- Set filetype-specific settings vim.api.nvim_create_autocmd("FileType", { group = augroup, pattern = { "lua", "python" }, callback = function() vim.opt_local.tabstop = 4 vim.opt_local.shiftwidth = 4 end, }) vim.api.nvim_create_autocmd("FileType", { group = augroup, pattern = { "javascript", "typescript", "json", "html", "css" }, callback = function() vim.opt_local.tabstop = 2 vim.opt_local.shiftwidth = 2 end, }) -- Auto-close terminal when process exits vim.api.nvim_create_autocmd("TermClose", { group = augroup, callback = function() if vim.v.event.status == 0 then vim.api.nvim_buf_delete(0, {}) end end, }) -- Disable line numbers in terminal vim.api.nvim_create_autocmd("TermOpen", { group = augroup, callback = function() vim.opt_local.number = false vim.opt_local.relativenumber = false vim.opt_local.signcolumn = "no" end, }) -- Create directories when saving files vim.api.nvim_create_autocmd("BufWritePre", { group = augroup, callback = function() local dir = vim.fn.expand(':p:h') if vim.fn.isdirectory(dir) == 0 then vim.fn.mkdir(dir, 'p') end end, }) -- Command-line completion vim.opt.wildmenu = true vim.opt.wildmode = "longest:full,full" vim.opt.wildignore:append({ "*.o", "*.obj", "*.pyc", "*.class", "*.jar" }) -- Better diff options vim.opt.diffopt:append("linematch:60") -- Performance improvements vim.opt.redrawtime = 10000 vim.opt.maxmempattern = 20000 -- Create undo directory if it doesn't exist local undodir = vim.fn.expand("~/.vim/undodir") if vim.fn.isdirectory(undodir) == 0 then vim.fn.mkdir(undodir, "p") end -- ============================================================================ -- STATUSLINE -- ============================================================================ local function statusline_mode() local modes = { n = "NORMAL", i = "INSERT", v = "VISUAL", V = "V-LINE", ["\22"] = "V-BLOCK", c = "COMMAND", R = "REPLACE", } return modes[vim.fn.mode()] or vim.fn.mode():upper() end local function statusline_branch() local branch = vim.b.gitsigns_head if branch and branch ~= "" then return " " .. branch end return "" end _G.statusline_mode = statusline_mode _G.statusline_branch = statusline_branch vim.api.nvim_set_hl(0, "StatusLineBold", { bold = true }) vim.api.nvim_create_autocmd({ "WinEnter", "BufEnter" }, { callback = function() vim.opt_local.statusline = " %#StatusLineBold#%{v:lua.statusline_mode()}%#StatusLine#" .. " %f%m%r%{v:lua.statusline_branch()}" .. "%=" .. "%y %l:%c %P " end }) vim.api.nvim_create_autocmd({ "WinLeave", "BufLeave" }, { callback = function() vim.opt_local.statusline = " %f%m%r%= %l:%c %P " end }) -- ============================================================================ -- LSP -- ============================================================================ -- Function to find project root local function find_root(patterns) local path = vim.fn.expand('%:p:h') local root = vim.fs.find(patterns, { path = path, upward = true })[1] return root and vim.fn.fnamemodify(root, ':h') or path end -- Shell LSP setup local function setup_shell_lsp() vim.lsp.start({ name = 'bashls', cmd = { 'bash-language-server', 'start' }, filetypes = { 'sh', 'bash', 'zsh' }, root_dir = find_root({ '.git', 'Makefile' }), settings = { bashIde = { globPattern = "*@(.sh|.inc|.bash|.command)" } } }) end -- Python LSP setup local function setup_python_lsp() vim.lsp.start({ name = 'pylsp', cmd = { 'pylsp' }, filetypes = { 'python' }, root_dir = find_root({ 'pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt', '.git' }), settings = { pylsp = { plugins = { pycodestyle = { enabled = false }, flake8 = { enabled = true, }, black = { enabled = true } } } } }) end -- C/C++ LSP setup (clangd) local function setup_clangd() vim.lsp.start({ name = 'clangd', cmd = { 'clangd', '--background-index' }, root_dir = find_root({ 'compile_commands.json', 'CMakeLists.txt', 'Makefile', '.git' }), }) end -- Rust LSP setup (rust-analyzer) local function setup_rust_analyzer() vim.lsp.start({ name = 'rust-analyzer', cmd = { 'rust-analyzer' }, root_dir = find_root({ 'Cargo.toml', '.git' }), settings = { ['rust-analyzer'] = { check = { command = 'clippy' }, } } }) end -- Auto-start LSPs based on filetype vim.api.nvim_create_autocmd('FileType', { pattern = 'sh,bash,zsh', callback = setup_shell_lsp, desc = 'Start shell LSP' }) vim.api.nvim_create_autocmd('FileType', { pattern = 'python', callback = setup_python_lsp, desc = 'Start Python LSP' }) vim.api.nvim_create_autocmd('FileType', { pattern = 'c,cpp', callback = setup_clangd, desc = 'Start clangd LSP' }) vim.api.nvim_create_autocmd('FileType', { pattern = 'rust', callback = setup_rust_analyzer, desc = 'Start rust-analyzer LSP' }) -- formatting local function format_code() local bufnr = vim.api.nvim_get_current_buf() local filename = vim.api.nvim_buf_get_name(bufnr) local filetype = vim.bo[bufnr].filetype -- Save cursor position local cursor_pos = vim.api.nvim_win_get_cursor(0) if filetype == 'python' or filename:match('%.py$') then if filename == '' then print("Save the file first before formatting Python") return end local black_cmd = "black --quiet " .. vim.fn.shellescape(filename) local black_result = vim.fn.system(black_cmd) if vim.v.shell_error == 0 then vim.cmd('checktime') vim.api.nvim_win_set_cursor(0, cursor_pos) print("Formatted with black") return else print("No Python formatter available (install black)") return end end if filetype == 'sh' or filetype == 'bash' or filename:match('%.sh$') then local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local content = table.concat(lines, '\n') local cmd = { 'shfmt', '-i', '2', '-ci', '-sr' } -- 2 spaces, case indent, space redirects local result = vim.fn.system(cmd, content) if vim.v.shell_error == 0 then local formatted_lines = vim.split(result, '\n') if formatted_lines[#formatted_lines] == '' then table.remove(formatted_lines) end vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, formatted_lines) vim.api.nvim_win_set_cursor(0, cursor_pos) print("Shell script formatted with shfmt") return else print("shfmt error: " .. result) return end end print("No formatter available for " .. filetype) end vim.api.nvim_create_user_command("FormatCode", format_code, { desc = "Format current file" }) vim.keymap.set('n', 'fm', format_code, { desc = 'Format file' }) -- LSP keymaps vim.api.nvim_create_autocmd('LspAttach', { callback = function(event) local opts = { buffer = event.buf } -- Navigation vim.keymap.set('n', 'gD', vim.lsp.buf.definition, opts) vim.keymap.set('n', 'gs', vim.lsp.buf.declaration, opts) vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts) vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts) -- Information vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts) vim.keymap.set('n', '', vim.lsp.buf.signature_help, opts) -- Code actions vim.keymap.set('n', 'ca', vim.lsp.buf.code_action, opts) vim.keymap.set('n', 'rn', vim.lsp.buf.rename, opts) -- Diagnostics vim.keymap.set('n', 'nd', vim.diagnostic.goto_next, opts) vim.keymap.set('n', 'pd', vim.diagnostic.goto_prev, opts) vim.keymap.set('n', 'd', vim.diagnostic.open_float, opts) vim.keymap.set('n', 'q', vim.diagnostic.setloclist, opts) end, }) -- Better LSP UI vim.diagnostic.config({ virtual_text = { prefix = '●' }, signs = true, underline = true, update_in_insert = false, severity_sort = true, }) vim.diagnostic.config({ signs = { text = { [vim.diagnostic.severity.ERROR] = "✗", [vim.diagnostic.severity.WARN] = "⚠", [vim.diagnostic.severity.INFO] = "ℹ", [vim.diagnostic.severity.HINT] = "💡", } } }) vim.api.nvim_create_user_command('LspInfo', function() local clients = vim.lsp.get_clients({ bufnr = 0 }) if #clients == 0 then print("No LSP clients attached to current buffer") else for _, client in ipairs(clients) do print("LSP: " .. client.name .. " (ID: " .. client.id .. ")") end end end, { desc = 'Show LSP client info' })