diff --git a/README.md b/README.md new file mode 100644 index 0000000..c50d03e --- /dev/null +++ b/README.md @@ -0,0 +1,247 @@ +# nvimConfig + +Modular Neovim wrapper config. Defines a wrapped `vv` binary with per-category packages, plugins, and environment variables. Available as NixOS module, Home Manager module, or single-starflake import. + +## Quick start (imported into another flake) + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + rixpkgs.url = "github:dwinkler1/rixpkgs/af2dd3f7b4b172077747c0869d4e30702fb71b0e"; + fran = { + url = "github:dwinkler1/fran"; + inputs.nixpkgs.follows = "rixpkgs"; + }; + nvimConfig = { + url = "github:dwinkler1/nvimConfig"; + inputs = { + nixpkgs.follows = "nixpkgs"; + rixpkgs.follows = "rixpkgs"; + fran.follows = "fran"; + }; + }; + }; + + outputs = { self, nixpkgs, nvimConfig, ... } @ inputs: let + systems = ["aarch64-darwin" "x86_64-linux" "aarch64-linux"]; + forAllSystems = nixpkgs.lib.genAttrs systems; + in { + packages = forAllSystems (system: let + pkgs = import nixpkgs { + inherit system; + config.allowUnfree = true; + overlays = [ nvimConfig.overlays.dependencies ]; + }; + evalResult = nvimConfig.inputs.wrappers.lib.evalModules { + modules = [ + nvimConfig.wrapperModules.default + projectSettings # see below + ]; + }; + in { + default = evalResult.config.wrap { inherit pkgs; }; + }); + + devShells = forAllSystems (system: let + pkgs = import nixpkgs { + inherit system; + config.allowUnfree = true; + overlays = [ nvimConfig.overlays.dependencies ]; + }; + evalResult = nvimConfig.inputs.wrappers.lib.evalModules { + modules = [ + nvimConfig.wrapperModules.default + projectSettings + ]; + }; + nv = evalResult.config.wrap { inherit pkgs; }; + in { + default = pkgs.mkShell { + packages = + [ nv ] + ++ (evalResult.config.catPkgs.markdown or []) + ++ (evalResult.config.catPkgs.nix or []); + }; + }); + }; +} +``` + +## Home Manager module + +```nix +{ inputs, ... }: { + home.packages = [ + (inputs.nvimConfig.homeModules.default { inherit pkgs; }) + # Or via the overlay: + # pkgs.vv + ]; +} +``` + +## NixOS module + +```nix +{ inputs, ... }: { + environment.systemPackages = [ + (inputs.nvimConfig.nixosModules.default { inherit pkgs; }) + ]; +} +``` + +## Overriding settings + +Define a `projectSettings` attrset and pass it as the second module in `evalModules`. Every option in `nvimConfig.wrapperConfigs.default` can be overridden here. + +### Categories + +Enable only the cats you need. Defaults are listed in `modules/module/settings/cats.nix`. + +```nix +projectSettings = { + cats = { + python = true; # enable Python tooling + r = false; # disable R + nix = true; + lua = false; + markdown = false; + optional = false; + gitPlugins = false; + }; +}; +``` + +### Language packages (overlay extension) + +Add Python/R/Julia libraries that get appended to the overlay base packages. + +```nix +projectSettings = { + settings.lang_packages = { + python = with pkgs.python3Packages; [ pandas numpy ]; + r = with pkgs.rpkgs.rPackages; [ fixest data_table ]; + julia = [ "StatsBase" "Plots" ]; + }; +}; +``` + +Use `lib.mkForce` to replace rather than append: + +```nix +projectSettings = { + settings.lang_packages = { + python = pkgs.lib.mkForce (with pkgs.python3Packages; [ polars duckdb ]); + }; +}; +``` + +### Runtime dependencies (per-cat packages) + +Override `catPkgs` to add tools that appear in both the wrapper PATH and the devShell. To add always-available packages not gated by any cat, use `runtimePkgs` directly (see next section). + +```nix +projectSettings = { + catPkgs = { + nix = [ pkgs.nil pkgs.nixfmt ]; + python = [ + (pkgs.python3.withPackages (ps: [ ps.pandas ps.duckdb ])) + pkgs.ruff + ]; + }; +}; +``` + +### Direct runtimePkgs override (always-on) + +Add packages to `catPkgs.always` for tools that should always be available regardless of other cat toggles. + +```nix +projectSettings = { + catPkgs = { + always = [ pkgs.git pkgs.curl pkgs.pre-commit ]; + }; +}; +``` + +### Runtime env vars + +```nix +projectSettings = { + env = { + MY_PROJECT_VAR = "some-value"; + }; + envDefault = { + EDITOR = "vv"; + }; +}; +``` + +`env` values are forced; `envDefault` values can be overridden by the user's shell. + +### Neovim settings + +```nix +projectSettings = { + settings = { + colorscheme = "kanagawa"; + background = "dark"; + wrapRc = true; + config_directory = ./nvim; # path to init.lua + lua/ dir + aliases = [ "v" "nvim" ]; # extra binary names (default: [ "vvim" ]) + }; + binName = "nv"; # binary name (default: vv) +}; +``` + +## Adding plugins + +```nix +projectSettings = { + specs = { + extraPlugins = { + data = with pkgs.vimPlugins; [ + telescope-nvim + which-key-nvim + ]; + }; + + extraLazy = { + lazy = true; + data = with pkgs.vimPlugins; [ dashboard-nvim ]; + }; + + # Inject Lua config at startup + extraLua = { + data = pkgs.writeText "extra.lua" '' + vim.opt.number = true + vim.opt.relativenumber = true + ''; + before = ["INIT_MAIN"]; + }; + }; +}; +``` + +## Architecture + +``` +langPackages (flake.nix) ──► settings.lang_packages (module option) + │ +cats.nix ──► config.cats │ + │ │ + ▼ ▼ + cat-packages.nix ──► config.catPkgs. + │ │ + ┌───────┘ │ + ▼ ▼ + deps.nix (runtimePkgs) flake.nix (devShells) + │ │ + ▼ ▼ + wrapped vv PATH nix develop PATH +``` + +- **Overlays** (`overlays/`) inject base packages into nixpkgs (rWrapper, quarto, baseRPackages, basePythonPackages). +- **cat-packages.nix** is the single source of truth for per-category packages. Each list is gated by its cat toggle. +- **deps.nix** wires `catPkgs` into the wrapper's runtime PATH. +- **flake.nix** reads `catPkgs` for the devShell. `langPackages` feeds into `settings.lang_packages`. diff --git a/flake.lock b/flake.lock index 698ccf9..02745a6 100644 --- a/flake.lock +++ b/flake.lock @@ -74,20 +74,19 @@ "nixpkgs": [ "rixpkgs" ], - "rnvimsrc": [ - "plugins-r" - ] + "rnvimsrc": "rnvimsrc" }, "locked": { - "lastModified": 1778684156, - "narHash": "sha256-Z4y1tQfkIsPK4NRxGn668HMDfWxnxNxSJ0CAOOXiIfY=", + "lastModified": 1779438909, + "narHash": "sha256-1lvv0bdvSVyeCIgeZ7Ws7ffbDFurA5LJscS9dRLHzC8=", "owner": "dwinkler1", "repo": "r_nvim_nix", - "rev": "2f49dfee27886068e2f49cbd54558ce4cc424c82", + "rev": "ec17e22ab362a0ddfd6c2e9c5e95d43897a143be", "type": "github" }, "original": { "owner": "dwinkler1", + "ref": "v0.99.4", "repo": "r_nvim_nix", "type": "github" } @@ -108,6 +107,23 @@ "type": "github" } }, + "rnvimsrc": { + "flake": false, + "locked": { + "lastModified": 1776905071, + "narHash": "sha256-dXox6qEs1VDE7vPNDoN8bY4g06uj1IEs6uki72w8lpA=", + "owner": "R-nvim", + "repo": "R.nvim", + "rev": "582f2af11290ac067e49018db38e12a511325556", + "type": "github" + }, + "original": { + "owner": "R-nvim", + "ref": "v0.99.4", + "repo": "R.nvim", + "type": "github" + } + }, "root": { "inputs": { "fran": "fran", diff --git a/flake.nix b/flake.nix index a19f7dc..cde55df 100644 --- a/flake.nix +++ b/flake.nix @@ -11,9 +11,12 @@ }; rixpkgs.url = "github:dwinkler1/rixpkgs/af2dd3f7b4b172077747c0869d4e30702fb71b0e"; - r-nvim-nix.url = "github:dwinkler1/r_nvim_nix"; - r-nvim-nix.inputs.rnvimsrc.follows = "plugins-r"; - r-nvim-nix.inputs.nixpkgs.follows = "rixpkgs"; + r-nvim-nix = { + url = "github:dwinkler1/r_nvim_nix/v0.99.4"; + inputs = { + nixpkgs.follows = "rixpkgs"; + }; + }; fran = { url = "github:dwinkler1/fran"; @@ -44,14 +47,15 @@ duckdb polars ]; - r = (with pkgs.rpkgs.rPackages; [ - arrow - broom - data_table - janitor - styler - pkgs.nvimcom - ]) ++ [ pkgs.nvimcom ]; + r = + (with pkgs.rpkgs.rPackages; [ + arrow + broom + data_table + janitor + styler + ]) + ++ [pkgs.nvimcom]; julia = [ "DataFramesMeta" "QuackIO" @@ -59,17 +63,6 @@ }; mkWrapperConfig = pkgs: { - cats = { - clickhouse = false; - gitPlugins = true; - julia = false; - lua = true; - markdown = true; - nix = true; - optional = false; - python = false; - r = true; - }; settings = { lang_packages = langPackages pkgs; }; @@ -97,8 +90,12 @@ mkPkgs = system: import nixpkgs { inherit system; - config = { allowUnfree = true; }; - overlays = [ overlayDefs.dependencyOverlay ]; + config = {allowUnfree = true;}; + overlays = [ + overlayDefs.dependencyOverlay + inputs.r-nvim-nix.overlays.default + inputs.fran.overlays.default + ]; }; module = (import ./modules/neovim.nix) inputs; @@ -140,56 +137,21 @@ pkgs = mkPkgs system; nvimPkg = wrapperSettings pkgs; - langPkgs = langPackages pkgs; - - pythonPackages = let - python_packages_fn = - if pkgs ? basePythonPackages - then ps: pkgs.basePythonPackages ps ++ langPkgs.python - else _: langPkgs.python; - in - with pkgs; [ - (python3.withPackages python_packages_fn) - nodejs - ruff - basedpyright - uv - ]; - - rPackages = let - r_packages = (pkgs.baseRPackages or []) ++ langPkgs.r; - in - with pkgs; [ - (rWrapper.override {packages = r_packages;}) - radianWrapper - (quarto.override {extraRPackages = r_packages;}) - air-formatter - yaml-language-server - nvimcom - rnvimserver - ]; - - juliaPackages = let - julia_with_packages = pkgs.julia-bin.withPackages langPkgs.julia; - in [julia_with_packages]; - - markdownPackages = with pkgs; [ - python313Packages.pylatexenc - quarto - zk - ]; - - shellPackages = - [nvimPkg] - ++ pkgs.lib.optionals wrapper.config.cats.python pythonPackages - ++ pkgs.lib.optionals wrapper.config.cats.r rPackages - ++ pkgs.lib.optionals wrapper.config.cats.julia juliaPackages - ++ pkgs.lib.optionals wrapper.config.cats.markdown markdownPackages; + shellPackages = [nvimPkg] + ++ wrapper.config.catPkgs.always or [] + ++ wrapper.config.catPkgs.python or [] + ++ wrapper.config.catPkgs.r or [] + ++ wrapper.config.catPkgs.julia or [] + ++ wrapper.config.catPkgs.markdown or [] + ++ wrapper.config.catPkgs.optional or [] + ++ wrapper.config.catPkgs.external or [] + ++ wrapper.config.catPkgs.nix or [] + ++ wrapper.config.catPkgs.lua or [] + ++ wrapper.config.catPkgs.clickhouse or []; in { default = pkgs.mkShell { name = "vShell"; packages = shellPackages; - nativeBuildInputs = pkgs.lib.optionals wrapper.config.cats.optional [ pkgs.devenv ]; shellHook = '' echo 'I am a NixShell' export R_HOME=$(R RHOME) @@ -223,9 +185,10 @@ cat version_output.txt >> $out fi ''; - module-eval = - let _ = wrapper.config; - in pkgs.runCommand "check-module-eval" {} '' + module-eval = let + _ = wrapper.config; + in + pkgs.runCommand "check-module-eval" {} '' echo "Module evaluation successful" > $out ''; } diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix new file mode 100644 index 0000000..3570ff6 --- /dev/null +++ b/modules/module/settings/cat-packages.nix @@ -0,0 +1,109 @@ +{ + config, + pkgs, + lib, + ... +}: +let + maybe = cat: pkgsList: + lib.optionals (config.cats.${cat} or false) pkgsList; +in +{ + options.catPkgs = lib.mkOption { + type = lib.types.attrsOf (lib.types.listOf lib.types.package); + description = "Per-cat package lists, gated by cat toggles. Single source of truth for runtimeDeps and devShells."; + }; + + config.catPkgs = { + always = maybe "always" (with pkgs; [ ]); + + clickhouse = maybe "clickhouse" (with pkgs; [ clickhouse-lts ]); + + external = maybe "external" (with pkgs; [ + perl + ruby + shfmt + sqlfluff + tree-sitter + ]); + + julia = maybe "julia" [ + (pkgs.julia-bin.withPackages config.settings.lang_packages.julia) + ]; + + lua = maybe "lua" (with pkgs; [ lua-language-server ]); + + markdown = maybe "markdown" (with pkgs; [ + python313Packages.pylatexenc + quarto + zk + ]); + + nix = maybe "nix" (with pkgs; [ + alejandra + nix-doc + nixd + ]); + + optional = maybe "optional" (with pkgs; [ + bat + broot + devenv + dust + fd + fzf + gawk + gh + git + hunspell + hunspellDicts.de-at + hunspellDicts.en-us + ispell + jq + just + lazygit + man + ncdu + pigz + poppler + ripgrep + tokei + wget + yq + zathura + ]); + + python = maybe "python" (let + python_packages_fn = + if pkgs ? basePythonPackages + then ps: pkgs.basePythonPackages ps ++ config.settings.lang_packages.python + else _: config.settings.lang_packages.python; + python_with_packages = pkgs.python3.withPackages python_packages_fn; + in + with pkgs; [ + python_with_packages + nodejs + ruff + basedpyright + uv + ]); + + r = maybe "r" (let + r_packages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; + in + with pkgs; [ + (rWrapper.override { packages = r_packages; }) + radianWrapper + (quarto.override { extraRPackages = r_packages; }) + air-formatter + yaml-language-server + rnvimserver + ]); + + # cats without packages get empty lists + general = [ ]; + gitPlugins = [ ]; + treesitterParsers = [ pkgs.tree-sitter ]; + utils = [ ]; + }; +} diff --git a/modules/module/settings/cats.nix b/modules/module/settings/cats.nix index 13758d4..e1895f7 100644 --- a/modules/module/settings/cats.nix +++ b/modules/module/settings/cats.nix @@ -13,6 +13,7 @@ Set a category to `false` to skip its dependency/plugin specs. Available categories: + - always: always-on packages (not gated by a toggle) - clickhouse: Clickhouse client and tools - external: external tools and integrations - general: core Neovim plugins/features @@ -30,6 +31,7 @@ }; config.cats = { + always = lib.mkDefault true; clickhouse = lib.mkDefault false; external = lib.mkDefault true; general = lib.mkDefault true; diff --git a/modules/module/settings/runtime-path.nix b/modules/module/settings/runtime-path.nix deleted file mode 100644 index b321019..0000000 --- a/modules/module/settings/runtime-path.nix +++ /dev/null @@ -1,33 +0,0 @@ -{ - config, - lib, - ... -}: -let - collect_runtime_packages = runtime_deps_type: - config.specCollect - (acc: spec: - let - is_enabled = if spec ? enable then spec.enable else true; - has_runtime_deps = (spec.runtimeDeps or false) == runtime_deps_type; - packages = spec.runtimePackages or [ ]; - in - acc ++ lib.optionals (is_enabled && has_runtime_deps) packages - ) - [ ]; - - prefix_packages = collect_runtime_packages "prefix"; - - to_path_specs = packages: [ - { - data = [ - "PATH" - ":" - "${lib.makeBinPath packages}" - ]; - } - ]; -in -{ - config.prefixVar = lib.optionals (prefix_packages != [ ]) (to_path_specs prefix_packages); -} diff --git a/modules/module/specs/deps.nix b/modules/module/specs/deps.nix index c3a40ea..b0c437f 100644 --- a/modules/module/specs/deps.nix +++ b/modules/module/specs/deps.nix @@ -13,133 +13,69 @@ }; }; - config.specs.external = { + config.specs.always = lib.mkIf (config.cats.always or true) { + data = lib.mkDefault null; + runtimeDeps = "prefix"; + runtimePkgs = config.catPkgs.always; + }; + + config.specs.external = lib.mkIf (config.cats.external or true) { data = lib.mkDefault null; before = ["INIT_MAIN"]; config = '' vim.o.shell = "${pkgs.zsh}/bin/zsh" ''; runtimeDeps = "prefix"; - runtimePkgs = with pkgs; [ - perl - ruby - shfmt - sqlfluff - tree-sitter - ]; + runtimePkgs = config.catPkgs.external; }; config.specs.optional = lib.mkIf (config.cats.optional or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; before = ["INIT_MAIN"]; - runtimePkgs = with pkgs; [ - bat - broot - devenv - dust - fd - fzf - gawk - gh - git - hunspell - hunspellDicts.de-at - hunspellDicts.en-us - ispell - jq - just - lazygit - man - ncdu - pigz - poppler - ripgrep - tokei - wget - yq - zathura - ]; + runtimePkgs = config.catPkgs.optional; }; config.specs.markdown = lib.mkIf (config.cats.markdown or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = with pkgs; [ - python313Packages.pylatexenc - quarto - zk - ]; + runtimePkgs = config.catPkgs.markdown; }; config.specs.nix = lib.mkIf (config.cats.nix or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = with pkgs; [ - alejandra - nix-doc - nixd - ]; + runtimePkgs = config.catPkgs.nix; }; config.specs.lua = lib.mkIf (config.cats.lua or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = with pkgs; [ - lua-language-server - ]; + runtimePkgs = config.catPkgs.lua; }; config.specs.python = lib.mkIf (config.cats.python or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = let - python_packages_fn = - if pkgs ? basePythonPackages - then ps: pkgs.basePythonPackages ps ++ config.settings.lang_packages.python - else _: config.settings.lang_packages.python; - python_with_packages = pkgs.python3.withPackages python_packages_fn; - in - with pkgs; [ - python_with_packages - nodejs - ruff - basedpyright - uv - ]; + runtimePkgs = config.catPkgs.python; }; config.specs.r = lib.mkIf (config.cats.r or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = let - r_packages = (pkgs.baseRPackages or []) ++ config.settings.lang_packages.r; - in - with pkgs; [ - (rWrapper.override {packages = r_packages;}) - radianWrapper - (quarto.override {extraRPackages = r_packages;}) - air-formatter - yaml-language-server - updateR - ]; + runtimePkgs = config.catPkgs.r; }; config.specs.julia = lib.mkIf (config.cats.julia or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = let - julia_with_packages = - pkgs.julia-bin.withPackages config.settings.lang_packages.julia; - in [julia_with_packages]; + runtimePkgs = config.catPkgs.julia; }; config.specs.clickhouse = lib.mkIf (config.cats.clickhouse or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = with pkgs; [ - clickhouse-lts - ]; + runtimePkgs = config.catPkgs.clickhouse; }; config.runtimePkgs = config.specCollect (acc: v: acc ++ (v.runtimePkgs or [])) []; diff --git a/modules/neovim.nix b/modules/neovim.nix index 64d2cda..f4ca40f 100644 --- a/modules/neovim.nix +++ b/modules/neovim.nix @@ -9,6 +9,7 @@ inputs: { imports = [ wlib.wrapperModules.neovim + ./module/settings/cat-packages.nix ./module/specs/deps.nix ./module/specs/plugins.nix ./module/settings/core.nix @@ -16,7 +17,6 @@ inputs: ./module/settings/env.nix ./module/settings/hosts.nix ./module/settings/lang-packages.nix - ./module/settings/runtime-path.nix ]; options.nvim-lib.neovimPlugins = lib.mkOption {