{ 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 neogit diffview-nvim vim-airline zephyr-nvim vim-nix vim-toml elm-vim vim-markdown split-term-vim # vim-grammarous markdown-preview-nvim rainbow nvim-web-devicons fzf-vim editorconfig-nvim vim-vsnip # tmuxline-vim nvim-dap nvim-dap-ui nvim-notify nvim-cmp cmp-nvim-lsp cmp-nvim-lua cmp-buffer cmp-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 cmp-conventionalcommits cmp-calc cmp-cmdline rustaceanvim plenary-nvim crates-nvim nvim-lspconfig telescope-nvim telescope-undo-nvim oil-nvim distant-nvim ]; extraLuaConfig = let paths = { lldb = pkgs.lldb; rust_analyzer = rust-analyzer; }; pathsLua = pkgs.writeTextFile { name = "nvim-deps.lua"; text = '' return { ${concatStringsSep ",\n " (mapAttrsToList (name: path: ''${name}_path = "${path}"'') paths)} } ''; }; 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; }; }