-- 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' }, }, }, { 'lewis6991/gitsigns.nvim', event = { 'BufReadPre', 'BufNewFile' }, opts = { signs = { add = { text = '+' }, change = { text = '~' }, delete = { text = '_' }, topdelete = { text = '-' }, changedelete = { text = '~' }, }, }, }, }) -- 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" -- Split behavior vim.opt.splitbelow = true -- Horizontal splits go below vim.opt.splitright = true -- Vertical splits go right -- Normal mode mappings vim.keymap.set("n", "c", ":nohlsearch", { desc = "Clear search highlights" }) -- Center screen when jumping vim.keymap.set("n", "n", "nzzzv", { desc = "Next search result (centered)" }) vim.keymap.set("n", "N", "Nzzzv", { desc = "Previous search result (centered)" }) vim.keymap.set("n", "", "zz", { desc = "Half page down (centered)" }) vim.keymap.set("n", "", "zz", { desc = "Half page up (centered)" }) -- Delete without yanking vim.keymap.set({ "n", "v" }, "d", '"_d', { desc = "Delete without yanking" }) -- Buffer navigation vim.keymap.set("n", "bn", ":bnext", { desc = "Next buffer" }) vim.keymap.set("n", "bp", ":bprevious", { desc = "Previous buffer" }) -- Better window navigation vim.keymap.set("n", "", "h", { desc = "Move to left window" }) vim.keymap.set("n", "", "j", { desc = "Move to bottom window" }) vim.keymap.set("n", "", "k", { desc = "Move to top window" }) vim.keymap.set("n", "", "l", { desc = "Move to right window" }) -- Splitting & Resizing vim.keymap.set("n", "sv", ":vsplit", { desc = "Split window vertically" }) vim.keymap.set("n", "sh", ":split", { desc = "Split window horizontally" }) vim.keymap.set("n", "", ":resize +2", { desc = "Increase window height" }) vim.keymap.set("n", "", ":resize -2", { desc = "Decrease window height" }) vim.keymap.set("n", "", ":vertical resize -2", { desc = "Decrease window width" }) vim.keymap.set("n", "", ":vertical resize +2", { desc = "Increase window width" }) -- 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" }) vim.keymap.set("n", "ff", ":find ", { desc = "Find file" }) -- Better J behavior vim.keymap.set("n", "J", "mzJ`z", { desc = "Join lines and keep cursor position" }) -- Quick config editing vim.keymap.set("n", "rc", ":e ~/.config/nvim/init.lua", { desc = "Edit config" }) -- ============================================================================ -- 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, }) -- Auto-resize splits when window is resized vim.api.nvim_create_autocmd("VimResized", { group = augroup, callback = function() vim.cmd("tabdo wincmd =") 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 -- ============================================================================ -- FLOATING TERMINAL -- ============================================================================ -- terminal local terminal_state = { buf = nil, win = nil, is_open = false } local function FloatingTerminal() -- If terminal is already open, close it (toggle behavior) if terminal_state.is_open and vim.api.nvim_win_is_valid(terminal_state.win) then vim.api.nvim_win_close(terminal_state.win, false) terminal_state.is_open = false return end -- Create buffer if it doesn't exist or is invalid if not terminal_state.buf or not vim.api.nvim_buf_is_valid(terminal_state.buf) then terminal_state.buf = vim.api.nvim_create_buf(false, true) -- Set buffer options for better terminal experience vim.api.nvim_buf_set_option(terminal_state.buf, 'bufhidden', 'hide') end -- Calculate window dimensions local width = math.floor(vim.o.columns * 0.8) local height = math.floor(vim.o.lines * 0.8) local row = math.floor((vim.o.lines - height) / 2) local col = math.floor((vim.o.columns - width) / 2) -- Create the floating window terminal_state.win = vim.api.nvim_open_win(terminal_state.buf, true, { relative = 'editor', width = width, height = height, row = row, col = col, style = 'minimal', border = 'rounded', }) -- Set transparency for the floating window vim.api.nvim_win_set_option(terminal_state.win, 'winblend', 0) -- Set transparent background for the window vim.api.nvim_win_set_option(terminal_state.win, 'winhighlight', 'Normal:FloatingTermNormal,FloatBorder:FloatingTermBorder') -- Define highlight groups for transparency vim.api.nvim_set_hl(0, "FloatingTermNormal", { bg = "none" }) vim.api.nvim_set_hl(0, "FloatingTermBorder", { bg = "none", }) -- Start terminal if not already running local has_terminal = false local lines = vim.api.nvim_buf_get_lines(terminal_state.buf, 0, -1, false) for _, line in ipairs(lines) do if line ~= "" then has_terminal = true break end end if not has_terminal then vim.fn.termopen(os.getenv("SHELL")) end terminal_state.is_open = true vim.cmd("startinsert") -- Set up auto-close on buffer leave vim.api.nvim_create_autocmd("BufLeave", { buffer = terminal_state.buf, callback = function() if terminal_state.is_open and vim.api.nvim_win_is_valid(terminal_state.win) then vim.api.nvim_win_close(terminal_state.win, false) terminal_state.is_open = false end end, once = true }) end -- Function to explicitly close the terminal local function CloseFloatingTerminal() if terminal_state.is_open and vim.api.nvim_win_is_valid(terminal_state.win) then vim.api.nvim_win_close(terminal_state.win, false) terminal_state.is_open = false end end -- Key mappings vim.keymap.set("n", "t", FloatingTerminal, { noremap = true, silent = true, desc = "Toggle floating terminal" }) vim.keymap.set("t", "", function() if terminal_state.is_open then vim.api.nvim_win_close(terminal_state.win, false) terminal_state.is_open = false end end, { noremap = true, silent = true, desc = "Close floating terminal from terminal mode" }) -- ============================================================================ -- TABS -- ============================================================================ -- Tab display settings vim.opt.showtabline = 1 -- Always show tabline (0=never, 1=when multiple tabs, 2=always) vim.opt.tabline = '' -- Use default tabline (empty string uses built-in) -- Transparent tabline appearance vim.cmd([[ hi TabLineFill guibg=NONE ctermfg=242 ctermbg=NONE ]]) -- Alternative navigation (more intuitive) vim.keymap.set('n', 'tn', ':tabnew', { desc = 'New tab' }) vim.keymap.set('n', 'tx', ':tabclose', { desc = 'Close tab' }) -- Tab moving vim.keymap.set('n', 'tm', ':tabmove', { desc = 'Move tab' }) vim.keymap.set('n', 't>', ':tabmove +1', { desc = 'Move tab right' }) vim.keymap.set('n', 't<', ':tabmove -1', { desc = 'Move tab left' }) -- Function to open file in new tab local function open_file_in_tab() vim.ui.input({ prompt = 'File to open in new tab: ', completion = 'file' }, function(input) if input and input ~= '' then vim.cmd('tabnew ' .. input) end end) end -- Function to duplicate current tab local function duplicate_tab() local current_file = vim.fn.expand('%:p') if current_file ~= '' then vim.cmd('tabnew ' .. current_file) else vim.cmd('tabnew') end end -- Function to close tabs to the right local function close_tabs_right() local current_tab = vim.fn.tabpagenr() local last_tab = vim.fn.tabpagenr('$') for i = last_tab, current_tab + 1, -1 do vim.cmd(i .. 'tabclose') end end -- Function to close tabs to the left local function close_tabs_left() local current_tab = vim.fn.tabpagenr() for i = current_tab - 1, 1, -1 do vim.cmd('1tabclose') end end -- Enhanced keybindings vim.keymap.set('n', 'tO', open_file_in_tab, { desc = 'Open file in new tab' }) vim.keymap.set('n', 'td', duplicate_tab, { desc = 'Duplicate current tab' }) vim.keymap.set('n', 'tr', close_tabs_right, { desc = 'Close tabs to the right' }) vim.keymap.set('n', 'tL', close_tabs_left, { desc = 'Close tabs to the left' }) -- Function to close buffer but keep tab if it's the only buffer in tab local function smart_close_buffer() local buffers_in_tab = #vim.fn.tabpagebuflist() if buffers_in_tab > 1 then vim.cmd('bdelete') else -- If it's the only buffer in tab, close the tab vim.cmd('tabclose') end end vim.keymap.set('n', 'bd', smart_close_buffer, { desc = 'Smart close buffer/tab' }) -- ============================================================================ -- STATUSLINE -- ============================================================================ local function statusline_mode() local modes = { n = "NORMAL", i = "INSERT", v = "VISUAL", V = "V-LINE", ["\22"] = "V-BLOCK", c = "COMMAND", R = "REPLACE", t = "TERMINAL", } 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' })