{ config, pkgs, lib, ... }: with lib; let rust-analyzer = if (pkgs ? rust-analyzer-nightly && config.class ? dev) then pkgs.rust-analyzer-nightly else pkgs.rust-analyzer; rust-sdk = with pkgs; if config.class ? dev then symlinkJoin { name = "nvim-fenix"; paths = [ (fenix.latest.withComponents [ "cargo" "clippy-preview" "rust-src" "rust-std" "rustc" "rustfmt-preview" ]) cargo-watch ]; } else symlinkJoin { name = "nvim-rust"; paths = [rustc cargo rustfmt clippy cargo-watch]; }; in { programs.neovim = { # https://github.com/nix-community/home-manager/blob/master/modules/programs/neovim.nix enable = true; vimAlias = true; withNodeJs = true; extraPackages = with pkgs; [fzf xclip fish]; plugins = with pkgs.vimPlugins; [ vim-fugitive git-blame-nvim neogit diffview-nvim vim-airline zephyr-nvim vim-toml elm-vim split-term-vim # vim-grammarous markdown-preview-nvim rainbow nvim-web-devicons fzf-vim editorconfig-nvim vim-vsnip # tmuxline-vim which-key-nvim nvim-dap nvim-dap-ui nvim-notify nvim-cmp cmp-omni cmp-nvim-lsp cmp-nvim-lua cmp-nvim-lsp-signature-help cmp-nvim-lsp-document-symbol cmp-buffer cmp-async-path cmp-spell cmp-vsnip cmp-conventionalcommits cmp-calc cmp-cmdline rustaceanvim ( if config.programs.neovim.package.version == "0.10.0" then throw "lsp-inlayhints-nvim may be removed" else lsp-inlayhints-nvim ) # https://github.com/mrcjkb/rustaceanvim/discussions/46#discussioncomment-7620822 plenary-nvim crates-nvim nvim-lspconfig telescope-nvim telescope-undo-nvim oil-nvim ]; extraLuaConfig = let # tries to compute a package set required to make package resolution in lua # via deps["pkg"] work inherit (builtins) head tail hasAttr getAttr; pkg = parts: pkgs: let rem = tail parts; subset = if hasAttr (head parts) pkgs then getAttr (head parts) pkgs else throw "no attr ${name}"; in if rem == [] then subset else builtins.addErrorContext "${concatStringsSep "." parts}" (pkg rem subset); getPkg = name: let parts = builtins.split "\\." name; in pkg parts pkgs; packages = file: let out = builtins.readFile (pkgs.runCommandLocal "extract-deps" { nativeBuildInputs = [pkgs.gnused]; __contentAddressed = true; } '' sed -nr 's/.*(deps\["(.*)_path"\]).*/\2/p' ${file} | uniq > $out ''); names = lib.splitString "\n" out; in filter (name: name != "") names; paths = (listToAttrs (map (name: { inherit name; value = getPkg name; }) (packages "${confDir}/*.lua"))) // { lldb = pkgs.lldb; rust_analyzer = rust-analyzer; typst_lsp = pkgs.typst-lsp; inherit (pkgs.python3Packages) python-lsp-server; inherit (pkgs.luajitPackages) lua-lsp; inherit (pkgs.nodePackages) typescript-language-server bash-language-server; }; pathsLua = pkgs.writeTextFile { name = "nvim-deps.lua"; text = '' deps = {} ${concatStringsSep "\n " (mapAttrsToList (name: path: ''deps["${name}_path"] = "${path}"'') paths)} return deps ''; }; confDir = lib.sourceFilesBySuffices ./. ["lua" "vim"]; in with lib; '' vim.cmd [[source ${confDir}/init.vim]] package.path = package.path .. ";${confDir}/?.lua;${pathsLua};" dofile("${confDir}/init.lua") ''; }; programs.git.ignores = [".nvim_session"]; xdg.configFile."nvim/coc-settings.json".text = let preferProjectEnv = binName: alternate: pkgs.writeShellScript "${binName}-switcher" '' if command -v ${binName}; then ${binName} ''${@} else ${alternate} ''${@} fi ''; addSDK = { name, lsp, sdk, }: with pkgs; runCommandLocal name { nativeBuildInputs = [makeWrapper]; } '' makeWrapper ${lsp} $out \ --prefix PATH : ${lib.makeBinPath sdk} ''; in builtins.toJSON { python = { pythonPath = preferProjectEnv "python3" (pkgs.python3 + "/bin/python3"); formatting.autopep8Path = "${pkgs.python3Packages.autopep8}/bin/autopep8"; }; pyright = { enable = true; }; # https://rust-analyzer.github.io/manual.html rust-analyzer = { cargo = { allFeatures = true; runBuildScripts = true; autoreload = true; }; completion = { autoimport.enable = true; privateEditable.enable = true; fullFunctionSignatures.enable = true; # https://github.com/rust-lang/rust-analyzer/pull/15582 snippets = { "return Err(..)" = { postfix = ["reterr"]; body = ''return Err($${receiver});''; description = "return expression as Err"; scope = "expr"; }; "format!(..)" = { postfix = ["fmt"]; body = ''format!($${receiver})''; description = "use receiver as format string"; scope = "expr"; }; "wrap { .. }" = { postfix = ["brace" "wrap"]; body = ''{$${receiver}}''; description = "wrap this type in { .. }"; scope = "expr"; }; "async move { .. }" = { postfix = ["asyncm"]; body = ''async move {$${receiver}}''; description = "wrap this type in async move { .. }"; scope = "expr"; }; "try { .. }" = { postfix = ["try"]; body = ''let result: Result<_,_> = try {$${receiver}};''; description = "wrap this type in try { .. }"; scope = "expr"; }; "while let Some(item) = { .. } {}" = { postfix = ["letwhile"]; body = ''while let Some(item) = $${receiver} {}''; description = "wrap this type in while let Some"; scope = "expr"; }; "let $x = $x.clone()" = { postfix = ["cloned"]; body = ''let $${receiver} = $${receiver}.clone();''; description = "clone a variable into a new binding"; scope = "expr"; }; "let $x = $x.into()" = { postfix = ["into"]; body = ''let $${receiver} = $${receiver}.clone();''; description = "call into() and create a new binding"; scope = "expr"; }; "assert_eq!($x, .. );" = { postfix = ["asseq"]; body = ''assert_eq!($${receiver}, );''; description = "create an assertion for the expression"; scope = "expr"; }; }; }; procMacro = { enable = true; attributes.enable = true; }; serverPath = addSDK { name = "rust-env"; lsp = "${rust-analyzer}/bin/rust-analyzer"; sdk = [rust-sdk]; }; imports.group.enable = true; inlayHints = { closureReturnTypeHints.enable = false; }; highlightRelated = { yieldPoints.enable = true; references.enable = true; exitPoints.enable = true; breakPoints.enable = true; }; checkOnSave = { #https://github.com/rust-lang/rust-clippy/issues/9560 enable = false; allFeatures = true; command = "clippy"; # extraArgs = [ "--message-format=json" ]; }; diagnostics = { enable = true; experimental.enable = true; }; files.excludeDirs = ["result" "target"]; extraEnv = { RA_LOG = "debug"; }; }; languageserver = { ccls = { command = preferProjectEnv "ccls" "${pkgs.ccls}/bin/ccls"; filetypes = ["c" "cc" "cpp" "c++" "objc" "objcpp"]; rootPatterns = [".ccls" "compile_commands.json" ".git/" ".hg/"]; initializationOptions = { cache = { directory = "${config.xdg.cacheHome}/ccls"; }; }; clang.extraOptions = ["-std=c++20"]; }; nix = { command = "${pkgs.nil}/bin/nil"; formatting.command = "${pkgs.nixpkgs-fmt}/bin/nixpkgs-fmt"; flake = { autoArchive = true; autoEvalInputs = true; }; maxMemoryMB = 1024 * 4; rootPatterns = ["flake.nix"]; filetypes = ["nix"]; }; diagnostic-languageserver = { filetypes = { sh = ["${pkgs.shellcheck}/bin/shellcheck"]; }; }; cSpell = {diagnosticLevel = "hint";}; }; }; home.sessionVariables = rec { EDITOR = "nvim"; VISUAL_EDITOR = EDITOR; }; }