Flake for my NixOS devices
at main 850 lines 24 kB view raw
1{inputs, ...}: { 2 pkgs, 3 config, 4 lib, 5 ... 6}: { 7 imports = [inputs.nixvim.homeModules.nixvim]; 8 9 options.cow.neovim.enable = lib.mkEnableOption "Neovim + Nixvim + Customizations"; 10 11 config = lib.mkIf config.cow.neovim.enable { 12 cow.imperm.keep = [".local/share/nvim"]; 13 14 home.sessionVariables.EDITOR = "nvim"; 15 16 programs.nixvim = { 17 # Meta 18 19 enable = true; 20 enableMan = false; 21 defaultEditor = true; 22 viAlias = true; 23 vimAlias = true; 24 25 nixpkgs.pkgs = pkgs; 26 27 performance = { 28 byteCompileLua = { 29 enable = true; 30 nvimRuntime = true; 31 plugins = true; 32 }; 33 combinePlugins = { 34 enable = true; 35 }; 36 }; 37 38 # Theming 39 40 colorschemes.catppuccin = lib.mkIf config.cow.cat.enable { 41 enable = true; 42 settings = { 43 inherit (config.catppuccin) flavor; 44 no_underline = false; 45 no_bold = false; 46 no_italics = false; 47 term_colors = true; 48 integrations = { 49 fidget = true; 50 markdown = true; 51 ufo = true; 52 rainbow_delimiters = true; 53 lsp_trouble = true; 54 which_key = true; 55 telescope.enabled = true; 56 treesitter = true; 57 lsp_saga = true; 58 illuminate = { 59 enabled = true; 60 lsp = true; 61 }; 62 neotree = true; 63 native_lsp = { 64 enabled = true; 65 inlay_hints = { 66 background = true; 67 }; 68 virtual_text = { 69 errors = ["italic"]; 70 hints = ["italic"]; 71 information = ["italic"]; 72 warnings = ["italic"]; 73 ok = ["italic"]; 74 }; 75 underlines = { 76 errors = ["underline"]; 77 hints = ["underline"]; 78 information = ["underline"]; 79 warnings = ["underline"]; 80 }; 81 }; 82 }; 83 }; 84 }; 85 86 # Misc. Global Options 87 88 extraConfigLua = '' 89 vim.diagnostic.config({ 90 signs = { 91 text = { 92 [vim.diagnostic.severity.ERROR] = "", 93 [vim.diagnostic.severity.WARN] = "", 94 }, 95 }, 96 }) 97 ''; 98 99 opts = { 100 number = true; 101 relativenumber = true; 102 smartindent = true; 103 cursorline = true; 104 showtabline = 2; 105 tabstop = 2; 106 shiftwidth = 2; 107 breakindent = true; 108 fillchars.__raw = "[[eob: ,fold: ,foldopen:,foldsep: ,foldclose:]]"; 109 foldcolumn = "1"; 110 foldlevel = 100; 111 foldlevelstart = 100; 112 foldenable = true; 113 mouse = ""; 114 }; 115 116 # Allow system clipboard copying if graphical env is enabled 117 clipboard.providers.wl-copy.enable = lib.mkDefault config.cow.gdi.enable; 118 119 # Associate .mdx extension to mdx buffer type 120 filetype.extension = { 121 mdx = "mdx"; 122 }; 123 124 # Auto Run Commands 125 126 autoCmd = [ 127 { 128 group = "restore_cursor"; 129 event = ["BufReadPost"]; 130 pattern = "*"; 131 callback.__raw = '' 132 function() 133 if 134 vim.fn.line "'\"" > 1 135 and vim.fn.line "'\"" <= vim.fn.line "$" 136 and vim.bo.filetype ~= "commit" 137 and vim.fn.index({ "xxd", "gitrebase" }, vim.bo.filetype) == -1 138 then 139 vim.cmd "normal! g`\"" 140 end 141 end 142 ''; 143 } 144 ]; 145 146 autoGroups = { 147 # Try to restore cursor to the last position it was at last time this file was opened 148 restore_cursor = {}; 149 }; 150 151 # Keybinds 152 153 # globals.mapleader = " "; 154 155 keymaps = let 156 prefixMap = group: 157 builtins.map (k: { 158 action = "<cmd>${ 159 if group ? "cmdPrefix" 160 then group.cmdPrefix + " " 161 else "" 162 }${k.action}<cr>"; 163 key = "${group.prefix}${k.key}"; 164 options = k.options; 165 }) 166 group.keys; 167 in 168 lib.lists.flatten ( 169 builtins.map (g: 170 if g ? "group" 171 then prefixMap g 172 else g) [ 173 { 174 action = ''"+p''; 175 key = "<C-S-V>"; 176 options.desc = "Paste from system clipboard"; 177 } 178 { 179 action = ''"+y''; 180 key = "<C-S-C>"; 181 options.desc = "Copy to system clipboard"; 182 } 183 { 184 action = ''"+x''; 185 key = "<C-S-X>"; 186 options.desc = "Cut to system clipboard"; 187 } 188 { 189 action = "<cmd>Format<cr>"; 190 key = "<C-S-I>"; 191 options.desc = "Format Buffer"; 192 } 193 { 194 group = "Tab Navigation"; 195 prefix = "<Tab>"; 196 keys = [ 197 { 198 action = "BufferLineCycleNext"; 199 key = "e"; 200 options.desc = "Next Tab"; 201 } 202 { 203 action = "BufferLineCyclePrev"; 204 key = "q"; 205 options.desc = "Previous Tab"; 206 } 207 { 208 action = "Neotree toggle reveal"; 209 key = "t"; 210 options.desc = "Toggle Neotree"; 211 } 212 ]; 213 } 214 { 215 group = "Tab Closing"; 216 prefix = "<Tab><Tab>"; 217 keys = [ 218 { 219 action = "BufferLineCloseLeft"; 220 key = "q"; 221 options.desc = "Close Tab Left"; 222 } 223 { 224 action = "BufferLineCloseRight"; 225 key = "e"; 226 options.desc = "Close Tab Right"; 227 } 228 { 229 action = "BufferLinePickClose"; 230 key = "<Tab>"; 231 options.desc = "Pick Tab and Close"; 232 } 233 { 234 action = "BufferLineCloseOthers"; 235 key = "w"; 236 options.desc = "Close Other Tabs"; 237 } 238 ]; 239 } 240 { 241 action = "<cmd>Bdelete<cr>"; 242 key = "<C-q>"; 243 options.desc = "Close Current Buffer"; 244 } 245 { 246 group = "LSP Actions"; 247 prefix = "<C-.>"; 248 cmdPrefix = "Lspsaga"; 249 keys = [ 250 { 251 action = "code_action code_action"; 252 key = "a"; 253 options.desc = "Code Actions"; 254 } 255 { 256 action = "rename"; 257 key = "r"; 258 options.desc = "LSP Rename"; 259 } 260 { 261 action = "diagnostic_jump_next"; 262 key = "e"; 263 options.desc = "Next Diagnostic"; 264 } 265 { 266 action = "diagnostic_jump_previous"; 267 key = "E"; 268 options.desc = "Previous Diagnostic"; 269 } 270 { 271 action = "goto_definition"; 272 key = "d"; 273 options.desc = "Jump to Definition"; 274 } 275 { 276 action = "peek_definition"; 277 key = "D"; 278 options.desc = "Peek Definition"; 279 } 280 { 281 action = "finder ref"; 282 key = "fr"; 283 options.desc = "Find References"; 284 } 285 { 286 action = "finder imp"; 287 key = "fi"; 288 options.desc = "Find Implementations"; 289 } 290 { 291 action = "finder def"; 292 key = "fd"; 293 options.desc = "Find Definitions"; 294 } 295 { 296 action = "finder"; 297 key = "ff"; 298 options.desc = "Finder"; 299 } 300 { 301 action = "hover_doc"; 302 key = "h"; 303 options.desc = "Hover Doc"; 304 } 305 ]; 306 } 307 { 308 group = "Telescope"; 309 prefix = " "; 310 cmdPrefix = "Telescope"; 311 keys = [ 312 { 313 key = "u"; 314 action = "undo"; 315 options.desc = "Undo Tree"; 316 } 317 { 318 key = "c"; 319 action = "commands"; 320 options.desc = "Browse Commands"; 321 } 322 { 323 key = "w"; 324 action = "spell_suggest"; 325 options.desc = "Spell Suggest"; 326 } 327 { 328 key = "s"; 329 action = "lsp_document_symbols"; 330 options.desc = "LSP Symbols"; 331 } 332 { 333 key = "t"; 334 action = "treesitter"; 335 options.desc = "Treesitter Symbols"; 336 } 337 { 338 key = "f"; 339 action = "find_files"; 340 options.desc = "Files"; 341 } 342 { 343 key = "gs"; 344 action = "git_status"; 345 options.desc = "Git Status"; 346 } 347 { 348 key = "gb"; 349 action = "git_branches"; 350 options.desc = "Git Branches"; 351 } 352 { 353 key = "gc"; 354 action = "git_commits"; 355 options.desc = "Git Commits"; 356 } 357 { 358 key = "r"; 359 action = "oldfiles"; 360 options.desc = "Recent Files"; 361 } 362 { 363 key = "l"; 364 action = "live_grep"; 365 options.desc = "Live Grep"; 366 } 367 ]; 368 } 369 { 370 action.__raw = "[[<C-\\><C-n><C-w>]]"; 371 mode = ["t"]; 372 key = "<C-w>"; 373 } 374 { 375 action.__raw = "[[<C-\\><C-n>]]"; 376 mode = ["t"]; 377 key = "<esc>"; 378 } 379 { 380 action = "<cmd>WhichKey<cr>"; 381 key = "<C-/>"; 382 } 383 ] 384 ); 385 386 # Plugins 387 388 dependencies = { 389 fd.enable = true; 390 ripgrep.enable = true; 391 tree-sitter.enable = true; 392 }; 393 394 extraPlugins = with pkgs.vimPlugins; 395 (lib.optional config.cow.dev.web {plugin = pkgs.nvim-mdx;}) 396 ++ [ 397 {plugin = flatten-nvim;} # Opens neovim invocations in terminal windows in the current Neovim session 398 {plugin = satellite-nvim;} # Scrollbar 399 {plugin = tiny-devicons-auto-colors-nvim;} # Color language icons 400 ]; 401 402 plugins = { 403 # Navigation 404 405 # Interactive Fuzzy Search w/ various providers 406 telescope = { 407 enable = true; 408 settings.defaults = { 409 layout_config.prompt_position = "top"; 410 }; 411 extensions = { 412 file-browser.enable = true; 413 ui-select.enable = true; 414 undo.enable = true; 415 }; 416 }; 417 418 # Highlight current row 419 illuminate.enable = true; 420 # Underline matching token in buffer 421 cursorline.enable = true; 422 423 # Nicer bdelete 424 bufdelete.enable = true; 425 426 # Tab bar 427 bufferline = { 428 enable = true; 429 settings.highlights.__raw = lib.mkIf config.cow.cat.enable '' 430 require("catppuccin.special.bufferline").get_theme() 431 ''; 432 settings.options = { 433 indicator.style = "none"; 434 show_buffer_close_icons = false; 435 show_close_icon = false; 436 offsets = [ 437 { 438 filetype = "neo-tree"; 439 highlight = "String"; 440 text = "Files"; 441 text_align = "center"; 442 # separator = true; 443 } 444 ]; 445 separator_style = "slant"; 446 hover = { 447 enabled = true; 448 delay = 150; 449 reveal = ["close"]; 450 }; 451 sort_by = "insert_at_end"; 452 diagnostics = "nvim_lsp"; 453 diagnostics_indicator.__raw = '' 454 function(count, level, diagnostics_dict, context) 455 local icon = level:match("error") and " " or " " 456 return " " .. icon .. count 457 end 458 ''; 459 }; 460 }; 461 462 # Tree file manager 463 neo-tree = { 464 enable = true; 465 settings = { 466 hide_root_node = false; 467 follow_current_file.enabled = true; 468 add_blank_line_at_top = true; 469 default_component_configs = { 470 container.right_padding = 2; 471 name.trailing_slash = true; 472 indent = { 473 indent_size = 2; 474 with_expanders = true; 475 }; 476 }; 477 window.width = 40; 478 auto_clean_after_session_restore = true; 479 close_if_last_window = true; 480 filesystem.components.name.__raw = '' 481 function(config, node, state) 482 local components = require('neo-tree.sources.common.components') 483 local name = components.name(config, node, state) 484 if node:get_depth() == 1 then 485 name.text = vim.fs.basename(vim.loop.cwd() or "") .. "/" 486 end 487 return name 488 end 489 ''; 490 }; 491 }; 492 493 # In-buffer UI/tweaks 494 495 # Toggle relativenumber off when inserting 496 numbertoggle.enable = true; 497 498 # Folding implementation 499 nvim-ufo.enable = true; 500 501 # Nicer indenting 502 indent-o-matic.enable = true; 503 intellitab.enable = true; 504 505 # Image Previews 506 image.enable = true; 507 508 # Color Picker 509 ccc = { 510 enable = true; 511 settings = { 512 inputs = [ 513 "ccc.input.rgb" 514 "ccc.input.hsl" 515 "ccc.input.hwb" 516 "ccc.input.lab" 517 "ccc.input.lch" 518 "ccc.input.oklab" 519 "ccc.input.oklch" 520 "ccc.input.cmyk" 521 "ccc.input.hsluv" 522 "ccc.input.okhsl" 523 "ccc.input.hsv" 524 "ccc.input.okhsv" 525 "ccc.input.xyz" 526 ]; 527 }; 528 }; 529 530 # Completions 531 cmp = { 532 enable = true; 533 settings = { 534 sources = map (name: {inherit name;}) [ 535 "nvim_lsp" 536 "nvim_lsp_signature_help" 537 "spell" 538 "path" 539 "buffer" 540 ]; 541 mapping = { 542 "<Esc>" = "cmp.mapping.abort()"; 543 "<Tab>" = "cmp.mapping.confirm({ select = true })"; 544 "<Up>" = "cmp.mapping(cmp.mapping.select_prev_item(), {'i', 's'})"; 545 "<Down>" = "cmp.mapping(cmp.mapping.select_next_item(), {'i', 's'})"; 546 }; 547 }; 548 }; 549 550 # Color-coded matching symbols 551 rainbow-delimiters.enable = true; 552 553 # Line number column + LSP + folding + etc. 554 statuscol = { 555 enable = true; 556 settings.segments = let 557 dispCond = { 558 __raw = '' 559 function(ln) 560 return vim.bo.filetype ~= "neo-tree" 561 end 562 ''; 563 }; 564 in [ 565 { 566 click = "v:lua.ScSa"; 567 condition = [ 568 dispCond 569 ]; 570 text = [ 571 "%s" 572 ]; 573 } 574 { 575 click = "v:lua.ScLa"; 576 condition = [dispCond]; 577 text = [ 578 { 579 __raw = "require('statuscol.builtin').lnumfunc"; 580 } 581 ]; 582 } 583 { 584 click = "v:lua.ScFa"; 585 condition = [ 586 dispCond 587 { 588 __raw = "require('statuscol.builtin').not_empty"; 589 } 590 ]; 591 text = [ 592 { 593 __raw = "require('statuscol.builtin').foldfunc"; 594 } 595 " " 596 ]; 597 } 598 ]; 599 }; 600 601 # Informational bottom line 602 lualine = { 603 enable = true; 604 settings = { 605 extensions = [ 606 "trouble" 607 "toggleterm" 608 ]; 609 610 options = { 611 theme = lib.mkIf config.cow.cat.enable "catppuccin"; 612 disabled_filetypes = ["neo-tree"]; 613 ignore_focus = ["neo-tree"]; 614 }; 615 }; 616 }; 617 618 # New Windows 619 620 # Nice notifications and progress indicator 621 fidget = { 622 enable = true; 623 settings.notification = { 624 override_vim_notify = true; 625 window = { 626 y_padding = 2; 627 x_padding = 2; 628 zindex = 50; 629 align = "top"; 630 winblend = 0; 631 }; 632 }; 633 }; 634 635 # Interactive keybind helper 636 which-key = { 637 enable = true; 638 settings = { 639 show_help = true; 640 preset = "modern"; 641 win.wo.winblend = 8; 642 }; 643 }; 644 645 # Toggle a Terminal Window 646 toggleterm = { 647 enable = true; 648 luaConfig.post = '' 649 local flatten = require('flatten') 650 651 ---@type Terminal? 652 local saved_terminal 653 654 flatten.setup({ 655 hooks = { 656 should_block = function(argv) 657 return vim.tbl_contains(argv, "-b") 658 end, 659 pre_open = function() 660 local term = require("toggleterm.terminal") 661 local termid = term.get_focused_id() 662 saved_terminal = term.get(termid) 663 end, 664 post_open = function(opts) 665 if saved_terminal then 666 saved_terminal:close() 667 else 668 vim.api.nvim_set_current_win(opts.winnr) 669 end 670 671 if opts.filetype == "gitcommit" or opts.filetype == "gitrebase" then 672 vim.api.nvim_create_autocmd("BufWritePost", { 673 buffer = opts.bufnr, 674 once = true, 675 callback = vim.schedule_wrap(function() 676 require('bufdelete').bufdelete(opts.bufnr, true) 677 end), 678 }) 679 end 680 end, 681 block_end = function() 682 vim.schedule(function() 683 if saved_terminal then 684 saved_terminal:open() 685 saved_terminal = nil 686 end 687 end) 688 end, 689 }, 690 window = { 691 open = "alternate", 692 }, 693 }) 694 ''; 695 settings = { 696 size = 20; 697 open_mapping = "[[<C-x>]]"; 698 direction = "horizontal"; 699 start_in_insert = true; 700 insert_mappings = true; 701 terminal_mappings = true; 702 }; 703 }; 704 705 # Language Integration and LSPs 706 707 # Provider for syntax highlighting, symbols, etc. when not using an LSP 708 treesitter = { 709 enable = true; 710 luaConfig.post = lib.mkIf config.cow.dev.web '' 711 require('mdx').setup() 712 ''; 713 settings = { 714 highlight = { 715 enable = true; 716 additional_vim_regex_highlighting = false; 717 }; 718 indent.enable = true; 719 }; 720 }; 721 722 # Formatting code using multiple providers 723 conform-nvim = { 724 enable = true; 725 settings = { 726 formatters.treefmt = { 727 require_cwd = false; 728 }; 729 formatters_by_ft = { 730 "*" = ["treefmt"]; 731 }; 732 default_format_opts = { 733 lsp_format = "fallback"; 734 }; 735 }; 736 # Taken from https://github.com/stevearc/conform.nvim/blob/master/doc/recipes.md#format-command 737 luaConfig.post = '' 738 vim.api.nvim_create_user_command("Format", function(args) 739 local range = nil 740 if args.count ~= -1 then 741 local end_line = vim.api.nvim_buf_get_lines(0, args.line2 - 1, args.line2, true)[1] 742 range = { 743 start = { args.line1, 0 }, 744 ["end"] = { args.line2, end_line:len() }, 745 } 746 end 747 require("conform").format({ range = range, timeout_ms = 5000 }) 748 end, { range = true }) 749 ''; 750 }; 751 752 # Common buffer type associations to activate LSPs 753 lspconfig.enable = true; 754 755 # UI for many LSP features 756 lspsaga = { 757 enable = true; 758 settings = { 759 symbol_in_winbar.enable = false; 760 implement.enable = false; 761 lightbulb.enable = false; 762 ui = { 763 code_action = "󰛨"; 764 actionfix = "󰖷"; 765 }; 766 hover = { 767 openCmd = "!xdg-open"; 768 openLink = "<leader>o"; 769 maxWidth = 0.5; 770 maxHeight = 0.4; 771 }; 772 rename.autoSave = true; 773 finder = { 774 keys.close = "<ESC>"; 775 }; 776 codeAction.keys.quit = "<ESC>"; 777 }; 778 }; 779 780 # Get latest version of deps in a Cargo.toml as inline hints 781 crates.enable = lib.mkDefault config.cow.dev.rust; 782 783 # Better TS LSP, etc. 784 typescript-tools.enable = lib.mkDefault config.cow.dev.web; 785 786 # Misc. UI 787 788 # UI and provider for diagnostics 789 trouble = { 790 enable = true; 791 }; 792 793 # Icons used in many places for languages 794 web-devicons.enable = true; 795 }; 796 797 lsp = lib.mkDefault { 798 inlayHints.enable = true; 799 800 servers = let 801 inherit 802 (config.cow.dev) 803 dotnet 804 python 805 haskell 806 rust 807 web 808 c 809 typst 810 ; 811 in { 812 clangd.enable = c; 813 tinymist.enable = typst; 814 astro.enable = web; 815 hls = lib.mkIf haskell { 816 enable = true; 817 # ghcPackage = pkgs.haskell.compiler.ghc912; 818 package = pkgs.haskell.packages.ghc912.haskell-language-server; 819 }; 820 mdx_analyzer = lib.mkIf web { 821 enable = true; 822 package = pkgs.mdx-language-server; 823 }; 824 # ts_ls.enable = web; 825 html.enable = web; 826 emmer_language_server.enable = web; 827 marksman.enable = web; 828 cssls.enable = web; 829 jsonls.enable = web; 830 yamlls.enable = web; 831 ruff.enable = python; 832 csharp_ls.enable = dotnet; 833 nil_ls.enable = true; 834 bashls.enable = true; 835 nushell.enable = config.cow.nushell.enable; 836 taplo.enable = rust; 837 typos_lsp.enable = true; 838 rust_analyzer = lib.mkIf rust { 839 enable = true; 840 package = pkgs.rust-analyzer-nightly; 841 packageFallback = true; 842 }; 843 lemminx.enable = web; 844 eslint.enable = web; 845 just.enable = config.cow.utils.enable; 846 }; 847 }; 848 }; 849 }; 850}