my neovim config
at aurora 43 kB view raw
1-- ============================================================================ 2-- ___ _ 3-- / _ | ___ ___ ____ ___ _ __(_)_ _ 4-- / __ |/ _ \/ _ `(_-<_ / _ \ |/ / / ' \ 5-- /_/ |_/_//_/\_,_/___(_)_//_/___/_/_/_/_/ 6-- 7-- ============================================================================ 8 9-- ============================================================================ 10-- BOOTSTRAP LAZY. NVIM (Plugin Manager) 11-- ============================================================================ 12local lazy_path = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" 13if not vim.loop.fs_stat(lazy_path) then 14 vim.fn.system({ 15 "git", 16 "clone", 17 "--filter=blob:none", 18 "https://github.com/folke/lazy.nvim.git", 19 "--branch=stable", 20 lazy_path, 21 }) 22end 23vim.opt.rtp: prepend(lazy_path) 24-- ============================================================================ 25-- CORE SETTINGS 26-- ============================================================================ 27vim.loader.enable() 28package.path = package.path .. ';' .. vim.fn.stdpath('config') .. '/?.lua' 29-- I'm lazy 30local opt = vim.opt 31 32-- Nice Number Line 33opt.relativenumber = true 34opt.number = true 35 36-- Tabs & Spaces 37opt.expandtab = true 38opt.tabstop = 4 39opt.softtabstop = 4 40opt.shiftwidth = 4 41opt.smartindent = true 42opt.autoindent = true 43 44-- Searching 45opt.ignorecase = true 46opt.smartcase = true 47opt.hlsearch = true 48opt.incsearch = true 49 50-- Column Settings 51opt.signcolumn = "yes" 52opt.colorcolumn = "120" 53opt.wrap = false 54 55-- Cursor Settings 56opt.cursorline = true 57opt.cursorcolumn = true 58-- opt.guicursor = 59-- "n-v-c-sm:block,ci-ve:ver25,r-cr-o:hor20,i:block-blinkoff1-blinkon1" 60 61-- Window Settings 62opt.splitbelow = true 63opt.splitright = true 64opt.lazyredraw = true 65opt.showtabline = 0 66 67-- Spelling 68opt.spell = true 69opt.spelloptions = "camel" 70 71-- Other 72opt.mouse = "a" 73opt.showmode = false 74opt.termguicolors = true 75opt.hidden = true 76opt.clipboard = "unnamedplus" 77opt.formatoptions = "cjql" 78opt.laststatus = 3 79opt.completeopt = { "menu", "menuone", "preview" } 80opt.conceallevel = 2 81opt.concealcursor = "" 82-- opt.updatetime = 100 83 84-- Backup -- I have power issues :/ 85opt.backup = true 86opt.swapfile = true 87opt.undodir = os.getenv("HOME") .. "/.cache/undodir" 88opt.undofile = true 89 90-- arabic support 91opt.encoding = "utf-8" 92opt.termbidi = true 93 94-- ============================================================================ 95-- UTILITY FUNCTIONS 96-- ============================================================================ 97local M = {} 98 99M.bootstrap = function(lazy_path) 100 if not vim.loop.fs_stat(lazy_path) then 101 vim.fn.system({ 102 "git", 103 "clone", 104 "--filter=blob:none", 105 "https://github.com/folke/lazy.nvim.git", 106 "--branch=stable", 107 lazy_path, 108 }) 109 end 110 vim.opt.rtp:prepend(lazy_path) 111end 112 113local cmds = { "nu!", "rnu!", "nonu!" } 114local current_index = 1 115 116function M.toggle_numbering() 117 current_index = current_index % #cmds + 1 118 vim.cmd("set " .. cmds[current_index]) 119 local signcolumn_setting = "auto" 120 if cmds[current_index] == "nonu!" then signcolumn_setting = "yes: 4" end 121 vim.opt.signcolumn = signcolumn_setting 122end 123 124function M.custom_lua_format() 125 local buf = vim.api.nvim_get_current_buf() 126 local filepath = vim.api.nvim_buf_get_name(buf) 127 vim.api.nvim_command("write") 128 local cmd = string.format( 129 "lua-format -i --indent-width=2 --no-use-tab --keep-simple-function-one-line --keep-simple-control-block-one-line --single-quote-to-double-quote --spaces-inside-table-braces --spaces-around-equals-in-field %s", 130 filepath 131 ) 132 os.execute(cmd) 133 vim.api.nvim_command("edit") 134end 135 136-- ============================================================================ 137-- KEYBINDINGS 138-- ============================================================================ 139 140local map = vim.keymap.set 141local cmd = vim.cmd 142 143-- Easy wq 144cmd(":command! WQ wq") 145cmd(":command! WQ wq") 146cmd(":command! Wq wq") 147cmd(":command! Wqa xall") 148cmd(":command! Waq xall") 149cmd(":command! WA wa") 150cmd(":command! Wa wa") 151cmd(":command! W w") 152cmd(":command! Q q") 153 154-- Set up <Space> to be the leader key 155map("n", "<Space>", "<NOP>", { noremap = true, silent = true }) 156vim.g.mapleader = " " 157 158-- Easy : 159map("n", ";", ":", { noremap = true }) 160 161-- Easy Resize 162map("n", "<M-c>", ":vertical resize -2<CR>", { noremap = true }) 163map("n", "<M-k>", ":resize +2<CR>", { noremap = true }) 164-- map("n", "<M-k>", ":resize -2<CR>", {noremap = true}) 165map("n", "<M-d>", ":vertical resize +2<CR>", { noremap = true }) 166 167-- Easy Capitalization 168map("n", "<F7>", "<Esc>V:s/\\v<(.)(\\w*)/\\u\\1\\L\\2/g<CR>:noh<CR>", {}) 169map("v", "<F7>", "<Esc>V:s/\\v<(.)(\\w*)/\\u\\1\\L\\2/g<CR>:noh<CR>", {}) 170map("i", "<F7>", "<Esc>V:s/\\v<(.)(\\w*)/\\u\\1\\L\\2/g<CR>:noh<CR>i", {}) 171 172-- The Best Thing In vscode But Better 173map("v", "U", ":m '<-2<CR>gv=gv") 174map("v", "Y", ":m '>+1<CR>gv=gv") 175 176-- Replace The Current Word 177-- map.set("n", "<leader>s", [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]]) 178 179-- The BEST MAP 180map("x", "<Leader>p", '"_dP') 181 182-- STOP USING THE ARROW KEYS !! 183-- map('n', '<Up>', [[:echoerr "Do not do that!!"<cr>]], {noremap = true}) 184-- map('n', '<Down>', [[:echoerr "Do not do that!!"<cr>]], {noremap = true}) 185-- map('n', '<Left>', [[:echoerr "Do not do that!!"<cr>]], {noremap = true}) 186-- map('n', '<Right>', [[:echoerr "Do not do that!!"<cr>]], {noremap = true}) 187 188-- I use Dvorak layout, so.. no hjkl :| 189-- Instead, I will use -kcd 190local modes = { "n", "v", "s", "o" } -- Normal, visual, select, operator-pending 191local keys = { { "h", "-" }, { "j", "c" }, { "l", "d" } } 192for _, mode in ipairs(modes) do 193 for _, key in ipairs(keys) do 194 map(mode, key[1], key[2], { noremap = true }) 195 map(mode, key[2], key[1], { noremap = true }) 196 end 197end 198 199-- Keybinds Reloading Init Files 200map("n", "<Leader>vr", "<Cmd>luafile $MYVIMRC<CR>", { noremap = true }) 201 202-- Deleting Words With <C-Bs> 203map("i", "", "<C-w>", { noremap = true, silent = true }) 204map("c", "", "<C-w>", { noremap = true, silent = true }) 205map("i", "<C-BS>", "<C-w>", { noremap = true, silent = true }) 206map("c", "<C-BS>", "<C-w>", { noremap = true }) 207 208-- Better Indent/Unintend Lines And Blocks Of Text 209map("n", ">", ">>", { noremap = true, silent = true }) 210map("n", "<", "<<", { noremap = true, silent = true }) 211map("v", ">", ">gv", { noremap = true, silent = true }) 212map("v", "<", "<gv", { noremap = true, silent = true }) 213 214-- Make Y Actually Make Sense 215map("n", "Y", "yg$", { noremap = true, silent = true }) 216 217-- Buffer Navigation 218map("n", "<Leader><Tab>", ":bn<CR>", { noremap = true, silent = true }) 219map("n", "<Leader><S-Tab>", ":bp<CR>", { noremap = true, silent = true }) 220 221-- Buffer Deletion 222map("n", "<Leader>bd", ":bd<CR>", { noremap = true, silent = true }) 223 224-- Window Navigation 225map("n", "<C-h>", "<C-w>h", { noremap = true, silent = true }) 226map("n", "<C-j>", "<C-w>j", { noremap = true, silent = true }) 227map("n", "<C-k>", "<C-w>k", { noremap = true, silent = true }) 228map("n", "<C-l>", "<C-w>l", { noremap = true, silent = true }) 229 230-- Ctrl + W Close The Window 231-- map('n', '<C-w>', ':bd!<CR>', { noremap = true, silent = true }) 232-- map('i', '<C-w>', ':bd!<CR>', { noremap = true, silent = true }) 233 234-- split 235map("n", "<leader>wv", ":vsplit<CR>", { noremap = true }) 236map("n", "<leader>ws", ":split<CR>", { noremap = true }) 237 238-- Other 239map("n", "<Leader>l", ":noh<CR>", { noremap = true }) -- Clear highlights 240map("n", "<leader>o", ":pu =''<CR>", { noremap = true }) -- Insert a newline and back to normal mode 241map("n", "<leader>O", ":pu! =''<CR>", { noremap = true }) -- Insert a newline and back to normal mode 242 243-- Auto close parenthesis 244local autopairs = { 245 ["("] = ")", 246 ["["] = "]", 247 ["{"] = "}", 248 ["<"] = ">", 249} 250for open, close in pairs(autopairs) do 251 vim.keymap.set("i", open, function() 252 return open .. close .. "<Left>" 253 end, { expr = true, noremap = true }) 254end 255 256-- ============================================================================ 257-- PLUGINS 258-- ============================================================================ 259local plugins = { 260 -- ========== Color Scheme ========== 261 { 262 "ellisonleao/gruvbox.nvim", 263 priority = 1000, 264 lazy = false, 265 enabled = true, 266 config = function() 267 require("gruvbox").setup({ 268 transparent_mode = false, 269 -- overrides = { 270 -- NvimTreeNormal = {bg = '#32302f'}, 271 -- NvimTreeEndOfBuffer = {fg = '#32302f'}, 272 -- EndOfBuffer = {fg = "#32302f"}, 273 -- NonText = {fg = "#5a524c"} 274 -- } 275 }) 276 vim.cmd([[colorscheme gruvbox]]) 277 vim.o.background = "dark" -- or "light" for light mode 278 end, 279 }, 280 -- ========== Completion ========== 281 { 282 "L3MON4D3/LuaSnip", 283 -- follow latest release. 284 version = "v2.*", -- Replace <CurrentMajor> by the latest released major (first number of latest release) 285 -- install jsregexp (optional!). 286 build = "make install_jsregexp", 287 dependencies = { "rafamadriz/friendly-snippets" }, 288 config = function() 289 require("luasnip.loaders.from_vscode").lazy_load() 290 require("snippets") 291 end, 292 }, 293 { 294 "hrsh7th/nvim-cmp", 295 dependencies = { 296 "hrsh7th/cmp-nvim-lsp", 297 "hrsh7th/cmp-buffer", 298 -- 'hrsh7th/cmp-path', 299 "https://codeberg.org/FelipeLema/cmp-async-path", 300 "hrsh7th/cmp-cmdline", 301 "L3MON4D3/LuaSnip", 302 "saadparwaiz1/cmp_luasnip", 303 }, 304 event = { "LspAttach", "InsertCharPre" }, 305 config = function() 306 local cmp = require("cmp") 307 cmp.setup({ 308 snippet = { 309 expand = function(args) require("luasnip").lsp_expand(args.body) end, 310 }, 311 mapping = cmp.mapping.preset.insert({ 312 ["<Tab>"] = cmp.mapping(function(fallback) 313 if cmp.visible() then 314 cmp.select_next_item() 315 else 316 fallback() 317 end 318 end, { "i", "s" }), 319 ["<S-Tab>"] = cmp.mapping(function(fallback) 320 if cmp.visible() then 321 cmp.select_prev_item() 322 else 323 fallback() 324 end 325 end, { "i", "s" }), 326 ["<C-;>"] = cmp.mapping.abort(), 327 ["<C-k>"] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items. 328 329 ["<C-g>"] = cmp.mapping(function() 330 if not cmp.visible() then cmp.complete() end 331 cmp.abort() 332 end, { "i", "s" }), 333 ["<c-y>"] = cmp.mapping(function(fallback) 334 if not cmp.visible() then fallback() end 335 cmp.scroll_docs(-1) 336 end, { "i", "s" }), 337 ["<c-e>"] = cmp.mapping(function(fallback) 338 if not cmp.visible() then fallback() end 339 cmp.scroll_docs(1) 340 end, { "i", "s" }), 341 ["<c-p>"] = cmp.mapping(function(fallback) 342 if not cmp.visible() then fallback() end 343 cmp.select_prev_item() 344 end, { "i", "s" }), 345 ["<c-n>"] = cmp.mapping(function(fallback) 346 if not cmp.visible() then fallback() end 347 cmp.select_next_item() 348 end, { "i", "s" }), 349 ["<cr>"] = cmp.mapping(function(fallback) 350 if cmp.get_selected_entry() == nil then fallback() end 351 cmp.confirm() 352 end, { "i", "s" }), 353 }), 354 355 sources = cmp.config.sources({ 356 { name = "async_path", max_item_count = 20 }, 357 { 358 name = "nvim_lsp", 359 max_item_count = 80, 360 }, 361 { 362 name = "buffer", 363 max_item_count = 20, 364 option = { 365 get_bufnrs = function() 366 return vim.tbl_map(function(win) return vim.api.nvim_win_get_buf(win) end, vim.api.nvim_list_wins()) 367 end, 368 }, 369 }, 370 { name = "luasnip" }, -- For luasnip users. 371 -- { name = 'ultisnips' }, -- For ultisnips users. 372 -- { name = 'snippy' }, -- For snippy users. 373 -- { name = 'crates' }, 374 }), 375 -- eof 376 }) 377 378 -- Set configuration for specific filetype. 379 cmp.setup.filetype("gitcommit", { 380 sources = cmp.config.sources({ 381 { name = "git" }, -- You can specify the `git` source if [you were installed it](https://github.com/petertriho/cmp-git). 382 }, { 383 { name = "buffer" }, 384 }), 385 }) 386 387 -- Use buffer source for `/` and `?` (if you enabled `native_menu`, this won't work anymore). 388 cmp.setup.cmdline({ "/", "?" }, { 389 mapping = cmp.mapping.preset.cmdline(), 390 sources = { 391 { name = "buffer" }, 392 }, 393 }) 394 395 -- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore). 396 cmp.setup.cmdline(":", { 397 mapping = cmp.mapping.preset.cmdline(), 398 sources = cmp.config.sources({ 399 { name = "path" }, 400 }, { 401 { name = "cmdline" }, 402 }), 403 matching = { disallow_symbol_nonprefix_matching = false }, 404 }) 405 end, 406 }, 407 408 -- ========== UI & Navigation ========== 409 { 410 "nvim-lualine/lualine.nvim", 411 opts = { 412 options = { 413 icons_enabled = true, 414 theme = "gruvbox-material", 415 component_separators = { left = "|", right = "|" }, 416 section_separators = { left = "", right = "" }, 417 disabled_filetypes = { "NvimTree", "packer" }, 418 always_divide_middle = true, 419 globalstatuses = true, 420 }, 421 sections = { 422 lualine_a = { "mode" }, 423 lualine_b = { 424 "branch", 425 "diff", 426 { 427 "diagnostics", 428 sources = { "nvim_lsp", "coc" }, 429 update_in_insert = true, 430 always_visible = true, 431 }, 432 }, 433 lualine_c = { { "filename", file_status = true, path = 1 } }, 434 lualine_x = { "filetype" }, 435 lualine_y = { "%p%%", "location" }, 436 lualine_z = {}, 437 }, 438 inactive_sections = { 439 lualine_a = {}, 440 lualine_b = {}, 441 lualine_c = { { "filename", file_status = true, path = 1 } }, 442 lualine_x = { "filetype" }, 443 lualine_y = { "%p%%", "location" }, 444 lualine_z = {}, 445 }, 446 tabline = {}, 447 extensions = { "fugitive", "fzf", "nvim-tree" }, 448 }, 449 }, 450 -- Neovim file explorer: edit your filesystem like a buffer 451 { 452 "stevearc/oil.nvim", 453 opts = {}, 454 dependencies = { { "echasnovski/mini.icons", opts = {} } }, 455 config = function() 456 require("oil").setup({ 457 default_file_explorer = true, 458 view_options = { 459 show_hidden = true, 460 }, 461 keymaps = { 462 ["g?"] = "actions.show_help", 463 ["<CR>"] = "actions.select", 464 ["<C-s>"] = { 465 "actions.select", 466 opts = { vertical = true }, 467 desc = "Open the entry in a vertical split", 468 }, 469 ["<C-h>"] = { 470 "actions.select", 471 opts = { horizontal = true }, 472 desc = "Open the entry in a horizontal split", 473 }, 474 ["<C-t>"] = { "actions.select", opts = { tab = true }, desc = "Open the entry in new tab" }, 475 ["<C-p>"] = "actions.preview", 476 ["q"] = "actions.close", 477 ["<C-l>"] = "actions.refresh", 478 ["-"] = "actions.parent", 479 ["_"] = "actions.open_cwd", 480 ["`"] = "actions.cd", 481 ["~"] = { "actions.cd", opts = { scope = "tab" }, desc = ":tcd to the current oil directory" }, 482 ["gs"] = "actions.change_sort", 483 ["gx"] = "actions.open_external", 484 ["g."] = "actions.toggle_hidden", 485 ["g\\"] = "actions.toggle_trash", 486 }, 487 float = { 488 padding = 3, 489 border = "rounded", 490 }, 491 }) 492 end, 493 }, 494 -- 495 { 496 "nvim-telescope/telescope.nvim", 497 lazy = false, 498 keys = { 499 { 500 "<Leader>ff", 501 ":lua require('telescope.builtin').find_files()<CR>", 502 { noremap = true, silent = true }, 503 desc = "Find Files", 504 }, 505 { 506 "<Leader>bb", 507 ":lua require('telescope.builtin').buffers()<CR>", 508 { noremap = true, silent = true }, 509 desc = "Buffers", 510 }, 511 { 512 "<Leader>g", 513 ":lua require('telescope.builtin').live_grep()<CR>", 514 { noremap = true, silent = true }, 515 desc = "Live Grep", 516 }, 517 { 518 "<Leader>h", 519 ":lua require('telescope.builtin').help_tags()<CR>", 520 { noremap = true, silent = true }, 521 desc = "Help Tags", 522 }, 523 { 524 "<Leader>ss", 525 ":lua require('telescope.builtin').spell_suggest()<CR>", 526 { noremap = true, silent = true }, 527 desc = "Spell Suggest", 528 }, 529 { 530 "<Leader>k", 531 ":lua require('telescope.builtin').keymaps()<CR>", 532 { noremap = true, silent = true }, 533 desc = "Show Key Maps", 534 }, 535 { 536 "<Leader>vo", 537 ":lua require('telescope.builtin').vim_options()<CR>", 538 { noremap = true, silent = true }, 539 desc = "Neovim Options", 540 }, 541 { 542 "<Leader>D", 543 ":lua require('telescope.builtin').diagnostics()<CR>", 544 { noremap = true, silent = true }, 545 desc = "Diagnostics", 546 }, 547 { 548 "<Leader>cs", 549 ":lua require('telescope.builtin').git_status()<CR>", 550 { noremap = true, silent = true }, 551 desc = "Git Status", 552 }, 553 }, 554 dependencies = { 555 { "nvim-lua/plenary.nvim" }, 556 { "nvim-telescope/telescope-fzf-native.nvim" }, 557 -- ripgrep needs to be installed on the system 'pacman -S ripgrep' 558 }, 559 config = function() 560 local actions = require("telescope.actions") 561 require("telescope").setup({ 562 defaults = { mappings = { i = { ["<esc>"] = actions.close } } }, 563 extensions = { 564 fzf = { 565 fuzzy = true, 566 override_generic_sorter = true, 567 override_file_sorter = true, 568 case_mode = "smart_case", 569 }, 570 }, 571 }) 572 end, 573 }, 574 -- ========== Linting ========== 575 -- Lightweight yet powerful formatter plugin for Neovim 576 { 577 "stevearc/conform.nvim", 578 lazy = true, 579 -- event = { "BufWritePre" }, 580 cmd = { "ConformInfo" }, 581 keys = { 582 { 583 "<leader>cf", 584 function() require("conform").format({ async = true }) end, 585 mode = "", 586 desc = "Format buffer", 587 }, 588 }, 589 opts = { 590 -- Map of filetype to formatters 591 formatters_by_ft = { 592 lua = { "stylua" }, 593 -- Conform will run multiple formatters sequentially 594 go = { "goimports", "gofmt" }, 595 -- You can also customize some of the format options for the filetype 596 rust = { "rustfmt", lsp_format = "fallback" }, 597 -- You can use a function here to determine the formatters dynamically 598 python = function(bufnr) 599 if require("conform").get_formatter_info("ruff_format", bufnr).available then 600 return { "ruff_format" } 601 else 602 return { "isort", "black" } 603 end 604 end, 605 nix = { "nixfmt" }, 606 ["markdown"] = { "prettier", "markdownlint-cli2", "markdown-toc" }, 607 ["markdown.mdx"] = { "prettier", "markdownlint-cli2", "markdown-toc" }, 608 -- Use the "*" filetype to run formatters on all filetypes. 609 -- ["*"] = { "codespell" }, 610 -- Use the "_" filetype to run formatters on filetypes that don't 611 -- have other format 612 ["_"] = { "trim_whitespace" }, 613 }, 614 -- Set this to change the default values when calling conform.format() 615 -- This will also affect the default values for format_on_save/format_after_save 616 default_format_opts = { lsp_format = "fallback" }, 617 -- If this is set, Conform will run the formatter asynchronously after save. 618 -- It will pass the table to conform.format(). 619 -- This can also be a function that returns the table. 620 -- format_after_save = { lsp_format = "fallback" }, 621 -- Set the log level. Use `:ConformInfo` to see the location of the log file. 622 log_level = vim.log.levels.ERROR, 623 -- Conform will notify you when a formatter errors 624 notify_on_error = true, 625 -- Conform will notify you when no formatters are available for the buffer 626 notify_no_formatters = true, 627 -- Custom formatters and overrides for built-in formatters 628 formatters = { 629 stylua = { 630 prepend_args = { 631 "--call-parentheses", 632 "Always", 633 "--collapse-simple-statement", 634 "Always", 635 "--indent-type", 636 "Tabs", 637 "--indent-width", 638 "2", 639 "--sort-requires", 640 }, 641 }, 642 ["markdown-toc"] = { 643 condition = function(_, ctx) 644 for _, line in ipairs(vim.api.nvim_buf_get_lines(ctx.buf, 0, -1, false)) do 645 if line:find("<!%-%- toc %-%->") then return true end 646 end 647 end, 648 }, 649 ["markdownlint-cli2"] = { 650 condition = function(_, ctx) 651 local diag = vim.tbl_filter(function(d) return d.source == "markdownlint" end, vim.diagnostic.get(ctx.buf)) 652 return #diag > 0 653 end, 654 }, 655 }, 656 }, 657 }, 658 659 -- ========== Navigation & Motion ========== 660 { 661 "ggandor/leap.nvim", 662 keys = { 663 { "s", mode = { "n", "x", "o" }, desc = "Leap forward to" }, 664 { "S", mode = { "n", "x", "o" }, desc = "Leap backward to" }, 665 { "gs", mode = { "n", "x", "o" }, desc = "Leap from windows" }, 666 }, 667 config = function(_, opts) 668 local leap = require("leap") 669 for k, v in pairs(opts) do 670 leap.opts[k] = v 671 end 672 leap.add_default_mappings(true) 673 vim.keymap.del({ "x", "o" }, "x") 674 vim.keymap.del({ "x", "o" }, "X") 675 end, 676 dependencies = { "tpope/vim-repeat" }, 677 }, 678 679 { 680 "ggandor/flit.nvim", 681 keys = function() 682 local ret = {} 683 for _, key in ipairs({ "f", "F", "t", "T" }) do 684 ret[#ret + 1] = { key, mode = { "n", "x", "o" }, desc = key } 685 end 686 return ret 687 end, 688 opts = { labeled_modes = "nx" }, 689 dependencies = { "ggandor/leap.nvim" }, 690 }, 691 692 -- ========== Git & Tools ========== 693 { 694 "lewis6991/gitsigns.nvim", 695 lazy = false, 696 init = function() 697 require("gitsigns").setup({ 698 attach_to_untracked = false, 699 current_line_blame = true, -- Toggle with `:Gitsigns toggle_current_line_blame` 700 current_line_blame_opts = { 701 virt_text = true, 702 virt_text_pos = "eol", -- 'eol' | 'overlay' | 'right_align' 703 delay = 200, 704 ignore_whitespace = false, 705 virt_text_priority = 100, 706 }, 707 current_line_blame_formatter = "<author>, <author_time:%Y-%m-%d> - <summary>", 708 -- current_line_blame_formatter_opts = { 709 -- relative_time = false, 710 -- }, 711 }) 712 end, 713}, 714 -- Git source for nvim-cmp 715 { 716 "petertriho/cmp-git", 717 dependencies = { 718 "nvim-lua/plenary.nvim", 719 }, 720 init = function() table.insert(require("cmp").get_config().sources, { name = "git" }) end, 721 config = function() 722 local format = require("cmp_git.format") 723 local sort = require("cmp_git.sort") 724 725 require("cmp_git").setup({ 726 -- defaults 727 filetypes = { "gitcommit", "octo" }, 728 remotes = { "upstream", "origin", "github", "gitlab", "codeberg" }, -- in order of most to least prioritized 729 enableRemoteUrlRewrites = false, -- enable git url rewrites, see https://git-scm.com/docs/git-config#Documentation/git-config.txt-urlltbasegtinsteadOf 730 git = { 731 commits = { 732 limit = 100, 733 sort_by = sort.git.commits, 734 format = format.git.commits, 735 }, 736 }, 737 github = { 738 hosts = {}, -- list of private instances of github 739 issues = { 740 fields = { "title", "number", "body", "updatedAt", "state" }, 741 filter = "all", -- assigned, created, mentioned, subscribed, all, repos 742 limit = 100, 743 state = "open", -- open, closed, all 744 sort_by = sort.github.issues, 745 format = format.github.issues, 746 }, 747 mentions = { 748 limit = 100, 749 sort_by = sort.github.mentions, 750 format = format.github.mentions, 751 }, 752 pull_requests = { 753 fields = { "title", "number", "body", "updatedAt", "state" }, 754 limit = 100, 755 state = "open", -- open, closed, merged, all 756 sort_by = sort.github.pull_requests, 757 format = format.github.pull_requests, 758 }, 759 }, 760 gitlab = { 761 hosts = {}, -- list of private instances of gitlab 762 issues = { 763 limit = 100, 764 state = "opened", -- opened, closed, all 765 sort_by = sort.gitlab.issues, 766 format = format.gitlab.issues, 767 }, 768 mentions = { 769 limit = 100, 770 sort_by = sort.gitlab.mentions, 771 format = format.gitlab.mentions, 772 }, 773 merge_requests = { 774 limit = 100, 775 state = "opened", -- opened, closed, locked, merged 776 sort_by = sort.gitlab.merge_requests, 777 format = format.gitlab.merge_requests, 778 }, 779 }, 780 trigger_actions = { 781 { 782 debug_name = "git_commits", 783 trigger_character = ":", 784 action = function(sources, trigger_char, callback, params, git_info) 785 return sources.git:get_commits(callback, params, trigger_char) 786 end, 787 }, 788 { 789 debug_name = "gitlab_issues", 790 trigger_character = "#", 791 action = function(sources, trigger_char, callback, params, git_info) 792 return sources.gitlab:get_issues(callback, git_info, trigger_char) 793 end, 794 }, 795 { 796 debug_name = "gitlab_mentions", 797 trigger_character = "@", 798 action = function(sources, trigger_char, callback, params, git_info) 799 return sources.gitlab:get_mentions(callback, git_info, trigger_char) 800 end, 801 }, 802 { 803 debug_name = "gitlab_mrs", 804 trigger_character = "!", 805 action = function(sources, trigger_char, callback, params, git_info) 806 return sources.gitlab:get_merge_requests(callback, git_info, trigger_char) 807 end, 808 }, 809 { 810 debug_name = "github_issues_and_pr", 811 trigger_character = "#", 812 action = function(sources, trigger_char, callback, params, git_info) 813 return sources.github:get_issues_and_prs(callback, git_info, trigger_char) 814 end, 815 }, 816 { 817 debug_name = "github_mentions", 818 trigger_character = "@", 819 action = function(sources, trigger_char, callback, params, git_info) 820 return sources.github:get_mentions(callback, git_info, trigger_char) 821 end, 822 }, 823 }, 824 }) 825 end, 826}, 827 -- ========== Language Support ========== 828 { 829 "NoahTheDuke/vim-just", 830 ft = { "just" }, 831 }, 832 833 -- ========== TreeSitter ========== 834 { 835 "nvim-treesitter/nvim-treesitter", 836 version = false, -- last release is way too old and doesn't work on Windows 837 build = ":TSUpdate", 838 event = { "VeryLazy" }, 839 init = function(plugin) 840 -- PERF: add nvim-treesitter queries to the rtp and it's custom query predicates early 841 -- This is needed because a bunch of plugins no longer `require("nvim-treesitter")`, which 842 -- no longer trigger the **nvim-treesitter** module to be loaded in time. 843 -- Luckily, the only things that those plugins need are the custom queries, which we make available 844 -- during startup. 845 require("lazy.core.loader").add_to_rtp(plugin) 846 require("nvim-treesitter.query_predicates") 847 end, 848 dependencies = { 849 { 850 "nvim-treesitter/nvim-treesitter-textobjects", 851 config = function() 852 -- When in diff mode, we want to use the default 853 -- vim text objects c & C instead of the treesitter ones. 854 local move = require("nvim-treesitter.textobjects.move") ---@type table<string,fun(...)> 855 local configs = require("nvim-treesitter.configs") 856 for name, fn in pairs(move) do 857 if name:find("goto") == 1 then 858 move[name] = function(q, ...) 859 if vim.wo.diff then 860 local config = configs.get_module("textobjects.move")[name] ---@type table<string,string> 861 for key, query in pairs(config or {}) do 862 if q == query and key:find("[%]%[][cC]") then 863 vim.cmd("normal! " .. key) 864 return 865 end 866 end 867 end 868 return fn(q, ...) 869 end 870 end 871 end 872 end, 873 }, 874 }, 875 cmd = { "TSUpdateSync", "TSUpdate", "TSInstall" }, 876 keys = { 877 { "<c-space>", desc = "Increment Selection" }, 878 { "<bs>", desc = "Decrement Selection", mode = "x" }, 879 }, 880 opts = { 881 highlight = { enable = true }, 882 indent = { enable = true }, 883 ensure_installed = { 884 "rust", "bash", "c", "diff", "html", "javascript", "jsdoc", "json", "jsonc", "lua", "luadoc", "luap", 885 "markdown", "markdown_inline", "nim", "python", "query", "regex", "toml", "tsx", "typescript", "vim", 886 "nasm", "vimdoc", "xml", "yaml", "hurl", "nix", "zig", "asm", "jq", "just", "latex", "go", "gleam", "gitcommit", 887 "gitignore", "dockerfile", "go", "kdl", "kotlin", "java", "make", "pony", "po", "printf", "sql", "typst", "http", 888 "graphql", "earthfile", "editorconfig", "ssh_config", "tmux", "glsl", "hlsl", "ini", "udev", "llvm", "ispc", "inko", 889 "disassembly", "diff", "haskell", "corn", "odin", 890 }, 891 incremental_selection = { 892 enable = true, 893 keymaps = { 894 init_selection = "<C-space>", 895 node_incremental = "<C-space>", 896 scope_incremental = false, 897 node_decremental = "<bs>", 898 }, 899 }, 900 textobjects = { 901 move = { 902 enable = true, 903 goto_next_start = { ["]f"] = "@function.outer", ["]c"] = "@class.outer" }, goto_next_end = { ["]F"] = "@function.outer", ["]C"] = "@class.outer" }, 904 goto_previous_start = { 905 ["[f"] = "@function.outer", 906 ["[c"] = "@class.outer", 907 }, 908 goto_previous_end = { 909 ["[F"] = "@function.outer", 910 ["[C"] = "@class.outer", 911 }, 912 }, 913 }, 914 }, 915 config = function(_, opts) 916 if type(opts.ensure_installed) == "table" then 917 ---@type table<string, boolean> 918 local added = {} 919 opts.ensure_installed = vim.tbl_filter(function(lang) 920 if added[lang] then return false end 921 added[lang] = true 922 return true 923 end, opts.ensure_installed) 924 end 925 require("nvim-treesitter.configs").setup(opts) 926 end, 927 }, 928 929 -- ========== Cosmetics & Utilities ========== 930 { 931 "numToStr/Comment.nvim", 932 init = function() require("ts_context_commentstring").setup({}) end, 933 config = function() 934 require("Comment").setup({ 935 pre_hook = require("ts_context_commentstring.integrations.comment_nvim").create_pre_hook(), 936 }) 937 end, 938 lazy = true, 939 event = "VeryLazy", 940 keys = { 941 { 942 "<C-_>", 943 ":lua require('Comment.api').toggle.linewise.current()<CR>", 944 { noremap = true, silent = true }, 945 desc = "", 946 mode = "n", 947 }, 948 { 949 "<C-_>", 950 '<ESC><CMD>lua require("Comment.api").toggle.linewise(vim.fn.visualmode())<CR>', 951 { noremap = true, silent = true }, 952 desc = "", 953 mode = "x", 954 }, 955 { 956 "<C-_>", 957 ":lua require('Comment.api').toggle.linewise.current() <CR>", 958 { noremap = true, silent = true }, 959 desc = "", 960 mode = "i", 961 }, -- same as above but fixes some weird issues 962 { 963 "<C-/>", 964 ":lua require('Comment.api').toggle.linewise.current()<CR>", 965 { noremap = true, silent = true }, 966 desc = "", 967 mode = "n", 968 }, 969 { 970 "<C-/>", 971 '<ESC><CMD>lua require("Comment.api").toggle.linewise(vim.fn.visualmode())<CR>', 972 { noremap = true, silent = true }, 973 desc = "", 974 mode = "x", 975 }, 976 { 977 "<C-/>", 978 ":lua require('Comment.api').toggle.linewise.current() <CR>", 979 { noremap = true, silent = true }, 980 desc = "", 981 mode = "i", 982 }, 983 }, 984 dependencies = { "JoosepAlviste/nvim-ts-context-commentstring" }, 985 }, 986 { 987 "folke/todo-comments.nvim", 988 cmd = { "TodoTrouble", "TodoTelescope" }, 989 lazy = false, 990 opts = { 991 signs = true, -- show icons in the signs column 992 sign_priority = 8, -- sign priority 993 -- keywords recognized as todo comments 994 keywords = { 995 FIX = { 996 icon = "", -- icon used for the sign, and in search results 997 color = "error", -- can be a hex color, or a named color (see below) 998 alt = { "FIXME", "BUG", "FIXIT", "ISSUE" }, -- a set of other keywords that all map to this FIX keywords 999 -- signs = false, -- configure signs for some keywords individually 1000 }, 1001 TODO = { icon = "", color = "info" }, 1002 HACK = { icon = "", color = "warning" }, 1003 WARN = { icon = "", color = "warning", alt = { "WARNING", "XXX" } }, 1004 PERF = { icon = "", alt = { "OPTIM", "PERFORMANCE", "OPTIMIZE" } }, 1005 NOTE = { icon = "", color = "hint", alt = { "INFO" } }, 1006 TEST = { icon = "", color = "test", alt = { "TESTING", "PASSED", "FAILED" } }, 1007 IMPORTANT = { icon = "", color = "default", alt = { "IMPORT" } }, 1008 }, 1009 gui_style = { 1010 fg = "NONE", -- The gui style to use for the fg highlight group. 1011 bg = "BOLD", -- The gui style to use for the bg highlight group. 1012 }, 1013 merge_keywords = true, -- when true, custom keywords will be merged with the defaults 1014 -- highlighting of the line containing the todo comment 1015 -- * before: highlights before the keyword (typically comment characters) 1016 -- * keyword: highlights of the keyword 1017 -- * after: highlights after the keyword (todo text) 1018 highlight = { 1019 multiline = true, -- enable multine todo comments 1020 multiline_pattern = "^.", -- lua pattern to match the next multiline from the start of the matched keyword 1021 multiline_context = 5, -- extra lines that will be re-evaluated when changing a line 1022 before = "", -- "fg" or "bg" or empty 1023 keyword = "wide", -- "fg", "bg", "wide", "wide_bg", "wide_fg" or empty. (wide and wide_bg is the same as bg, but will also highlight surrounding characters, wide_fg acts accordingly but with fg) 1024 after = "fg", -- "fg" or "bg" or empty 1025 -- INFO: pattern or table of pattern to match 1026 -- INFO (anas): this 1027 pattern = [[.*<(KEYWORDS)\s*(.*)*\s*:]], 1028 -- pattern = [[.*<(KEYWORDS)\s*:]], -- pattern or table of patterns, used for highlighting (vim regex) 1029 comments_only = true, -- uses treesitter to match keywords in comments only 1030 max_line_len = 400, -- ignore lines longer than this 1031 exclude = {}, -- list of file types to exclude highlighting 1032 }, 1033 -- list of named colors where we try to extract the guifg from the 1034 -- list of highlight groups or use the hex color if hl not found as a fallback 1035 colors = { 1036 error = { "DiagnosticError", "ErrorMsg", "#DC2626" }, 1037 warning = { "DiagnosticWarn", "WarningMsg", "#FBBF24" }, 1038 info = { "DiagnosticInfo", "#2563EB" }, 1039 hint = { "DiagnosticHint", "#10B981" }, 1040 default = { "Identifier", "#7C3AED" }, 1041 test = { "Identifier", "#FF00FF" }, 1042 }, 1043 search = { 1044 command = "rg", 1045 args = { 1046 "--color=never", 1047 "--no-heading", 1048 "--with-filename", 1049 "--line-number", 1050 "--column", 1051 }, 1052 -- regex that will be used to match keywords. 1053 -- don't replace the (KEYWORDS) placeholder 1054 pattern = [[\b(KEYWORDS):]], -- ripgrep regex 1055 -- pattern = [[\b(KEYWORDS)\b]], -- match without the extra colon. You'll likely get false positives 1056 }, 1057 }, 1058 keys = { 1059 { "]t", function() require("todo-comments").jump_next() end, desc = "Next Todo Comment" }, 1060 { "[t", function() require("todo-comments").jump_prev() end, desc = "Previous Todo Comment" }, 1061 { "<leader>xt", "<cmd>Trouble todo toggle<cr>", desc = "Todo (Trouble)" }, 1062 { 1063 "<leader>xT", 1064 "<cmd>Trouble todo toggle filter = {tag = {TODO,FIX,FIXME}}<cr>", 1065 desc = "Todo/Fix/Fixme (Trouble)", 1066 }, 1067 { "<leader>st", "<cmd>TodoTelescope<cr>", desc = "Todo" }, 1068 { "<leader>sT", "<cmd>TodoTelescope keywords=TODO,FIX,FIXME<cr>", desc = "Todo/Fix/Fixme" }, 1069 }, 1070 }, 1071 -- Multiple cursors plugin for vim/neovim 1072 { 1073 "mg979/vim-visual-multi", 1074 init = function() 1075 vim.g.VM_maps = { 1076 ["Find Under"] = "<C-m>", 1077 ["Find Subword Under"] = "<C-m>", 1078 } 1079 end, 1080 }, 1081 { 1082 "ggandor/leap.nvim", 1083 keys = { 1084 { "s", mode = { "n", "x", "o" }, desc = "Leap forward to" }, 1085 { "S", mode = { "n", "x", "o" }, desc = "Leap backward to" }, 1086 { "gs", mode = { "n", "x", "o" }, desc = "Leap from windows" }, 1087 }, 1088 config = function(_, opts) 1089 local leap = require("leap") 1090 for k, v in pairs(opts) do 1091 leap.opts[k] = v 1092 end 1093 leap.add_default_mappings(true) 1094 vim.keymap.del({ "x", "o" }, "x") 1095 vim.keymap.del({ "x", "o" }, "X") 1096 end, 1097 dependencies = { "tpope/vim-repeat" }, 1098 }, 1099 { 1100 "mbbill/undotree", 1101 lazy = true, 1102 keys = { 1103 { "<Leader>u", ":UndotreeToggle<CR>", { noremap = true }, desc = "", mode = "n" }, 1104 }, 1105 }, 1106 { 1107 "folke/which-key.nvim", 1108 event = "VeryLazy", 1109 opts_extend = { "spec" }, 1110 opts = { 1111 defaults = {}, 1112 spec = { 1113 { 1114 mode = { "n", "v" }, 1115 -- Group names based on keys defined in other plugins 1116 { "<leader>b", group = "buffers" }, -- bb 1117 { "<leader>c", group = "code/git" }, -- cf (format), cs (git status) 1118 { "<leader>f", group = "find files" }, -- ff 1119 { "<leader>s", group = "search/spell/todo" }, -- ss, st, sT 1120 { "<leader>v", group = "neovim" }, -- vo (options) 1121 { "<leader>x", group = "trouble" }, -- xt, xT 1122 1123 -- Standalone keys defined in Telescope/Undotree 1124 { "<leader>D", desc = "Diagnostics" }, 1125 { "<leader>g", desc = "Live Grep" }, 1126 { "<leader>h", desc = "Help Tags" }, 1127 { "<leader>k", desc = "Keymaps" }, 1128 { "<leader>u", desc = "Undotree Toggle" }, 1129 1130 -- Navigation & Text Objects 1131 { "[", group = "prev" }, -- [t, [c, [f 1132 { "]", group = "next" }, -- ]t, ]c, ]f 1133 { "g", group = "goto/action" }, -- gs, gx, g?, g., etc 1134 { "gs", group = "surround/leap" }, 1135 { "z", group = "fold" }, 1136 1137 -- Window management 1138 { 1139 "<leader>w", 1140 group = "windows", 1141 proxy = "<c-w>", 1142 expand = function() return require("which-key.extras").expand.win() end, 1143 }, 1144 { "gx", desc = "Open with system app" }, 1145 }, 1146 }, 1147 }, 1148 keys = { 1149 { 1150 "<leader>?", 1151 function() require("which-key").show({ global = false }) end, 1152 desc = "Buffer Keymaps (which-key)", 1153 }, 1154 { 1155 "<c-w><space>", 1156 function() require("which-key").show({ keys = "<c-w>", loop = true }) end, 1157 desc = "Window Hydra Mode (which-key)", 1158 }, 1159 }, 1160 config = function(_, opts) 1161 local wk = require("which-key") 1162 wk.setup(opts) 1163 if not vim.tbl_isempty(opts.defaults) then 1164 vim.notify("which-key: opts.defaults is deprecated. Please use opts.spec instead.", "error") 1165 wk.register(opts.defaults) 1166 end 1167 end, 1168 }, 1169 "tpope/vim-fugitive", 1170 "ellisonleao/glow.nvim", 1171} 1172 1173require("lazy").setup(plugins, { 1174 defaults = { lazy = false }, 1175}) 1176 1177-- ============================================================================ 1178-- NEOVIDE CONFIGURATION 1179-- ============================================================================ 1180if vim.g.neovide then 1181 vim.o.guifont = "JetBrains_Mono,Noto_Color_Emoji,Noto_Kufi_Arabic: h10" 1182end 1183 1184-- ============================================================================ 1185-- AUTOCOMMANDS 1186-- ============================================================================ 1187local augroup = vim.api.nvim_create_augroup 1188local autocmd = vim.api. nvim_create_autocmd 1189 1190-- Groff 1191local groff = augroup("groff", {}) 1192autocmd("BufWritePost", { 1193 pattern = { "*.ms" }, 1194 command = "silent ! pdfroff -ms % -e -t -p > %: r.pdf", 1195 group = groff, 1196}) 1197 1198-- Indentation 1199local indention = augroup("indentaion", {}) 1200autocmd("FileType", { 1201 pattern = { 1202 "html", 1203 "htmldjango", 1204 "css", 1205 "scss", 1206 "javascript", 1207 "javascriptreact", 1208 "typescript", 1209 "nix", 1210 "typescriptreact", 1211 "lua", 1212 "markdown", 1213 "jinja", 1214 "html. mustache", 1215 "html.handlebars", 1216 "prisma", 1217 }, 1218 command = "setlocal tabstop=2 softtabstop=2 shiftwidth=2", 1219 group = indention, 1220}) 1221 1222-- Highlight yanked text 1223local highlightYank = augroup("highlightYank", {}) 1224autocmd("TextYankPost", { 1225 callback = function() vim.highlight.on_yank({ higroup = "Visual", timeout = 300 }) end, 1226 group = highlightYank, 1227}) 1228 1229-- Center Screen On Insert 1230local centerScreen = augroup("centerScreen", {}) 1231autocmd("InsertEnter", { pattern = "*", command = "norm zz", group = centerScreen }) 1232 1233-- Opens PDF/Media files in PDFVIWER/BROWSER instead of viewing binary 1234local openMediaFiles = augroup("openMediaFiles", {}) 1235autocmd("BufReadPost", { 1236 pattern = { "*.pdf" }, 1237 callback = function() 1238 local command 1239 if pcall(os.getenv("PDFVIWER")) then 1240 command = os.getenv("PDFVIWER") 1241 else 1242 command = os.getenv("BROWSER") 1243 end 1244 vim.fn.jobstart(string.format("%s '%s'", command, vim.fn.expand("%")), { detach = true }) 1245 vim.api.nvim_buf_delete(0, {}) 1246 end, 1247 group = openMediaFiles, 1248}) 1249 1250autocmd("BufReadPost", { 1251 pattern = { "*.png", "*.webp", "*.jpg", "*. jpeg", "*.mp4" }, 1252 callback = function() 1253 local command = os.getenv("BROWSER") 1254 vim.fn.jobstart(string. format("%s '%s'", command, vim.fn.expand("%")), { detach = true }) 1255 vim.api.nvim_buf_delete(0, {}) 1256 end, 1257 group = openMediaFiles, 1258}) 1259 1260-- restore cursor position 1261vim.api.nvim_create_autocmd("BufReadPost", { 1262 callback = function() 1263 local row, col = unpack(vim.api.nvim_buf_get_mark(0, '"')) 1264 if row > 0 and row <= vim.api.nvim_buf_line_count(0) then 1265 vim.api.nvim_win_set_cursor(0, { row, col }) 1266 end 1267 end, 1268}) 1269 1270-- make sure viminfo/shada is enabled 1271vim.opt.shada = "!,'100,<50,s10,h" 1272 1273-- EOF