authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-08-17 20:40:58-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-08-17 20:43:47-07:00
logff5e23efc2d910484d7f44da0a67dee52035133f
treea3d5b2c8a5a46f55fc619940cf30fb3a9604083b
parent2e9eda64d4ee4fc9b76ab962de6ef7bb87a7d77d

feat: "autofmt" code formatter

different codebases use different formatters. autofmt looks for project configuration to pick the correct formatter, allowing an editor to simply point to the script and have all ambiguities resolved. this commit overhauls the conform configuration to set up autofmt correctly, as well installing it with 'pkgs.autofmt' closes #6

15 files changed, 722 insertions(+), 113 deletions(-)

files/autofmt.js created+593
......@@ -0,0 +1,593 @@
1#!/usr/bin/env node
2// autofmt v1 - https://paperclover.dev/nix/config/src/branch/main/files/autofmt.js
3//
4// Different codebases use different formatters. Autofmt looks for project
5// configuration to pick the correct formatter, allowing an editor to simply
6// point to this script and ambiguities resolved. This single file program
7// depends only on Node.js v22.
8//
9// When using this repository's Nix-based Neovim configuration, autofmt is
10// automatically configured as the default formatter. To configure manually,
11// set the editor's formatter to run `autofmt --stdio=<filename>`. The filename
12// is used to determine which formatter to invoke.
13//
14// With `conform.nvim`:
15// formatters = {
16// autofmt = { command = "autofmt" }
17// }
18//
19// With Zed:
20// "formatter": {
21// "external": {
22// "command": "autofmt",
23// "arguments": ["--stdio", "{buffer_path}"]
24// }
25// },
26//
27// @ts-nocheck
28
29// -- definitions --
30const extensions = {
31 c: [".c", ".h"],
32 cpp: [".cpp", ".cc", ".cxx", ".hpp", ".hxx", ".hh"],
33 css: [".css"],
34 html: [".html"],
35 javascript: [".js", ".ts", ".cjs", ".cts", ".mjs", ".mts", ".jsx", ".tsx"],
36 json: [".json", ".jsonc"],
37 markdown: [".md", ".markdown"],
38 mdx: [".mdx"],
39 nix: [".nix"],
40 rust: [".rs"],
41 toml: [".toml"],
42 yaml: [".yml", ".yaml"],
43 zig: [".zig"],
44};
45const formatters = {
46 // this object is sorted by priority
47 //
48 // `languages: true` are multiplexers that apply to all listed
49 // languages, as it is assumed a project will setup their multiplexer
50 // correctly for all tracked files.
51 //
52 // if a formatter doesnt match a config file, the first one is picked
53 // as a default, making things like `deno fmt file.ts` or `clang-format`
54 // the default when there are no config files.
55 dprint: {
56 languages: true,
57 files: ["dprint.json", "dprint.jsonc"],
58 cmd: ["dprint", "fmt", "--", "$files"],
59 stdin: (file) => ["dprint", "fmt", "--stdin", file],
60 },
61 treefmt: {
62 languages: true,
63 files: ["treefmt.toml", ".treefmt.toml"],
64 cmd: ["treefmt", "--", "$files"],
65 stdin: (file) => ["treefmt", "--stdin", file],
66 },
67 // -- web ecosystem --
68 deno: {
69 languages: ["javascript", "markdown", "json", "yaml", "html", "css"],
70 files: ["dprint.json", "dprint.jsonc"],
71 cmd: ["deno", "fmt", "--", "$files"],
72 stdin: (file) => ["deno", "fmt", "--ext", path.extname(file).slice(1), "-"],
73 },
74 prettier: {
75 languages: ["javascript", "markdown", "json", "yaml", "mdx", "html", "css"],
76 files: ["node_modules/.bin/prettier"],
77 cmd: ["node_modules/.bin/prettier", "--write", "--", "$files"],
78 stdin: (file) => ["node_modules/.bin/prettier", "--stdin-filepath", file],
79 },
80 biome: {
81 languages: ["javascript", "json", "css"],
82 files: ["node_modules/.bin/biome"],
83 cmd: ["node_modules/.bin/biome", "format", "--", "$files"],
84 stdin: (file) => [
85 "node_modules/bin/biome",
86 "format",
87 `--stdin-file-path=${file}`,
88 ],
89 },
90 "nixfmt-rfc-style": {
91 languages: ["nix"],
92 files: {
93 "flake.nix": (contents) => contents.includes("nixfmt"),
94 },
95 cmd: ["nixfmt", "--", "$files"],
96 stdin: (file) => ["nixfmt", `--filename=${file}`],
97 },
98 alejandra: {
99 languages: ["nix"],
100 files: {
101 "flake.nix": (contents) => contents.includes("alejandra"),
102 },
103 cmd: ["alejandra", "--", "$files"],
104 stdin: () => ["alejandra"],
105 },
106 clang: {
107 languages: ["c", "cpp"],
108 files: true,
109 cmd: ["clang-format", "--", "$files"],
110 stdin: (file) => ["clang-format", `--assume-filename=${file}`],
111 },
112 zig: {
113 languages: ["zig"],
114 files: true,
115 cmd: ["zig", "fmt", "$files"],
116 stdin: (file) => [
117 "zig",
118 "fmt",
119 "--stdin",
120 ...file.endsWith(".zon") ? ["--zon"] : [],
121 ],
122 },
123 rustfmt: {
124 languages: ["rust"],
125 files: true,
126 cmd: ["rustfmt", "--", "$files"],
127 stdin: () => ["rustfmt"],
128 },
129 taplo: {
130 languages: ["toml"],
131 files: ["taplo.toml"],
132 cmd: ["taplo", "format", "--", "$files"],
133 cmd: () => ["taplo", "format", "-"],
134 },
135};
136
137// -- cli --
138if (!fs.globSync) {
139 console.error(`error: autofmt must be run with Node.js v22 or newer`);
140 process.exit(1);
141}
142const [, bin, ...argv] = process.argv;
143let inputs = [];
144let globalExclude = [];
145let gitignore = true;
146let dryRun = false;
147let stdio = null;
148let excludes = [".git"];
149while (argv.length > 0) {
150 const arg = argv.shift();
151 if (arg === "-h" || arg === "--help") usage();
152 else if (arg === "--no-gitignore") gitignore = false;
153 else if (arg === "--dry-run") dryRun = true;
154 else if (arg.match(/^--stdio=./)) {
155 if (stdio) {
156 console.error("error: can only pass --stdio once");
157 usage();
158 }
159 stdio = arg.slice("--stdio=".length);
160 } else if (arg === "--stdio") {
161 const value = argv.shift();
162 if (!value) {
163 console.error("error: missing value for --stdio");
164 usage();
165 }
166 if (stdio) {
167 console.error("error: can only pass --stdio once");
168 usage();
169 }
170 stdio = value;
171 } else if (arg.match(/^--exclude=./)) {
172 excludes.push(arg.slice("--exclude=".length));
173 } else if (arg === "--") {
174 inputs.push(...argv);
175 break;
176 } else if (arg.startsWith("-")) {
177 console.error("error: unknown option " + JSON.stringify(arg));
178 usage();
179 } else inputs.push(arg);
180}
181function usage() {
182 const exe = path.basename(bin);
183 console.error(`usage: ${exe} [...files or directories]`);
184 console.error(``);
185 console.error(`uses the right formatter for the job (by scanning config)`);
186 console.error(`when autofmt reads dirs, it will respect gitignore`);
187 console.error(``);
188 console.error(`to format current directory recursively, run '${exe} .'`);
189 console.error(``);
190 console.error(`options:`);
191 console.error(` --dry-run print commands instead of running them`);
192 console.error(` --no-gitignore do not read '.gitignore'`);
193 console.error(` --exclude=<glob> add an exclusion glob`);
194 console.error(` --stdio=<filename> read/write contents via stdin/stdout`);
195 console.error(``);
196 process.exit(1);
197}
198if (inputs.length === 0 && !stdio) usage();
199if (stdio && inputs.length > 0) {
200 console.error("error: stdio mode only operates on one file");
201 process.exit(1);
202}
203const { sep } = path;
204
205// -- disable warnings --
206const { emit: originalEmit } = process;
207const warnings = ["ExperimentalWarning"];
208process.emit = function (event, error) {
209 return event === "warning" && warnings.includes(error.name)
210 ? false
211 : originalEmit.apply(process, arguments);
212};
213
214// -- vars --
215const extToLanguage = Object.fromEntries(
216 Object.entries(extensions).flatMap(([lang, exts]) =>
217 exts.map((ext) => [ext, lang])
218 ),
219);
220const multis = Object.keys(formatters).filter(
221 (x) => formatters[x].languages === true,
222);
223const files = [];
224const gitignores = new Map();
225const dirs = new Map();
226const cached = new Map();
227
228// -- stdin mode --
229if (stdio) {
230 const fmtWithPath = pickFormatter(stdio);
231 if (!fmtWithPath) {
232 console.error(`No formatter configured for ${path.relative(".", stdio)}`);
233 process.exit(1);
234 }
235 let [fmt, cwd] = fmtWithPath.split("\0");
236 let cmd = formatters[fmt].stdin(stdio);
237 cwd ??= process.cwd();
238 if (dryRun) {
239 console.info(cmd.join(" "));
240 process.exit(0);
241 }
242 const proc = child_process.spawn(cmd[0], cmd.slice(1), {
243 stdio: ["inherit", "inherit", "inherit"],
244 });
245 proc.on("error", (e) => {
246 let message = "";
247 if (e?.code === "ENOENT") {
248 message = `${cmd[0]} is not installed`;
249 } else {
250 message = String(e?.message ?? e);
251 }
252 console.error(`error: ${message}`);
253 process.exit(1);
254 });
255 const [code] = await events.once(proc, "exit");
256 process.exit(code ?? 1);
257}
258
259// -- decide what formatters to run
260inputs = inputs.map((x) => path.resolve(x));
261inputs.forEach(walk);
262const toRun = new Map();
263for (const file of new Set(files)) {
264 const fmt = pickFormatter(file);
265 if (!fmt) {
266 if (inputs.includes(file)) {
267 console.warn(`No formatter configured for ${path.relative(".", file)}`);
268 }
269 continue;
270 }
271 let list = toRun.get(fmt);
272 list ?? toRun.set(fmt, list = []);
273 list.push(file);
274}
275
276// -- create a list of commands --
277const commands = [];
278let totalFiles = 0;
279for (const [fmtWithPath, files] of toRun) {
280 let [fmt, cwd] = fmtWithPath.split("\0");
281 let { cmd } = formatters[fmt];
282 if (cwd) cmd = [path.join(cwd, cmd[0]), ...cmd.slice(1)];
283 cwd ??= process.cwd();
284 let i = 0;
285 totalFiles += files.length;
286 if ((i = cmd.indexOf("$files")) != -1) {
287 const c = cmd.slice();
288 c.splice(i, 1, ...files);
289 commands.push({ cmd: c, cwd, files });
290 } else if ((i = cmd.indexOf("$file")) != -1) {
291 for (const file of files) {
292 const c = cmd.slice();
293 c.splice(i, 1, file);
294 commands.push({ cmd: c, cwd, files: [file] });
295 }
296 } else {
297 throw new Error(`Formatter ${fmt} has incorrectly configured command.`);
298 }
299}
300if (commands.length === 0) {
301 console.error("No formattable files");
302 process.exit(0);
303}
304
305// -- dry run mode --
306if (dryRun) {
307 for (const { cmd } of commands) {
308 console.info(cmd);
309 }
310 process.exit(0);
311}
312
313// -- user interface --
314let filesComplete = 0;
315let lastFile = commands[0].cmd;
316const syncStart = "\u001B[?2026h";
317const syncEnd = "\u001B[?2026l";
318let buffer = "";
319let statusVisible = false;
320const tty = process.stderr.isTTY;
321function writeStatus() {
322 if (!tty) return;
323 clearStatus();
324 buffer ||= syncStart;
325 buffer += `${filesComplete}/${totalFiles} - ${lastFile}`;
326 statusVisible = true;
327}
328function clearStatus() {
329 if (!tty) return;
330 if (!statusVisible) return;
331 buffer ||= syncStart;
332 buffer += "\r\x1b[2K\r";
333 statusVisible = false;
334}
335function flush() {
336 if (!buffer) return;
337 const width = Math.max(1, process.stderr.columns - 1);
338 process.stderr.write(
339 buffer.split("\n").map((x) => x.slice(0, width)).join("\n") + syncEnd,
340 );
341 buffer = "";
342}
343
344// -- async process queue --
345let running = 0;
346/** @param cmd {{ cmd: string, cwd: string, files: string[] }} */
347function run({ cmd, cwd, files }) {
348 running += 1;
349 let c = child_process.spawn(cmd[0], cmd.slice(1), {
350 stdio: ["ignore", "pipe", "pipe"],
351 cwd,
352 });
353 const relatives = new Set(
354 files.map((file) => file.startsWith(cwd) ? path.relative(cwd, file) : file),
355 );
356 function onLine(line) {
357 for (const file of relatives) {
358 if (line.includes(file)) {
359 filesComplete += 1;
360 relatives.delete(file);
361 lastFile = path.relative(process.cwd(), path.resolve(cwd, file));
362 if (tty) {
363 writeStatus();
364 flush();
365 } else {
366 console.info(lastFile);
367 }
368 return;
369 }
370 }
371 }
372 let errBuffer = "";
373 let exited = false;
374 c.on("error", (e) => {
375 let message = "";
376 if (e?.code === "ENOENT") {
377 if (cmd[0].includes("/")) {
378 running -= 1;
379 run({ cmd: [path.basename(cmd[0]), ...cmd.slice(1)], cwd, files });
380 return;
381 }
382 message = `${cmd[0]} is not installed`;
383 } else {
384 message = String(e?.message ?? e);
385 }
386 clearStatus();
387 const filesConcise =
388 path.relative(".", path.resolve(cwd, relatives.keys().next().value)) + (
389 relatives.size > 1 ? ` and ${relatives.size - 1} more` : ""
390 );
391 buffer += errBuffer + `error: ${message}, cannot format ${filesConcise}.\n`;
392 flush();
393 exited = true;
394 runNext();
395 });
396 readline.createInterface(c.stderr).addListener("line", (line) => {
397 errBuffer += line + "\n";
398 onLine(line);
399 });
400 readline.createInterface(c.stdout).addListener("line", onLine);
401 c.on("exit", (code, signal) => {
402 if (exited) return;
403 exited = true;
404 if (code !== 0) {
405 clearStatus();
406 const exitStatus = code != null ? `code ${code}` : `signal ${signal}`;
407 buffer += errBuffer + `error: ${cmd[0]} exited with ${exitStatus}\n`;
408 flush();
409 } else {
410 filesComplete += relatives.size;
411 if (relatives.size) {
412 lastFile = path.relative(
413 ".",
414 path.resolve(cwd, relatives.keys().next().value),
415 );
416 }
417 writeStatus();
418 flush();
419 }
420 runNext();
421 });
422 function runNext() {
423 running -= 1;
424 const next = commands.pop();
425 if (next) run(next);
426 else if (running == 0) {
427 clearStatus();
428 flush();
429 console.info(
430 `Formatted ${filesComplete} file${filesComplete !== 1 ? "s" : ""}`,
431 );
432 }
433 }
434}
435
436for (let i = 0; i < navigator.hardwareConcurrency; i++) {
437 const cmd = commands.pop();
438 if (cmd) run(cmd);
439 else break;
440}
441
442// -- library functions --
443
444/** @param file {string} */
445function walk(file) {
446 file = path.resolve(file);
447 try {
448 if (fs.statSync(file).isDirectory()) {
449 const exclude = getGitIgnores(file);
450 const read = fs
451 .globSync(escapeGlob(file) + "/{**,.**}", {
452 exclude,
453 withFileTypes: true,
454 })
455 .filter((file) => !file.isDirectory())
456 .map((file) => path.join(file.parentPath, file.name));
457 files.push(...read);
458 } else {
459 files.push(file);
460 }
461 } catch (err) {
462 console.error(
463 `Failed to stat ${file}: ${err?.code ?? err?.message ?? err}`,
464 );
465 process.exit(1);
466 }
467}
468
469/** @param dir {string} @returns {Array<string>} */
470function readDir(dir) {
471 dir = path.resolve(dir);
472 let contents = dirs.get(dir);
473 if (!contents) {
474 try {
475 contents = fs.readdirSync(dir);
476 } catch {
477 contents = [];
478 }
479 dirs.set(dir, contents);
480 }
481 return contents;
482}
483
484/** @param dir {string} @returns {string} */
485function pickFormatter(file) {
486 const lang = extToLanguage[path.extname(file)];
487 const dir = path.dirname(file);
488 let c = walkUp(dir, (x) => cached.get(x) ?? cached.get(`${x}:${lang}`));
489 if (c) return c;
490
491 const possible = Object.keys(formatters).filter(
492 (x) =>
493 Array.isArray(formatters[x].languages) &&
494 formatters[x].languages.includes(lang),
495 );
496 const order = [...multis, ...possible];
497 return walkUp(dir, (x) => {
498 const children = readDir(x);
499 for (const fmt of order) {
500 let matches = false;
501 if (formatters[fmt].files === true) {
502 matches = true;
503 }
504 if (!matches) {
505 const filesToCheck = Array.isArray(formatters[fmt].files)
506 ? formatters[fmt].files.map((base) => ({ base, contents: true }))
507 : Object.entries(formatters[fmt].files)
508 .map(([base, contents]) => ({ base, contents }));
509 for (const { base, contents } of filesToCheck) {
510 if (base.includes("/")) {
511 matches = readDir(path.join(x, path.dirname(base))) //
512 .includes(path.basename(base));
513 } else {
514 matches = children.includes(base);
515 }
516 if (matches && typeof contents === "function") {
517 matches = !!contents(fs.readFileSync(path.join(x, base), "utf-8"));
518 }
519 }
520 }
521 if (matches) {
522 const formatterId = `${fmt}\0${x}`;
523 const k = multis.includes(fmt) ? x : `${x}:${lang}`;
524 cached.set(k, formatterId);
525 return formatterId;
526 }
527 }
528 }) ?? possible[0];
529}
530
531/** @param dir {string} @param find {(x: string) => any} */
532function walkUp(dir, find) {
533 do {
534 const found = find(dir);
535 if (found != null) return found;
536 const parent = path.dirname(dir);
537 if (parent === dir) break;
538 dir = parent;
539 } while (true);
540 return null;
541}
542
543/** @param dir {string} */
544function getGitIgnores(dir) {
545 if (!gitignore) return [];
546 if (dir.endsWith(sep)) dir = dir.slice(0, -1);
547 const files = fs.globSync(`${dir}${sep}{**${sep}*${sep},}.gitignore`, {});
548 const referenced = [];
549 for (const abs of files) {
550 const dir = path.dirname(abs);
551 referenced.push(dir);
552 if (!gitignores.has(dir)) gitignores.set(dir, readGitIgnore(dir));
553 }
554 do {
555 const parent = path.dirname(dir);
556 if (parent === dir || fs.existsSync(path.join(dir, ".git"))) break;
557 referenced.push(parent);
558 if (!gitignores.has(parent)) gitignores.set(parent, readGitIgnore(parent));
559 dir = parent;
560 } while (true);
561 return referenced.flatMap((root) =>
562 (gitignores.get(root) ?? [])
563 .filter((x) => x[0] !== "!")
564 .map(
565 (rule) => `${root}${sep}${rule[0] === "/" ? "" : `**${sep}`}${rule}`,
566 )
567 );
568}
569
570/** @param dir {string} */
571function readGitIgnore(dir) {
572 try {
573 return fs
574 .readFileSync(`${dir}${sep}.gitignore`, "utf-8")
575 .split("\n")
576 .map((line) => line.replace(/#.*$/, "").trim())
577 .filter(Boolean);
578 } catch {
579 return [];
580 }
581}
582
583function escapeGlob(str) {
584 return str.replace(/[\\*,{}]/g, "\\$&");
585}
586
587import * as child_process from "node:child_process";
588import * as fs from "node:fs";
589import * as path from "node:path";
590import * as readline from "node:readline";
591import * as events from "node:events";
592import process from "node:process";
593import assert from "node:assert";
flake.nix+27-44
......@@ -49,7 +49,7 @@
4949 }@inputs:
5050 let
5151 inherit (nixpkgs) lib;
52 # TODO: apply these overlays sooner and remove uses of legacyPackages elsewhere.
52
5353 overlays = [
5454 inputs.zig.overlays.default
5555 inputs.rust-overlay.overlays.default
......@@ -66,55 +66,42 @@
6666 };
6767 });
6868 })
69 ];
7069
71 # Users of this flake currently use x86_64 Linux and Apple Silicon
72 systems = [
73 "x86_64-linux"
74 "aarch64-darwin"
70 # custom packages
71 (_: pkgs: {
72 autofmt = pkgs.callPackage ./packages/autofmt.nix { };
73 })
7574 ];
75
76 # We only use x86_64 on Linux and Apple Silicon Macs
77 # Overlays are applied here, as early as possible.
78 getNixPkgs = system: import nixpkgs { inherit system overlays; };
79 systems = {
80 "x86_64-linux" = getNixPkgs "x86_64-linux";
81 "aarch64-darwin" = getNixPkgs "aarch64-darwin";
82 };
7683 forAllSystems =
7784 f:
78 builtins.listToAttrs (
79 builtins.map (system: {
80 name = system;
81 value = f (
82 inputs
83 // {
84 inherit system;
85 pkgs = nixpkgs.legacyPackages.${system};
86 }
87 );
88 }) systems
89 );
90
91 mkSystem = import ./lib/mkSystem.nix {
92 inherit
93 overlays
94 nixpkgs
95 inputs
96 mkNeovim
97 ;
98 };
99 mkNeovim = import ./lib/mkNeovim.nix {
100 inherit
101 self
102 overlays
103 nixpkgs
104 inputs
105 ;
106 };
85 builtins.mapAttrs # #
86 (system: pkgs: f (inputs // { inherit system pkgs; }))
87 systems;
88
89 # Library Functions
90 mkNeovim = import ./lib/mkNeovim.nix { inherit self inputs; };
91 mkSystem = import ./lib/mkSystem.nix { inherit inputs mkNeovim overlays; };
10792 in
10893 rec {
10994 inherit self;
11095 # "nix fmt"
111 formatter = forAllSystems (inputs: inputs.pkgs.nixfmt-tree);
96 formatter = forAllSystems (inputs: inputs.pkgs.autofmt);
11297 packages = forAllSystems (
113 { system, ... }:
98 { system, pkgs, ... }:
11499 {
115 nvim-chloe = mkNeovim "chloe" system;
116 nvim-natalie = mkNeovim "natalie" system;
117 nvim-julia = mkNeovim "julia" system;
100 nvim-chloe = mkNeovim "chloe" pkgs;
101 nvim-natalie = mkNeovim "natalie" pkgs;
102 nvim-julia = mkNeovim "julia" pkgs;
103
104 inherit (pkgs) autofmt;
118105 }
119106 // lib.optionalAttrs (system == "aarch64-darwin") {
120107 # "nix run .#darwin-rebuild"
......@@ -127,16 +114,12 @@
127114 user = "natalie";
128115 host = "desktop";
129116 system = "x86_64-linux";
130 extraModules = [
131 ];
132117 };
133118 # natalie's laptop
134119 darwinConfigurations."Natalies-MacBook-Air" = mkSystem "Natalies-MacBook-Air" {
135120 user = "natalie";
136121 host = "laptop";
137122 system = "aarch64-darwin";
138 extraModules = [
139 ];
140123 };
141124
142125 # chloe's mac studio "sandwich"
lib/mkNeovim.nix+4-10
......@@ -1,13 +1,7 @@
1{
2 self,
3 nixpkgs,
4 # TODO: apply overlays here
5 overlays,
6 inputs,
7}:
8user: system:
1{ self, inputs }:
2user: pkgs:
93let
10 darwin = nixpkgs.lib.strings.hasSuffix "-darwin" system;
4 darwin = pkgs.lib.strings.hasSuffix "-darwin" pkgs.system;
115
126 host = {
137 inherit darwin;
......@@ -18,7 +12,7 @@ let
1812 userConfig = import (userDir + "/user.nix");
1913in
2014(inputs.nvf.lib.neovimConfiguration {
21 pkgs = nixpkgs.legacyPackages.${system};
15 inherit pkgs;
2216 modules = builtins.filter (f: f != null) [
2317 (../users + ("/" + user + "/vim.nix"))
2418 ../modules/neovim
lib/mkSystem.nix+8-8
......@@ -1,7 +1,6 @@
11# This function creates a NixOS system based on our VM setup for a
22# particular architecture.
33{
4 nixpkgs,
54 overlays,
65 inputs,
76 mkNeovim,
......@@ -14,11 +13,11 @@ name:
1413 extraModules ? [ ],
1514}:
1615let
17 darwin = nixpkgs.lib.strings.hasSuffix "-darwin" system;
16 darwin = inputs.nixpkgs.lib.strings.hasSuffix "-darwin" system;
1817 getInputModule = a: b: inputs.${a}.${if darwin then "darwinModules" else "nixosModules"}.${b};
1918
2019 # NixOS vs nix-darwin functions
21 systemFunc = if darwin then inputs.darwin.lib.darwinSystem else nixpkgs.lib.nixosSystem;
20 systemFunc = if darwin then inputs.darwin.lib.darwinSystem else inputs.nixpkgs.lib.nixosSystem;
2221
2322 userDir = ../users + "/${user}";
2423 userConfig = import (userDir + "/user.nix");
......@@ -69,11 +68,12 @@ let
6968 mainHomeImports = builtins.filter (f: f != null) [
7069 (pathOrNull userHomePath)
7170 (pathOrNull hostHomePath)
72 {
73 home.packages = [
74 (mkNeovim user system)
75 ];
76 }
71 (
72 { pkgs, ... }:
73 {
74 home.packages = [ (mkNeovim user pkgs) ];
75 }
76 )
7777 inputs.nvf.homeManagerModules.default
7878 inputs.android-nixpkgs.hmModule
7979 ];
modules/neovim/default.nix+4-17
......@@ -5,7 +5,10 @@
55 ...
66}:
77{
8 imports = [ ./options.nix ];
8 imports = [
9 ./options.nix
10 ./formatter.nix
11 ];
912 # based on default options from upstream:
1013 # https://github.com/NotAShelf/nvf/blob/main/configuration.nix
1114 #
......@@ -107,20 +110,6 @@
107110 format.type = "nixfmt"; # looks so much nicer
108111 };
109112 };
110 formatter.conform-nvim = {
111 enable = true;
112 setupOpts = {
113 formatters_by_ft = {
114 typescript = [ "deno_fmt" ];
115 typescriptreact = [ "deno_fmt" ];
116 javascript = [ "deno_fmt" ];
117 javascriptreact = [ "deno_fmt" ];
118 };
119 formatters.deno_fmt = {
120 command = lib.meta.getExe pkgs.deno;
121 };
122 };
123 };
124113 filetree = {
125114 neo-tree = {
126115 enable = false;
......@@ -154,7 +143,6 @@
154143 enable = true;
155144 setupOpts = {
156145 bigfile.enable = true;
157 explorer.replace_netrw = true;
158146 dashboard = {
159147 preset.keys = [
160148 {
......@@ -209,7 +197,6 @@
209197 picker = {
210198 enable = true;
211199 sources = {
212 explorer = { };
213200 };
214201 };
215202 };
modules/neovim/formatter.nix created+34
......@@ -0,0 +1,34 @@
1{ lib, pkgs, ... }:
2{
3 vim.formatter.conform-nvim = {
4 enable = true;
5 setupOpts = {
6 formatters_by_ft =
7 let
8 autofmt = lib.mkLuaInline ''{"autofmt",stop_after_first=true}'';
9 in
10 {
11 css = autofmt;
12 html = autofmt;
13 javascript = autofmt;
14 javascriptreact = autofmt;
15 json = autofmt;
16 jsonc = autofmt;
17 markdown = autofmt;
18 nix = autofmt;
19 rust = autofmt;
20 typescript = autofmt;
21 typescriptreact = autofmt;
22 yaml = autofmt;
23 };
24 formatters.autofmt = {
25 "inherit" = false;
26 args = [
27 "--stdio"
28 "$FILENAME"
29 ];
30 command = "${pkgs.autofmt}/bin/autofmt";
31 };
32 };
33 };
34}
modules/nixos/nvidia.nix+12-9
......@@ -7,7 +7,10 @@ let
77 nvidiaDriverChannel = config.boot.kernelPackages.nvidiaPackages.latest;
88in
99{
10 services.xserver.videoDrivers = [ "nvidia" "amdgpu"];
10 services.xserver.videoDrivers = [
11 "nvidia"
12 "amdgpu"
13 ];
1114
1215 nixpkgs.config = {
1316 nvidia.acceptLicense = true;
......@@ -46,16 +49,16 @@ in
4649 finegrained = true; # More precise power consumption control
4750 };
4851
49 prime = {
50 offload = {
51 enable = true;
52 enableOffloadCmd = true;
53 };
52 prime = {
53 offload = {
54 enable = true;
55 enableOffloadCmd = true;
56 };
5457
55 nvidiaBusId = "PCI:1:0:0";
56 amdgpuBusId = "PCI:15:0:0";
58 nvidiaBusId = "PCI:1:0:0";
59 amdgpuBusId = "PCI:15:0:0";
5760
58 };
61 };
5962
6063 # Use the NVidia open source kernel module (not to be confused with the
6164 # independent third-party "nouveau" open source driver).
modules/shared/user-system-settings.nix+7-8
......@@ -10,13 +10,12 @@
1010 # Set your time zone.
1111 time.timeZone = user.timeZone;
1212
13 home-manager.users.${user.username}.home.sessionVariables =
14 {
15 EDITOR = user.editor;
16 TERMINAL = user.term;
17 }
18 // lib.optionalAttrs (user ? "browser") {
19 BROWSER = user.browser;
20 };
13 home-manager.users.${user.username}.home.sessionVariables = {
14 EDITOR = user.editor;
15 TERMINAL = user.term;
16 }
17 // lib.optionalAttrs (user ? "browser") {
18 BROWSER = user.browser;
19 };
2120
2221}
packages/autofmt.nix created+14
......@@ -0,0 +1,14 @@
1{ pkgs, ... }:
2pkgs.writeShellApplication {
3 name = "autofmt";
4 runtimeInputs = with pkgs; [
5 # include only a couple of formatters by default
6 deno
7 nixfmt-rfc-style
8 dprint
9 rustfmt
10 zig
11 clang-tools
12 ];
13 text = ''exec deno run -A ${../files/autofmt.js} "$@"'';
14}
readme.md-1
......@@ -78,4 +78,3 @@ configuration.
7878nix run .#nvim-natalie # run by user
7979./nvim # run based on your username
8080```
81
users/chloe/home.nix+1
......@@ -16,6 +16,7 @@ in
1616 ripgrep
1717 uv
1818 nh
19 pkgs.autofmt
1920 ];
2021 # packages to install for desktop environments (non-server)
2122 desktop = [
users/julia/cattop/configuration.nix+10-7
......@@ -2,13 +2,18 @@
22# your system. Help is available in the configuration.nix(5) man page, on
33# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
44
5{ config, lib, pkgs, ... }:
5{
6 config,
7 lib,
8 pkgs,
9 ...
10}:
611
712{
8 imports =
9 [ # Include the results of the hardware scan.
10 ./hardware-configuration.nix
11 ];
13 imports = [
14 # Include the results of the hardware scan.
15 ./hardware-configuration.nix
16 ];
1217
1318 # Use the systemd-boot EFI boot loader.
1419 boot.loader.efi.canTouchEfiVariables = true;
......@@ -39,7 +44,6 @@
3944 # Enable the GNOME Desktop Environment.
4045 services.xserver.displayManager.gdm.enable = true;
4146 services.xserver.desktopManager.gnome.enable = true;
42
4347
4448 # Configure keymap in X11
4549 # services.xserver.xkb.layout = "us";
......@@ -124,4 +128,3 @@
124128 # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
125129 system.stateVersion = "25.05"; # Did you read the comment?
126130}
127
users/natalie/glance.yml+2-2
......@@ -62,8 +62,8 @@ pages:
6262 location: London, United Kingdom
6363 units: metric # alternatively "imperial"
6464 hour-format: 12h # alternatively "24h"
65 # Optionally hide the location from being displayed in the widget
66 # hide-location: true
65 # Optionally hide the location from being displayed in the widget
66 # hide-location: true
6767
6868 - type: markets
6969 # The link to go to when clicking on the symbol in the UI,
users/natalie/vim.nix-1
......@@ -9,7 +9,6 @@
99 withPython3 = true;
1010 python3Packages = [ "pynvim" ];
1111
12
1312 autocmds = [
1413 #Autocommand to fall back to treesitter folding if LSP doesnt support it
1514 {
users/natalie/vim/keybinds.nix+6-6
......@@ -1,10 +1,10 @@
11{ ... }:
2 let
3 mkKeymap = mode: key: action: desc: {
4 inherit mode;
5 inherit key action desc;
6 };
7 n = mkKeymap "n"; # normal mode
2let
3 mkKeymap = mode: key: action: desc: {
4 inherit mode;
5 inherit key action desc;
6 };
7 n = mkKeymap "n"; # normal mode
88in
99{
1010 vim = {