Compare commits
8 Commits
e4a8b07117
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
5e1b9332a4
|
|||
|
c9b976f0ae
|
|||
|
07a78b6012
|
|||
|
94411c0abe
|
|||
|
7415ea0b54
|
|||
|
184a2f5545
|
|||
|
b413c8af81
|
|||
|
5c4045f4a6
|
@@ -24,19 +24,63 @@ you can tweak.
|
|||||||
|
|
||||||
## Import the modules into your own flake
|
## Import the modules into your own flake
|
||||||
|
|
||||||
Add this repo as a flake input and import its modules from your own configuration:
|
Add this repo as an input and assemble your machines in a private flake. Below is a
|
||||||
|
**realistic example layout**. Structurally only `flake.nix` is required — every other path
|
||||||
|
is just one way to organize your config; rename, merge, inline, or drop them freely. The one
|
||||||
|
bit of *content* a bootable machine can't skip is its `hardware-configuration.nix`
|
||||||
|
(`nixos-generate-config` output); the `hosts/<host>/` directory is only where this example
|
||||||
|
keeps it.
|
||||||
|
|
||||||
|
```
|
||||||
|
your-inventory/
|
||||||
|
├── flake.nix # inputs (pub + follows) + nixosConfigurations.<host>
|
||||||
|
├── flake.lock
|
||||||
|
├── system/ # system-level modules
|
||||||
|
│ ├── regional.nix # time zone / keymap / locale (see Overriding)
|
||||||
|
│ ├── packages.nix # extra system packages
|
||||||
|
│ └── tailscale.nix # VPN / network topology
|
||||||
|
├── home/ # home-manager modules, one file per concern
|
||||||
|
│ ├── identity.nix # git identity, name/email
|
||||||
|
│ ├── ssh.nix # SSH hosts (topology — stays private)
|
||||||
|
│ ├── syncthing.nix # Syncthing peers
|
||||||
|
│ └── … # personal apps: browser, editor, chat, …
|
||||||
|
└── hosts/
|
||||||
|
└── myhost/
|
||||||
|
├── default.nix # hostname, user account, host-specific system bits
|
||||||
|
└── hardware-configuration.nix # `nixos-generate-config` output
|
||||||
|
```
|
||||||
|
|
||||||
|
The `flake.nix` that wires it together:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{
|
{
|
||||||
inputs.config.url = "github:<owner>/<repo>";
|
inputs = {
|
||||||
# …
|
pub.url = "github:<owner>/<repo>";
|
||||||
outputs = { nixpkgs, config, home-manager, ... }: {
|
# Reuse the public flake's pins so versions never drift between the two.
|
||||||
|
nixpkgs.follows = "pub/nixpkgs";
|
||||||
|
home-manager.follows = "pub/home-manager";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = inputs@{ self, nixpkgs, pub, home-manager, ... }: {
|
||||||
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
|
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
modules = [
|
modules = [
|
||||||
config.nixosModules.default
|
pub.nixosModules.default # generic system baseline
|
||||||
./hosts/myhost # your hardware + hostname
|
./system/regional.nix # your regional values (see below)
|
||||||
|
./hosts/myhost # hardware + hostname
|
||||||
home-manager.nixosModules.home-manager
|
home-manager.nixosModules.home-manager
|
||||||
{ home-manager.users.me.imports = [ config.homeModules.default ]; }
|
{
|
||||||
|
home-manager.useGlobalPkgs = true;
|
||||||
|
home-manager.useUserPackages = true;
|
||||||
|
# Inject your own inputs into your home modules; the public ones ignore them.
|
||||||
|
home-manager.extraSpecialArgs = { inherit inputs; };
|
||||||
|
home-manager.users.me.imports = [
|
||||||
|
pub.homeModules.default # generic home baseline
|
||||||
|
./home/identity.nix # your git identity, name/email, …
|
||||||
|
./home/ssh.nix # your SSH hosts (topology stays private)
|
||||||
|
# … more personal home modules
|
||||||
|
];
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -46,6 +90,30 @@ Add this repo as a flake input and import its modules from your own configuratio
|
|||||||
Your machine list, hostnames, and any private specifics stay in *your* flake — you pull
|
Your machine list, hostnames, and any private specifics stay in *your* flake — you pull
|
||||||
only the generic modules from here.
|
only the generic modules from here.
|
||||||
|
|
||||||
|
## Overriding the baseline
|
||||||
|
|
||||||
|
The public modules are a baseline you extend without forking. Three ways to shape them
|
||||||
|
from your private flake, in rough order of how often you reach for them:
|
||||||
|
|
||||||
|
1. **Neutral default → your real value.** Region-agnostic settings ship as `mkDefault`
|
||||||
|
placeholders (`console.keyMap = "us"`, `time.timeZone = "UTC"`, `en_US` locale). Set the
|
||||||
|
real ones in a private module — no `mkForce` needed:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# system/regional.nix
|
||||||
|
{ time.timeZone = "Europe/Berlin"; console.keyMap = "de"; }
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **`local.*` options.** A module that exposes a knob puts it under the `local.*`
|
||||||
|
namespace with a default. Override in one line, e.g. a fuller TeX Live:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
local.texlive.package = pkgs.texlive.withPackages (ps: [ ps.scheme-full ]);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Extra modules.** Anything with no public counterpart is just another module in the
|
||||||
|
import lists above — personal apps, SSH/Syncthing topology, secrets wiring.
|
||||||
|
|
||||||
## Architecture: three layers
|
## Architecture: three layers
|
||||||
|
|
||||||
Kept apart on purpose. When adding config, decide which layer it belongs to:
|
Kept apart on purpose. When adding config, decide which layer it belongs to:
|
||||||
|
|||||||
Generated
+60
@@ -1,5 +1,26 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": [
|
||||||
|
"nixvim",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778716662,
|
||||||
|
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"home-manager": {
|
"home-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -37,10 +58,34 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixvim": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1782919967,
|
||||||
|
"narHash": "sha256-pRwjfB5HQJ3m8J8bOR43pPHtHI7VUJSqwLA3P06cOY0=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixvim",
|
||||||
|
"rev": "667c8471f4a0fb24d702d1a61af8609f1a5f1ba6",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"ref": "nixos-26.05",
|
||||||
|
"repo": "nixvim",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
|
"nixvim": "nixvim",
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -63,6 +108,21 @@
|
|||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
@@ -17,9 +17,20 @@
|
|||||||
url = "github:oxalica/rust-overlay";
|
url = "github:oxalica/rust-overlay";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# nixvim: declarative Neovim as home-manager options — the whole editor
|
||||||
|
# (plugins, LSP, keymaps) lives in modules/nvim.nix, pinned by this flake's
|
||||||
|
# lock. Track the release branch matching nixpkgs (a mismatched branch fails
|
||||||
|
# with `vimPlugins.<name> not found`). `follows` keeps one nixpkgs: both are
|
||||||
|
# on the same stable channel, so the version skew nixvim warns about (only an
|
||||||
|
# issue against unstable) does not arise here.
|
||||||
|
nixvim = {
|
||||||
|
url = "github:nix-community/nixvim/nixos-26.05";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs@{ self, nixpkgs, home-manager, rust-overlay, ... }:
|
outputs = inputs@{ self, nixpkgs, home-manager, rust-overlay, nixvim, ... }:
|
||||||
let
|
let
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
in {
|
in {
|
||||||
@@ -28,7 +39,11 @@
|
|||||||
# imports = [ config.nixosModules.default ];
|
# imports = [ config.nixosModules.default ];
|
||||||
# home-manager.users.<you>.imports = [ config.homeModules.default ];
|
# home-manager.users.<you>.imports = [ config.homeModules.default ];
|
||||||
nixosModules.default = import ./modules/nixos.nix;
|
nixosModules.default = import ./modules/nixos.nix;
|
||||||
homeModules.default = import ./modules/home.nix;
|
# nixvim's home-manager module rides along so `modules/nvim.nix` can set
|
||||||
|
# `programs.nixvim.*`; consumers get the editor by importing this one module.
|
||||||
|
homeModules.default = {
|
||||||
|
imports = [ nixvim.homeModules.nixvim ./modules/home.nix ];
|
||||||
|
};
|
||||||
|
|
||||||
# ---- The example / template host -----------------------------------
|
# ---- The example / template host -----------------------------------
|
||||||
# Real and buildable; dogfoods the modules above. Clone, replace
|
# Real and buildable; dogfoods the modules above. Clone, replace
|
||||||
|
|||||||
+56
-3
@@ -79,9 +79,50 @@ let
|
|||||||
2) ${pkgs.systemd}/bin/systemctl poweroff ;;
|
2) ${pkgs.systemd}/bin/systemctl poweroff ;;
|
||||||
esac
|
esac
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
# Swap the physical left/right placement of a two-monitor setup at runtime.
|
||||||
|
# Reflows the pair adjacent from x=0 (left one at origin, right one butted
|
||||||
|
# against its logical width) after reversing their order — so it stays
|
||||||
|
# gap-free regardless of differing widths, unlike a naive x-origin exchange
|
||||||
|
# (equal widths only). Machine-agnostic: reads whichever two connectors are
|
||||||
|
# live from `hyprctl -j`, no connector names. Ephemeral (hyprctl keyword) —
|
||||||
|
# the per-host default layout is restored on the next reload/relogin. No-op
|
||||||
|
# unless exactly two monitors are connected.
|
||||||
|
#
|
||||||
|
# Hyprland doesn't reliably re-notify layer-shell clients when an output is
|
||||||
|
# reconfigured at runtime (a known limitation — same class as the mirror/unplug
|
||||||
|
# cases where waybar needs a restart). Observed here: after the move, waybar and
|
||||||
|
# the wallpaper keep drawing at the pre-swap offsets, so the moved monitor's
|
||||||
|
# bar/bg land off-screen. Re-init both against the new geometry: waybar rebuilds
|
||||||
|
# its bars on SIGUSR2, and awww only paints outputs present when `awww img` last
|
||||||
|
# ran, so re-push the wallpaper symlink. Both are idempotent — harmless when a
|
||||||
|
# surface happened not to lag (which surface stales seems to race).
|
||||||
|
swapMonitors = pkgs.writeShellScript "hypr-swap-monitors" ''
|
||||||
|
set -eu
|
||||||
|
hc=${config.wayland.windowManager.hyprland.finalPackage}/bin/hyprctl
|
||||||
|
jq=${pkgs.jq}/bin/jq
|
||||||
|
data=$("$hc" monitors -j)
|
||||||
|
n=$(printf '%s' "$data" | "$jq" length)
|
||||||
|
if [ "$n" -ne 2 ]; then
|
||||||
|
${pkgs.libnotify}/bin/notify-send "Monitor swap" "need exactly 2 monitors ($n connected)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# sort by current x → [left, right]; the new left is the old right.
|
||||||
|
cmd=$(printf '%s' "$data" | "$jq" -r '
|
||||||
|
sort_by(.x)
|
||||||
|
| .[1] as $l | .[0] as $r
|
||||||
|
| (($l.width / $l.scale) | floor) as $off
|
||||||
|
| "keyword monitor \($l.name),preferred,0x0,\($l.scale)"
|
||||||
|
+ " ; keyword monitor \($r.name),preferred,\($off)x0,\($r.scale)"')
|
||||||
|
"$hc" --batch "$cmd"
|
||||||
|
# re-anchor the layer-shell clients onto the new geometry (see comment above)
|
||||||
|
${pkgs.procps}/bin/pkill -USR2 -x .waybar-wrapped || true
|
||||||
|
bg="$HOME/.local/share/bg"
|
||||||
|
[ -e "$bg" ] && ${pkgs.awww}/bin/awww img "$bg" || true
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [ ./gpg.nix ./media.nix ./texlive.nix ./librewolf.nix ./mc.nix ./mime.nix ./hyprland-workspaces.nix ];
|
imports = [ ./gpg.nix ./media.nix ./texlive.nix ./librewolf.nix ./mc.nix ./mime.nix ./hyprland-workspaces.nix ./nvim.nix ];
|
||||||
|
|
||||||
# Put ~/.local/bin (the XDG user-bin dir) on PATH so hand-dropped scripts run
|
# Put ~/.local/bin (the XDG user-bin dir) on PATH so hand-dropped scripts run
|
||||||
# by name without wiring each through nix.
|
# by name without wiring each through nix.
|
||||||
@@ -140,7 +181,13 @@ in
|
|||||||
nodejs
|
nodejs
|
||||||
uv # Python: versions, venvs, deps, lockfile. Runs uv-managed
|
uv # Python: versions, venvs, deps, lockfile. Runs uv-managed
|
||||||
# standalone CPython via nix-ld (see modules/nixos.nix).
|
# standalone CPython via nix-ld (see modules/nixos.nix).
|
||||||
|
htop # process viewer; config in ~/.config/htop (XDG-native)
|
||||||
highlight # `ccat` alias: syntax-highlighted cat
|
highlight # `ccat` alias: syntax-highlighted cat
|
||||||
|
file # identify file type by content
|
||||||
|
ripgrep # `rg`: fast recursive grep
|
||||||
|
fd # fast, ergonomic find
|
||||||
|
tree # recursive directory listing
|
||||||
|
unzip # extract .zip (tools that shell out to it expect the binary)
|
||||||
jq # JSON on the command line (scripts, ad-hoc inspection)
|
jq # JSON on the command line (scripts, ad-hoc inspection)
|
||||||
pv # pipe progress meter (throughput, ETA on streamed data)
|
pv # pipe progress meter (throughput, ETA on streamed data)
|
||||||
libreoffice
|
libreoffice
|
||||||
@@ -223,6 +270,7 @@ in
|
|||||||
merge.log = true;
|
merge.log = true;
|
||||||
push = { default = "simple"; followTags = true; };
|
push = { default = "simple"; followTags = true; };
|
||||||
submodule.recurse = true;
|
submodule.recurse = true;
|
||||||
|
fetch.recurseSubmodules = "on-demand";
|
||||||
|
|
||||||
alias = {
|
alias = {
|
||||||
l = "log --pretty=oneline -n 20 --graph --abbrev-commit";
|
l = "log --pretty=oneline -n 20 --graph --abbrev-commit";
|
||||||
@@ -356,7 +404,6 @@ in
|
|||||||
grep = "grep --color=auto";
|
grep = "grep --color=auto";
|
||||||
diff = "diff --color=auto";
|
diff = "diff --color=auto";
|
||||||
ccat = "highlight --out-format=ansi";
|
ccat = "highlight --out-format=ansi";
|
||||||
ka = "killall";
|
|
||||||
g = "git";
|
g = "git";
|
||||||
e = editor;
|
e = editor;
|
||||||
v = editor;
|
v = editor;
|
||||||
@@ -479,7 +526,10 @@ in
|
|||||||
configType = "hyprlang";
|
configType = "hyprlang";
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
monitor = ",preferred,auto,1";
|
# Fallback for hosts that don't pin a layout; a two-monitor host overrides
|
||||||
|
# this with explicit positions (see the private inventory). mkDefault so
|
||||||
|
# that override is a plain value, not a mkForce fight.
|
||||||
|
monitor = lib.mkDefault ",preferred,auto,1";
|
||||||
|
|
||||||
# The qt module sets QT_QPA_PLATFORMTHEME=gtk3 (→ Qt apps read the GTK
|
# The qt module sets QT_QPA_PLATFORMTHEME=gtk3 (→ Qt apps read the GTK
|
||||||
# Adwaita-dark theme), but only into hm-session-vars.sh + the systemd user
|
# Adwaita-dark theme), but only into hm-session-vars.sh + the systemd user
|
||||||
@@ -547,6 +597,7 @@ in
|
|||||||
input = {
|
input = {
|
||||||
kb_layout = "de";
|
kb_layout = "de";
|
||||||
follow_mouse = 1;
|
follow_mouse = 1;
|
||||||
|
numlock_by_default = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
"$mod" = "SUPER";
|
"$mod" = "SUPER";
|
||||||
@@ -582,6 +633,8 @@ in
|
|||||||
# With two monitors the relative +1 just toggles between them.
|
# With two monitors the relative +1 just toggles between them.
|
||||||
"$mod, o, focusmonitor, +1"
|
"$mod, o, focusmonitor, +1"
|
||||||
"$mod SHIFT, o, movecurrentworkspacetomonitor, +1"
|
"$mod SHIFT, o, movecurrentworkspacetomonitor, +1"
|
||||||
|
# Swap the two monitors' left/right placement (see swapMonitors above).
|
||||||
|
"$mod CTRL, o, exec, ${swapMonitors}"
|
||||||
] ++ (
|
] ++ (
|
||||||
# Dynamic definitions for viewing workspaces 1 to 9 and moving windows between them.
|
# Dynamic definitions for viewing workspaces 1 to 9 and moving windows between them.
|
||||||
# References physical key codes for robustness with the SHIFT key, e.g. `code:10` instead of `1`.
|
# References physical key codes for robustness with the SHIFT key, e.g. `code:10` instead of `1`.
|
||||||
|
|||||||
+6
-6
@@ -41,14 +41,14 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Time zone. Template default — override per host/region.
|
# Neutral template defaults for regional/localized settings. All are
|
||||||
time.timeZone = "Europe/Berlin";
|
# mkDefault: set the real values (time zone, keymap, locale categories) in your
|
||||||
|
# own config so this public baseline stays region-agnostic.
|
||||||
# Internationalisation properties. Template defaults — override per region.
|
time.timeZone = lib.mkDefault "UTC";
|
||||||
i18n.defaultLocale = lib.mkDefault "en_US.UTF-8";
|
i18n.defaultLocale = lib.mkDefault "en_US.UTF-8";
|
||||||
console = {
|
console = {
|
||||||
font = "Lat2-Terminus16";
|
font = lib.mkDefault "Lat2-Terminus16";
|
||||||
keyMap = "de";
|
keyMap = lib.mkDefault "us";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Programs.
|
# Programs.
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
# Neovim, configured declaratively via nixvim. The editor — plugins, LSP,
|
||||||
|
# completion, keymaps — is defined here and pinned by the flake lock; nothing is
|
||||||
|
# downloaded at runtime, unlike an imperative distro (NvChad/LazyVim). The nixvim
|
||||||
|
# home-manager module that provides `programs.nixvim` is imported for consumers by
|
||||||
|
# `homeModules.default` in flake.nix, so this file only sets options.
|
||||||
|
#
|
||||||
|
# Consumer knobs (the option-with-default contract): `local.nvim.enable` to opt
|
||||||
|
# out wholesale, `local.nvim.colorscheme` to swap theme in one line, and
|
||||||
|
# `local.nvim.lspServers` to reshape the language set. Everything else is
|
||||||
|
# opinionated — but since nixvim exposes every plugin as an option, a consumer can
|
||||||
|
# still override any `programs.nixvim.plugins.*` directly from their own config.
|
||||||
|
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.local.nvim;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.local.nvim = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to enable the nixvim-based Neovim setup.";
|
||||||
|
};
|
||||||
|
|
||||||
|
colorscheme = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "catppuccin";
|
||||||
|
example = "tokyonight";
|
||||||
|
description = ''
|
||||||
|
nixvim colorscheme module to enable (the attr under `colorschemes.*`,
|
||||||
|
e.g. "catppuccin", "tokyonight", "gruvbox"). Catppuccin is tuned below
|
||||||
|
(mocha flavour); for another scheme set its `colorschemes.<name>.settings`
|
||||||
|
from your own config if you want to tweak it.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
lspServers = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [
|
||||||
|
"nixd" # Nix — this repo's own language, so a sensible public baseline
|
||||||
|
"bashls" # Shell — likewise generic enough to default on
|
||||||
|
];
|
||||||
|
example = [ "nixd" "bashls" "rust_analyzer" "basedpyright" ];
|
||||||
|
description = ''
|
||||||
|
lspconfig server names to enable under `plugins.lsp.servers.<name>`.
|
||||||
|
Each must exist in nixvim's server list and its binary in nixpkgs. The
|
||||||
|
public default is deliberately minimal (Nix + shell); a consumer sets its
|
||||||
|
own language set here — and any per-server tuning a pick needs (e.g.
|
||||||
|
rust_analyzer's `installRustc`) via `programs.nixvim.plugins.lsp.servers`.
|
||||||
|
Note: standalone Lua wants "lua_ls", but Lua embedded in Nix strings is
|
||||||
|
invisible to any LSP, so it is not needed just to edit this config.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
programs.nixvim = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
# Point nixvim at our (followed) nixpkgs instead of the rev it pins. Both
|
||||||
|
# track nixos-26.05, so the version skew nixvim warns about does not arise,
|
||||||
|
# and this keeps a single nixpkgs in the closure. Setting it explicitly is
|
||||||
|
# also what silences that warning.
|
||||||
|
nixpkgs.source = pkgs.path;
|
||||||
|
|
||||||
|
# System clipboard on Wayland: yanks go to / puts read from wl-clipboard,
|
||||||
|
# so copy/paste crosses the nvim boundary. Enabling the provider pulls the
|
||||||
|
# wl-clipboard binaries in as a dependency.
|
||||||
|
clipboard = {
|
||||||
|
register = "unnamedplus";
|
||||||
|
providers.wl-copy.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
globals = {
|
||||||
|
mapleader = " ";
|
||||||
|
maplocalleader = " ";
|
||||||
|
};
|
||||||
|
|
||||||
|
opts = {
|
||||||
|
number = true;
|
||||||
|
relativenumber = true; # relative line numbers for quick j/k motions
|
||||||
|
signcolumn = "yes"; # always show the gutter so text doesn't jump
|
||||||
|
cursorline = true;
|
||||||
|
termguicolors = true; # 24-bit colour, required by catppuccin & co.
|
||||||
|
scrolloff = 8; # keep 8 lines of context around the cursor
|
||||||
|
|
||||||
|
expandtab = true; # spaces, not tabs
|
||||||
|
shiftwidth = 2;
|
||||||
|
tabstop = 2;
|
||||||
|
smartindent = true;
|
||||||
|
|
||||||
|
ignorecase = true; # case-insensitive search…
|
||||||
|
smartcase = true; # …unless the query has a capital
|
||||||
|
undofile = true; # persistent undo across sessions
|
||||||
|
splitright = true;
|
||||||
|
splitbelow = true;
|
||||||
|
|
||||||
|
# What :mksession stores (persistence.nvim). Drops "blank" and "terminal"
|
||||||
|
# from nvim's default so empty/plugin windows (neo-tree, toggleterm) aren't
|
||||||
|
# serialised — they restore as broken empty splits otherwise. Adds
|
||||||
|
# "globals", which some plugins rely on. Pairs with the PersistenceSavePre
|
||||||
|
# neo-tree close below.
|
||||||
|
sessionoptions = "buffers,curdir,folds,globals,tabpages,winsize";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Theme. Swap via local.nvim.colorscheme; catppuccin gets the mocha flavour.
|
||||||
|
# mkMerge keeps the computed `${cfg.colorscheme}` key from colliding with the
|
||||||
|
# literal `catppuccin` one when they happen to be the same attr.
|
||||||
|
colorschemes = lib.mkMerge [
|
||||||
|
{ ${cfg.colorscheme}.enable = true; }
|
||||||
|
(lib.mkIf (cfg.colorscheme == "catppuccin") {
|
||||||
|
catppuccin.settings.flavour = "mocha";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
plugins = {
|
||||||
|
# Shared icon set for the tree, statusline, completion menu.
|
||||||
|
web-devicons.enable = true;
|
||||||
|
|
||||||
|
# Syntax via tree-sitter. The default grammar set is broad, so most
|
||||||
|
# languages get highlighting without an LSP or any per-language wiring.
|
||||||
|
treesitter = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
highlight.enable = true;
|
||||||
|
indent.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# LSP. Servers come from local.nvim.lspServers; blink-cmp feeds its
|
||||||
|
# completion capabilities in automatically (setupLspCapabilities).
|
||||||
|
lsp = {
|
||||||
|
enable = true;
|
||||||
|
servers = lib.genAttrs cfg.lspServers (_: { enable = true; });
|
||||||
|
keymaps = {
|
||||||
|
silent = true;
|
||||||
|
lspBuf = {
|
||||||
|
gd = "definition";
|
||||||
|
gD = "declaration";
|
||||||
|
K = "hover";
|
||||||
|
"<leader>rn" = "rename";
|
||||||
|
"<leader>ca" = "code_action";
|
||||||
|
};
|
||||||
|
# gr/gi (references, implementations) are wired to Telescope pickers in
|
||||||
|
# the global keymaps below instead — fuzzy + preview browsing beats the
|
||||||
|
# quickfix list that vim.lsp.buf.references would populate. gd stays a
|
||||||
|
# direct jump here (usually one target; Ctrl-o returns).
|
||||||
|
diagnostic = {
|
||||||
|
# Idiomatic vim bracket pair for stepping a list, kept as the public
|
||||||
|
# default. A consumer whose layout makes brackets awkward (they need
|
||||||
|
# AltGr on QWERTZ) can add comfortable aliases from their own config:
|
||||||
|
# this option is attrsOf, so extra keys merge in without conflict.
|
||||||
|
"[d" = "goto_prev";
|
||||||
|
"]d" = "goto_next";
|
||||||
|
"<leader>ds" = "open_float"; # show the diagnostic under the cursor
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Completion. blink.cmp — faster and lighter to configure than nvim-cmp.
|
||||||
|
blink-cmp = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
keymap.preset = "default"; # C-space open, C-y confirm, C-n/C-p move
|
||||||
|
appearance.nerd_font_variant = "mono";
|
||||||
|
sources.default = [ "lsp" "path" "snippets" "buffer" ];
|
||||||
|
completion.documentation.auto_show = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Fuzzy finder — files, live grep, open buffers (buffer switching lives
|
||||||
|
# here rather than a bufferline; see the S-h/S-l binds below too).
|
||||||
|
telescope.enable = true;
|
||||||
|
|
||||||
|
# File tree.
|
||||||
|
neo-tree.enable = true;
|
||||||
|
|
||||||
|
# Statusline, NvChad-ish blocks: powerline separators, one global bar,
|
||||||
|
# theme following the colorscheme.
|
||||||
|
lualine = {
|
||||||
|
enable = true;
|
||||||
|
settings.options = {
|
||||||
|
theme = "auto";
|
||||||
|
globalstatus = true;
|
||||||
|
section_separators = { left = ""; right = ""; };
|
||||||
|
component_separators = { left = ""; right = ""; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Inline colour swatches for #rrggbb / colour names.
|
||||||
|
colorizer.enable = true;
|
||||||
|
|
||||||
|
# Git signs in the gutter.
|
||||||
|
gitsigns.enable = true;
|
||||||
|
|
||||||
|
# Session persistence: auto-saves a session per project directory into XDG
|
||||||
|
# state, restorable later. <leader>S restores the current dir's session; a
|
||||||
|
# consumer wanting auto-restore on a bare `nvim` adds a VimEnter autocmd.
|
||||||
|
persistence.enable = true;
|
||||||
|
|
||||||
|
# Floating terminal toggled with Ctrl-\ (shares nvim's cwd). Try it; if
|
||||||
|
# a tiled foot next to nvim serves you better, drop this block.
|
||||||
|
toggleterm = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
open_mapping = { __raw = "[[<c-\\>]]"; };
|
||||||
|
direction = "float";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Popup of the pending keybinds after leader — a live, always-accurate
|
||||||
|
# cheatsheet. The group labels name each leader subtree so the popup reads
|
||||||
|
# by category; <leader>? opens the full list on demand (keymaps below).
|
||||||
|
which-key = {
|
||||||
|
enable = true;
|
||||||
|
settings.spec = [
|
||||||
|
{ __unkeyed-1 = "<leader>f"; group = "Find (Telescope)"; }
|
||||||
|
{ __unkeyed-1 = "<leader>d"; group = "Diagnostics"; }
|
||||||
|
{ __unkeyed-1 = "<leader>b"; group = "Buffer"; }
|
||||||
|
{ __unkeyed-1 = "<leader>c"; group = "Code"; }
|
||||||
|
{ __unkeyed-1 = "<leader>r"; group = "Refactor"; }
|
||||||
|
{ __unkeyed-1 = "<leader>s"; group = "Search"; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Global (non-LSP) keymaps. Deliberately small — a starting point to grow.
|
||||||
|
keymaps = [
|
||||||
|
{ mode = "n"; key = "<leader>w"; action = "<cmd>w<cr>"; options.desc = "Save"; }
|
||||||
|
{ mode = "n"; key = "<leader>q"; action = "<cmd>q<cr>"; options.desc = "Quit window"; }
|
||||||
|
{ mode = "n"; key = "<leader>Q"; action = "<cmd>qa<cr>"; options.desc = "Quit all (exit nvim)"; }
|
||||||
|
# Restore the saved session for the current directory (persistence.nvim).
|
||||||
|
{ mode = "n"; key = "<leader>S"; action.__raw = "function() require('persistence').load() end"; options.desc = "Restore session"; }
|
||||||
|
{ mode = "n"; key = "<leader>e"; action = "<cmd>Neotree toggle<cr>"; options.desc = "File tree"; }
|
||||||
|
{ mode = "n"; key = "<Esc>"; action = "<cmd>nohlsearch<cr>"; }
|
||||||
|
|
||||||
|
# Telescope
|
||||||
|
{ mode = "n"; key = "<leader>ff"; action = "<cmd>Telescope find_files<cr>"; options.desc = "Find files"; }
|
||||||
|
{ mode = "n"; key = "<leader>fg"; action = "<cmd>Telescope live_grep<cr>"; options.desc = "Live grep"; }
|
||||||
|
{ mode = "n"; key = "<leader>fb"; action = "<cmd>Telescope buffers<cr>"; options.desc = "Buffers"; }
|
||||||
|
{ mode = "n"; key = "<leader>fh"; action = "<cmd>Telescope help_tags<cr>"; options.desc = "Help tags"; }
|
||||||
|
|
||||||
|
# LSP navigation via Telescope pickers (fuzzy + preview) for the
|
||||||
|
# many-result lookups; gd/gD stay direct jumps (plugins.lsp.keymaps above).
|
||||||
|
{ mode = "n"; key = "gr"; action = "<cmd>Telescope lsp_references<cr>"; options.desc = "References"; }
|
||||||
|
{ mode = "n"; key = "gi"; action = "<cmd>Telescope lsp_implementations<cr>"; options.desc = "Implementations"; }
|
||||||
|
|
||||||
|
# Buffer navigation (no bufferline: cycle with Shift-h/l, list via <leader>fb)
|
||||||
|
{ mode = "n"; key = "<S-l>"; action = "<cmd>bnext<cr>"; options.desc = "Next buffer"; }
|
||||||
|
{ mode = "n"; key = "<S-h>"; action = "<cmd>bprevious<cr>"; options.desc = "Prev buffer"; }
|
||||||
|
{ mode = "n"; key = "<leader>bd"; action = "<cmd>bdelete<cr>"; options.desc = "Close buffer"; }
|
||||||
|
|
||||||
|
# Cheatsheet: <leader>? pops the full which-key list; <leader>sk fuzzy-
|
||||||
|
# searches every active mapping via Telescope (catches non-leader binds too).
|
||||||
|
{ mode = "n"; key = "<leader>?"; action = "<cmd>WhichKey<cr>"; options.desc = "All keybinds (which-key)"; }
|
||||||
|
{ mode = "n"; key = "<leader>sk"; action = "<cmd>Telescope keymaps<cr>"; options.desc = "Search keymaps"; }
|
||||||
|
];
|
||||||
|
|
||||||
|
# persistence saves the window layout via :mksession, but neo-tree's special
|
||||||
|
# buffer doesn't serialise — a saved tree window restores as a blank split.
|
||||||
|
# Close neo-tree just before a session is written so the layout stays clean.
|
||||||
|
autoCmd = [
|
||||||
|
{
|
||||||
|
event = "User";
|
||||||
|
pattern = "PersistenceSavePre";
|
||||||
|
callback.__raw = ''function() pcall(vim.cmd, "Neotree close") end'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user