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 @@...@@ -49,7 +49,7 @@
49 }@inputs:49 }@inputs:
50 let50 let
51 inherit (nixpkgs) lib;51 inherit (nixpkgs) lib;
52 # TODO: apply these overlays sooner and remove uses of legacyPackages elsewhere.52
53 overlays = [53 overlays = [
54 inputs.zig.overlays.default54 inputs.zig.overlays.default
55 inputs.rust-overlay.overlays.default55 inputs.rust-overlay.overlays.default
...@@ -66,55 +66,42 @@...@@ -66,55 +66,42 @@
66 };66 };
67 });67 });
68 })68 })
69 ];
7069
71 # Users of this flake currently use x86_64 Linux and Apple Silicon70 # custom packages
72 systems = [71 (_: pkgs: {
73 "x86_64-linux"72 autofmt = pkgs.callPackage ./packages/autofmt.nix { };
74 "aarch64-darwin"73 })
75 ];74 ];
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 };
76 forAllSystems =83 forAllSystems =
77 f:84 f:
78 builtins.listToAttrs (85 builtins.mapAttrs # #
79 builtins.map (system: {86 (system: pkgs: f (inputs // { inherit system pkgs; }))
80 name = system;87 systems;
81 value = f (88
82 inputs89 # Library Functions
83 // {90 mkNeovim = import ./lib/mkNeovim.nix { inherit self inputs; };
84 inherit system;91 mkSystem = import ./lib/mkSystem.nix { inherit inputs mkNeovim overlays; };
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 };
107 in92 in
108 rec {93 rec {
109 inherit self;94 inherit self;
110 # "nix fmt"95 # "nix fmt"
111 formatter = forAllSystems (inputs: inputs.pkgs.nixfmt-tree);96 formatter = forAllSystems (inputs: inputs.pkgs.autofmt);
112 packages = forAllSystems (97 packages = forAllSystems (
113 { system, ... }:98 { system, pkgs, ... }:
114 {99 {
115 nvim-chloe = mkNeovim "chloe" system;100 nvim-chloe = mkNeovim "chloe" pkgs;
116 nvim-natalie = mkNeovim "natalie" system;101 nvim-natalie = mkNeovim "natalie" pkgs;
117 nvim-julia = mkNeovim "julia" system;102 nvim-julia = mkNeovim "julia" pkgs;
103
104 inherit (pkgs) autofmt;
118 }105 }
119 // lib.optionalAttrs (system == "aarch64-darwin") {106 // lib.optionalAttrs (system == "aarch64-darwin") {
120 # "nix run .#darwin-rebuild"107 # "nix run .#darwin-rebuild"
...@@ -127,16 +114,12 @@...@@ -127,16 +114,12 @@
127 user = "natalie";114 user = "natalie";
128 host = "desktop";115 host = "desktop";
129 system = "x86_64-linux";116 system = "x86_64-linux";
130 extraModules = [
131 ];
132 };117 };
133 # natalie's laptop118 # natalie's laptop
134 darwinConfigurations."Natalies-MacBook-Air" = mkSystem "Natalies-MacBook-Air" {119 darwinConfigurations."Natalies-MacBook-Air" = mkSystem "Natalies-MacBook-Air" {
135 user = "natalie";120 user = "natalie";
136 host = "laptop";121 host = "laptop";
137 system = "aarch64-darwin";122 system = "aarch64-darwin";
138 extraModules = [
139 ];
140 };123 };
141124
142 # chloe's mac studio "sandwich"125 # chloe's mac studio "sandwich"
lib/mkNeovim.nix+4-10
...@@ -1,13 +1,7 @@...@@ -1,13 +1,7 @@
1{1{ self, inputs }:
2 self,2user: pkgs:
3 nixpkgs,
4 # TODO: apply overlays here
5 overlays,
6 inputs,
7}:
8user: system:
9let3let
10 darwin = nixpkgs.lib.strings.hasSuffix "-darwin" system;4 darwin = pkgs.lib.strings.hasSuffix "-darwin" pkgs.system;
115
12 host = {6 host = {
13 inherit darwin;7 inherit darwin;
...@@ -18,7 +12,7 @@ let...@@ -18,7 +12,7 @@ let
18 userConfig = import (userDir + "/user.nix");12 userConfig = import (userDir + "/user.nix");
19in13in
20(inputs.nvf.lib.neovimConfiguration {14(inputs.nvf.lib.neovimConfiguration {
21 pkgs = nixpkgs.legacyPackages.${system};15 inherit pkgs;
22 modules = builtins.filter (f: f != null) [16 modules = builtins.filter (f: f != null) [
23 (../users + ("/" + user + "/vim.nix"))17 (../users + ("/" + user + "/vim.nix"))
24 ../modules/neovim18 ../modules/neovim
lib/mkSystem.nix+8-8
...@@ -1,7 +1,6 @@...@@ -1,7 +1,6 @@
1# This function creates a NixOS system based on our VM setup for a1# This function creates a NixOS system based on our VM setup for a
2# particular architecture.2# particular architecture.
3{3{
4 nixpkgs,
5 overlays,4 overlays,
6 inputs,5 inputs,
7 mkNeovim,6 mkNeovim,
...@@ -14,11 +13,11 @@ name:...@@ -14,11 +13,11 @@ name:
14 extraModules ? [ ],13 extraModules ? [ ],
15}:14}:
16let15let
17 darwin = nixpkgs.lib.strings.hasSuffix "-darwin" system;16 darwin = inputs.nixpkgs.lib.strings.hasSuffix "-darwin" system;
18 getInputModule = a: b: inputs.${a}.${if darwin then "darwinModules" else "nixosModules"}.${b};17 getInputModule = a: b: inputs.${a}.${if darwin then "darwinModules" else "nixosModules"}.${b};
1918
20 # NixOS vs nix-darwin functions19 # 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
23 userDir = ../users + "/${user}";22 userDir = ../users + "/${user}";
24 userConfig = import (userDir + "/user.nix");23 userConfig = import (userDir + "/user.nix");
...@@ -69,11 +68,12 @@ let...@@ -69,11 +68,12 @@ let
69 mainHomeImports = builtins.filter (f: f != null) [68 mainHomeImports = builtins.filter (f: f != null) [
70 (pathOrNull userHomePath)69 (pathOrNull userHomePath)
71 (pathOrNull hostHomePath)70 (pathOrNull hostHomePath)
72 {71 (
73 home.packages = [72 { pkgs, ... }:
74 (mkNeovim user system)73 {
75 ];74 home.packages = [ (mkNeovim user pkgs) ];
76 }75 }
76 )
77 inputs.nvf.homeManagerModules.default77 inputs.nvf.homeManagerModules.default
78 inputs.android-nixpkgs.hmModule78 inputs.android-nixpkgs.hmModule
79 ];79 ];
modules/neovim/default.nix+4-17
...@@ -5,7 +5,10 @@...@@ -5,7 +5,10 @@
5 ...5 ...
6}:6}:
7{7{
8 imports = [ ./options.nix ];8 imports = [
9 ./options.nix
10 ./formatter.nix
11 ];
9 # based on default options from upstream:12 # based on default options from upstream:
10 # https://github.com/NotAShelf/nvf/blob/main/configuration.nix13 # https://github.com/NotAShelf/nvf/blob/main/configuration.nix
11 #14 #
...@@ -107,20 +110,6 @@...@@ -107,20 +110,6 @@
107 format.type = "nixfmt"; # looks so much nicer110 format.type = "nixfmt"; # looks so much nicer
108 };111 };
109 };112 };
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 };
124 filetree = {113 filetree = {
125 neo-tree = {114 neo-tree = {
126 enable = false;115 enable = false;
...@@ -154,7 +143,6 @@...@@ -154,7 +143,6 @@
154 enable = true;143 enable = true;
155 setupOpts = {144 setupOpts = {
156 bigfile.enable = true;145 bigfile.enable = true;
157 explorer.replace_netrw = true;
158 dashboard = {146 dashboard = {
159 preset.keys = [147 preset.keys = [
160 {148 {
...@@ -209,7 +197,6 @@...@@ -209,7 +197,6 @@
209 picker = {197 picker = {
210 enable = true;198 enable = true;
211 sources = {199 sources = {
212 explorer = { };
213 };200 };
214 };201 };
215 };202 };
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...@@ -7,7 +7,10 @@ let
7 nvidiaDriverChannel = config.boot.kernelPackages.nvidiaPackages.latest;7 nvidiaDriverChannel = config.boot.kernelPackages.nvidiaPackages.latest;
8in8in
9{9{
10 services.xserver.videoDrivers = [ "nvidia" "amdgpu"];10 services.xserver.videoDrivers = [
11 "nvidia"
12 "amdgpu"
13 ];
1114
12 nixpkgs.config = {15 nixpkgs.config = {
13 nvidia.acceptLicense = true;16 nvidia.acceptLicense = true;
...@@ -46,16 +49,16 @@ in...@@ -46,16 +49,16 @@ in
46 finegrained = true; # More precise power consumption control49 finegrained = true; # More precise power consumption control
47 };50 };
4851
49 prime = {52 prime = {
50 offload = {53 offload = {
51 enable = true;54 enable = true;
52 enableOffloadCmd = true;55 enableOffloadCmd = true;
53 };56 };
5457
55 nvidiaBusId = "PCI:1:0:0";58 nvidiaBusId = "PCI:1:0:0";
56 amdgpuBusId = "PCI:15:0:0";59 amdgpuBusId = "PCI:15:0:0";
5760
58 };61 };
5962
60 # Use the NVidia open source kernel module (not to be confused with the63 # Use the NVidia open source kernel module (not to be confused with the
61 # independent third-party "nouveau" open source driver).64 # independent third-party "nouveau" open source driver).
modules/shared/user-system-settings.nix+7-8
...@@ -10,13 +10,12 @@...@@ -10,13 +10,12 @@
10 # Set your time zone.10 # Set your time zone.
11 time.timeZone = user.timeZone;11 time.timeZone = user.timeZone;
1212
13 home-manager.users.${user.username}.home.sessionVariables =13 home-manager.users.${user.username}.home.sessionVariables = {
14 {14 EDITOR = user.editor;
15 EDITOR = user.editor;15 TERMINAL = user.term;
16 TERMINAL = user.term;16 }
17 }17 // lib.optionalAttrs (user ? "browser") {
18 // lib.optionalAttrs (user ? "browser") {18 BROWSER = user.browser;
19 BROWSER = user.browser;19 };
20 };
2120
22}21}
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....@@ -78,4 +78,3 @@ configuration.
78nix run .#nvim-natalie # run by user78nix run .#nvim-natalie # run by user
79./nvim # run based on your username79./nvim # run based on your username
80```80```
81
users/chloe/home.nix+1
...@@ -16,6 +16,7 @@ in...@@ -16,6 +16,7 @@ in
16 ripgrep16 ripgrep
17 uv17 uv
18 nh18 nh
19 pkgs.autofmt
19 ];20 ];
20 # packages to install for desktop environments (non-server)21 # packages to install for desktop environments (non-server)
21 desktop = [22 desktop = [
users/julia/cattop/configuration.nix+10-7
...@@ -2,13 +2,18 @@...@@ -2,13 +2,18 @@
2# your system. Help is available in the configuration.nix(5) man page, on2# your system. Help is available in the configuration.nix(5) man page, on
3# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).3# 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
7{12{
8 imports =13 imports = [
9 [ # Include the results of the hardware scan.14 # Include the results of the hardware scan.
10 ./hardware-configuration.nix15 ./hardware-configuration.nix
11 ];16 ];
1217
13 # Use the systemd-boot EFI boot loader.18 # Use the systemd-boot EFI boot loader.
14 boot.loader.efi.canTouchEfiVariables = true;19 boot.loader.efi.canTouchEfiVariables = true;
...@@ -39,7 +44,6 @@...@@ -39,7 +44,6 @@
39 # Enable the GNOME Desktop Environment.44 # Enable the GNOME Desktop Environment.
40 services.xserver.displayManager.gdm.enable = true;45 services.xserver.displayManager.gdm.enable = true;
41 services.xserver.desktopManager.gnome.enable = true;46 services.xserver.desktopManager.gnome.enable = true;
42
4347
44 # Configure keymap in X1148 # Configure keymap in X11
45 # services.xserver.xkb.layout = "us";49 # services.xserver.xkb.layout = "us";
...@@ -124,4 +128,3 @@...@@ -124,4 +128,3 @@
124 # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .128 # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
125 system.stateVersion = "25.05"; # Did you read the comment?129 system.stateVersion = "25.05"; # Did you read the comment?
126}130}
127
users/natalie/glance.yml+2-2
...@@ -62,8 +62,8 @@ pages:...@@ -62,8 +62,8 @@ pages:
62 location: London, United Kingdom62 location: London, United Kingdom
63 units: metric # alternatively "imperial"63 units: metric # alternatively "imperial"
64 hour-format: 12h # alternatively "24h"64 hour-format: 12h # alternatively "24h"
65 # Optionally hide the location from being displayed in the widget65 # Optionally hide the location from being displayed in the widget
66 # hide-location: true66 # hide-location: true
6767
68 - type: markets68 - type: markets
69 # The link to go to when clicking on the symbol in the UI,69 # The link to go to when clicking on the symbol in the UI,
users/natalie/vim.nix-1
...@@ -9,7 +9,6 @@...@@ -9,7 +9,6 @@
9 withPython3 = true;9 withPython3 = true;
10 python3Packages = [ "pynvim" ];10 python3Packages = [ "pynvim" ];
1111
12
13 autocmds = [12 autocmds = [
14 #Autocommand to fall back to treesitter folding if LSP doesnt support it13 #Autocommand to fall back to treesitter folding if LSP doesnt support it
15 {14 {
users/natalie/vim/keybinds.nix+6-6
...@@ -1,10 +1,10 @@...@@ -1,10 +1,10 @@
1{ ... }:1{ ... }:
2 let2let
3 mkKeymap = mode: key: action: desc: {3 mkKeymap = mode: key: action: desc: {
4 inherit mode;4 inherit mode;
5 inherit key action desc;5 inherit key action desc;
6 };6 };
7 n = mkKeymap "n"; # normal mode7 n = mkKeymap "n"; # normal mode
8in8in
9{9{
10 vim = {10 vim = {