refactor package installation

This commit is contained in:
Daniel Winkler 2026-05-23 20:15:23 +10:00
commit a0ba90c4d2
8 changed files with 433 additions and 193 deletions

247
README.md Normal file
View file

@ -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.<cat>
│ │
┌───────┘ │
▼ ▼
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`.

28
flake.lock generated
View file

@ -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",

View file

@ -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; [
r =
(with pkgs.rpkgs.rPackages; [
arrow
broom
data_table
janitor
styler
pkgs.nvimcom
]) ++ [ pkgs.nvimcom ];
])
++ [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;
};
@ -98,7 +91,11 @@
import nixpkgs {
inherit system;
config = {allowUnfree = true;};
overlays = [ overlayDefs.dependencyOverlay ];
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
'';
}

View file

@ -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 = [ ];
};
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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 [])) [];

View file

@ -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 {