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
This commit is contained in:
clover caruso 2025-08-17 20:40:58 -07:00
parent 2e9eda64d4
commit 4b91b37f4a
15 changed files with 723 additions and 114 deletions

593
files/autofmt.js Executable file
View file

@ -0,0 +1,593 @@
#!/usr/bin/env node
// autofmt v1 - https://paperclover.dev/nix/config/branch/main/files/autofmt.js
//
// Different codebases use different formatters. Autofmt looks for project
// configuration to pick the correct formatter, allowing an editor to simply
// point to this script and ambiguities resolved. This single file program
// depends only on Node.js v22.
//
// When using this repository's Nix-based Neovim configuration, autofmt is
// automatically configured as the default formatter. To configure manually,
// set the editor's formatter to run `autofmt --stdio=<filename>`. The filename
// is used to determine which formatter to invoke.
//
// With `conform.nvim`:
// formatters = {
// autofmt = { command = "autofmt" }
// }
//
// With Zed:
// "formatter": {
// "external": {
// "command": "autofmt",
// "arguments": ["--stdio", "{buffer_path}"]
// }
// },
//
// @ts-nocheck
// -- definitions --
const extensions = {
c: [".c", ".h"],
cpp: [".cpp", ".cc", ".cxx", ".hpp", ".hxx", ".hh"],
css: [".css"],
html: [".html"],
javascript: [".js", ".ts", ".cjs", ".cts", ".mjs", ".mts", ".jsx", ".tsx"],
json: [".json", ".jsonc"],
markdown: [".md", ".markdown"],
mdx: [".mdx"],
nix: [".nix"],
rust: [".rs"],
toml: [".toml"],
yaml: [".yml", ".yaml"],
zig: [".zig"],
};
const formatters = {
// this object is sorted by priority
//
// `languages: true` are multiplexers that apply to all listed
// languages, as it is assumed a project will setup their multiplexer
// correctly for all tracked files.
//
// if a formatter doesnt match a config file, the first one is picked
// as a default, making things like `deno fmt file.ts` or `clang-format`
// the default when there are no config files.
dprint: {
languages: true,
files: ["dprint.json", "dprint.jsonc"],
cmd: ["dprint", "fmt", "--", "$files"],
stdin: (file) => ["dprint", "fmt", "--stdin", file],
},
treefmt: {
languages: true,
files: ["treefmt.toml", ".treefmt.toml"],
cmd: ["treefmt", "--", "$files"],
stdin: (file) => ["treefmt", "--stdin", file],
},
// -- web ecosystem --
deno: {
languages: ["javascript", "markdown", "json", "yaml", "html", "css"],
files: ["dprint.json", "dprint.jsonc"],
cmd: ["deno", "fmt", "--", "$files"],
stdin: (file) => ["deno", "fmt", "--ext", path.extname(file).slice(1), "-"],
},
prettier: {
languages: ["javascript", "markdown", "json", "yaml", "mdx", "html", "css"],
files: ["node_modules/.bin/prettier"],
cmd: ["node_modules/.bin/prettier", "--write", "--", "$files"],
stdin: (file) => ["node_modules/.bin/prettier", "--stdin-filepath", file],
},
biome: {
languages: ["javascript", "json", "css"],
files: ["node_modules/.bin/biome"],
cmd: ["node_modules/.bin/biome", "format", "--", "$files"],
stdin: (file) => [
"node_modules/bin/biome",
"format",
`--stdin-file-path=${file}`,
],
},
"nixfmt-rfc-style": {
languages: ["nix"],
files: {
"flake.nix": (contents) => contents.includes("nixfmt"),
},
cmd: ["nixfmt", "--", "$files"],
stdin: (file) => ["nixfmt", `--filename=${file}`],
},
alejandra: {
languages: ["nix"],
files: {
"flake.nix": (contents) => contents.includes("alejandra"),
},
cmd: ["alejandra", "--", "$files"],
stdin: () => ["alejandra"],
},
clang: {
languages: ["c", "cpp"],
files: true,
cmd: ["clang-format", "--", "$files"],
stdin: (file) => ["clang-format", `--assume-filename=${file}`],
},
zig: {
languages: ["zig"],
files: true,
cmd: ["zig", "fmt", "$files"],
stdin: (file) => [
"zig",
"fmt",
"--stdin",
...file.endsWith(".zon") ? ["--zon"] : [],
],
},
rustfmt: {
languages: ["rust"],
files: true,
cmd: ["rustfmt", "--", "$files"],
stdin: () => ["rustfmt"],
},
taplo: {
languages: ["toml"],
files: ["taplo.toml"],
cmd: ["taplo", "format", "--", "$files"],
cmd: () => ["taplo", "format", "-"],
},
};
// -- cli --
if (!fs.globSync) {
console.error(`error: autofmt must be run with Node.js v22 or newer`);
process.exit(1);
}
const [, bin, ...argv] = process.argv;
let inputs = [];
let globalExclude = [];
let gitignore = true;
let dryRun = false;
let stdio = null;
let excludes = [".git"];
while (argv.length > 0) {
const arg = argv.shift();
if (arg === "-h" || arg === "--help") usage();
else if (arg === "--no-gitignore") gitignore = false;
else if (arg === "--dry-run") dryRun = true;
else if (arg.match(/^--stdio=./)) {
if (stdio) {
console.error("error: can only pass --stdio once");
usage();
}
stdio = arg.slice("--stdio=".length);
} else if (arg === "--stdio") {
const value = argv.shift();
if (!value) {
console.error("error: missing value for --stdio");
usage();
}
if (stdio) {
console.error("error: can only pass --stdio once");
usage();
}
stdio = value;
} else if (arg.match(/^--exclude=./)) {
excludes.push(arg.slice("--exclude=".length));
} else if (arg === "--") {
inputs.push(...argv);
break;
} else if (arg.startsWith("-")) {
console.error("error: unknown option " + JSON.stringify(arg));
usage();
} else inputs.push(arg);
}
function usage() {
const exe = path.basename(bin);
console.error(`usage: ${exe} [...files or directories]`);
console.error(``);
console.error(`uses the right formatter for the job (by scanning config)`);
console.error(`when autofmt reads dirs, it will respect gitignore`);
console.error(``);
console.error(`to format current directory recursively, run '${exe} .'`);
console.error(``);
console.error(`options:`);
console.error(` --dry-run print commands instead of running them`);
console.error(` --no-gitignore do not read '.gitignore'`);
console.error(` --exclude=<glob> add an exclusion glob`);
console.error(` --stdio=<filename> read/write contents via stdin/stdout`);
console.error(``);
process.exit(1);
}
if (inputs.length === 0 && !stdio) usage();
if (stdio && inputs.length > 0) {
console.error("error: stdio mode only operates on one file");
process.exit(1);
}
const { sep } = path;
// -- disable warnings --
const { emit: originalEmit } = process;
const warnings = ["ExperimentalWarning"];
process.emit = function (event, error) {
return event === "warning" && warnings.includes(error.name)
? false
: originalEmit.apply(process, arguments);
};
// -- vars --
const extToLanguage = Object.fromEntries(
Object.entries(extensions).flatMap(([lang, exts]) =>
exts.map((ext) => [ext, lang])
),
);
const multis = Object.keys(formatters).filter(
(x) => formatters[x].languages === true,
);
const files = [];
const gitignores = new Map();
const dirs = new Map();
const cached = new Map();
// -- stdin mode --
if (stdio) {
const fmtWithPath = pickFormatter(stdio);
if (!fmtWithPath) {
console.error(`No formatter configured for ${path.relative(".", stdio)}`);
process.exit(1);
}
let [fmt, cwd] = fmtWithPath.split("\0");
let cmd = formatters[fmt].stdin(stdio);
cwd ??= process.cwd();
if (dryRun) {
console.info(cmd.join(" "));
process.exit(0);
}
const proc = child_process.spawn(cmd[0], cmd.slice(1), {
stdio: ["inherit", "inherit", "inherit"],
});
proc.on("error", (e) => {
let message = "";
if (e?.code === "ENOENT") {
message = `${cmd[0]} is not installed`;
} else {
message = String(e?.message ?? e);
}
console.error(`error: ${message}`);
process.exit(1);
});
const [code] = await events.once(proc, "exit");
process.exit(code ?? 1);
}
// -- decide what formatters to run
inputs = inputs.map((x) => path.resolve(x));
inputs.forEach(walk);
const toRun = new Map();
for (const file of new Set(files)) {
const fmt = pickFormatter(file);
if (!fmt) {
if (inputs.includes(file)) {
console.warn(`No formatter configured for ${path.relative(".", file)}`);
}
continue;
}
let list = toRun.get(fmt);
list ?? toRun.set(fmt, list = []);
list.push(file);
}
// -- create a list of commands --
const commands = [];
let totalFiles = 0;
for (const [fmtWithPath, files] of toRun) {
let [fmt, cwd] = fmtWithPath.split("\0");
let { cmd } = formatters[fmt];
if (cwd) cmd = [path.join(cwd, cmd[0]), ...cmd.slice(1)];
cwd ??= process.cwd();
let i = 0;
totalFiles += files.length;
if ((i = cmd.indexOf("$files")) != -1) {
const c = cmd.slice();
c.splice(i, 1, ...files);
commands.push({ cmd: c, cwd, files });
} else if ((i = cmd.indexOf("$file")) != -1) {
for (const file of files) {
const c = cmd.slice();
c.splice(i, 1, file);
commands.push({ cmd: c, cwd, files: [file] });
}
} else {
throw new Error(`Formatter ${fmt} has incorrectly configured command.`);
}
}
if (commands.length === 0) {
console.error("No formattable files");
process.exit(0);
}
// -- dry run mode --
if (dryRun) {
for (const { cmd } of commands) {
console.info(cmd);
}
process.exit(0);
}
// -- user interface --
let filesComplete = 0;
let lastFile = commands[0].cmd;
const syncStart = "\u001B[?2026h";
const syncEnd = "\u001B[?2026l";
let buffer = "";
let statusVisible = false;
const tty = process.stderr.isTTY;
function writeStatus() {
if (!tty) return;
clearStatus();
buffer ||= syncStart;
buffer += `${filesComplete}/${totalFiles} - ${lastFile}`;
statusVisible = true;
}
function clearStatus() {
if (!tty) return;
if (!statusVisible) return;
buffer ||= syncStart;
buffer += "\r\x1b[2K\r";
statusVisible = false;
}
function flush() {
if (!buffer) return;
const width = Math.max(1, process.stderr.columns - 1);
process.stderr.write(
buffer.split("\n").map((x) => x.slice(0, width)).join("\n") + syncEnd,
);
buffer = "";
}
// -- async process queue --
let running = 0;
/** @param cmd {{ cmd: string, cwd: string, files: string[] }} */
function run({ cmd, cwd, files }) {
running += 1;
let c = child_process.spawn(cmd[0], cmd.slice(1), {
stdio: ["ignore", "pipe", "pipe"],
cwd,
});
const relatives = new Set(
files.map((file) => file.startsWith(cwd) ? path.relative(cwd, file) : file),
);
function onLine(line) {
for (const file of relatives) {
if (line.includes(file)) {
filesComplete += 1;
relatives.delete(file);
lastFile = path.relative(process.cwd(), path.resolve(cwd, file));
if (tty) {
writeStatus();
flush();
} else {
console.info(lastFile);
}
return;
}
}
}
let errBuffer = "";
let exited = false;
c.on("error", (e) => {
let message = "";
if (e?.code === "ENOENT") {
if (cmd[0].includes("/")) {
running -= 1;
run({ cmd: [path.basename(cmd[0]), ...cmd.slice(1)], cwd, files });
return;
}
message = `${cmd[0]} is not installed`;
} else {
message = String(e?.message ?? e);
}
clearStatus();
const filesConcise =
path.relative(".", path.resolve(cwd, relatives.keys().next().value)) + (
relatives.size > 1 ? ` and ${relatives.size - 1} more` : ""
);
buffer += errBuffer + `error: ${message}, cannot format ${filesConcise}.\n`;
flush();
exited = true;
runNext();
});
readline.createInterface(c.stderr).addListener("line", (line) => {
errBuffer += line + "\n";
onLine(line);
});
readline.createInterface(c.stdout).addListener("line", onLine);
c.on("exit", (code, signal) => {
if (exited) return;
exited = true;
if (code !== 0) {
clearStatus();
const exitStatus = code != null ? `code ${code}` : `signal ${signal}`;
buffer += errBuffer + `error: ${cmd[0]} exited with ${exitStatus}\n`;
flush();
} else {
filesComplete += relatives.size;
if (relatives.size) {
lastFile = path.relative(
".",
path.resolve(cwd, relatives.keys().next().value),
);
}
writeStatus();
flush();
}
runNext();
});
function runNext() {
running -= 1;
const next = commands.pop();
if (next) run(next);
else if (running == 0) {
clearStatus();
flush();
console.info(
`Formatted ${filesComplete} file${filesComplete !== 1 ? "s" : ""}`,
);
}
}
}
for (let i = 0; i < navigator.hardwareConcurrency; i++) {
const cmd = commands.pop();
if (cmd) run(cmd);
else break;
}
// -- library functions --
/** @param file {string} */
function walk(file) {
file = path.resolve(file);
try {
if (fs.statSync(file).isDirectory()) {
const exclude = getGitIgnores(file);
const read = fs
.globSync(escapeGlob(file) + "/{**,.**}", {
exclude,
withFileTypes: true,
})
.filter((file) => !file.isDirectory())
.map((file) => path.join(file.parentPath, file.name));
files.push(...read);
} else {
files.push(file);
}
} catch (err) {
console.error(
`Failed to stat ${file}: ${err?.code ?? err?.message ?? err}`,
);
process.exit(1);
}
}
/** @param dir {string} @returns {Array<string>} */
function readDir(dir) {
dir = path.resolve(dir);
let contents = dirs.get(dir);
if (!contents) {
try {
contents = fs.readdirSync(dir);
} catch {
contents = [];
}
dirs.set(dir, contents);
}
return contents;
}
/** @param dir {string} @returns {string} */
function pickFormatter(file) {
const lang = extToLanguage[path.extname(file)];
const dir = path.dirname(file);
let c = walkUp(dir, (x) => cached.get(x) ?? cached.get(`${x}:${lang}`));
if (c) return c;
const possible = Object.keys(formatters).filter(
(x) =>
Array.isArray(formatters[x].languages) &&
formatters[x].languages.includes(lang),
);
const order = [...multis, ...possible];
return walkUp(dir, (x) => {
const children = readDir(x);
for (const fmt of order) {
let matches = false;
if (formatters[fmt].files === true) {
matches = true;
}
if (!matches) {
const filesToCheck = Array.isArray(formatters[fmt].files)
? formatters[fmt].files.map((base) => ({ base, contents: true }))
: Object.entries(formatters[fmt].files)
.map(([base, contents]) => ({ base, contents }));
for (const { base, contents } of filesToCheck) {
if (base.includes("/")) {
matches = readDir(path.join(x, path.dirname(base))) //
.includes(path.basename(base));
} else {
matches = children.includes(base);
}
if (matches && typeof contents === "function") {
matches = !!contents(fs.readFileSync(path.join(x, base), "utf-8"));
}
}
}
if (matches) {
const formatterId = `${fmt}\0${x}`;
const k = multis.includes(fmt) ? x : `${x}:${lang}`;
cached.set(k, formatterId);
return formatterId;
}
}
}) ?? possible[0];
}
/** @param dir {string} @param find {(x: string) => any} */
function walkUp(dir, find) {
do {
const found = find(dir);
if (found != null) return found;
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
} while (true);
return null;
}
/** @param dir {string} */
function getGitIgnores(dir) {
if (!gitignore) return [];
if (dir.endsWith(sep)) dir = dir.slice(0, -1);
const files = fs.globSync(`${dir}${sep}{**${sep}*${sep},}.gitignore`, {});
const referenced = [];
for (const abs of files) {
const dir = path.dirname(abs);
referenced.push(dir);
if (!gitignores.has(dir)) gitignores.set(dir, readGitIgnore(dir));
}
do {
const parent = path.dirname(dir);
if (parent === dir || fs.existsSync(path.join(dir, ".git"))) break;
referenced.push(parent);
if (!gitignores.has(parent)) gitignores.set(parent, readGitIgnore(parent));
dir = parent;
} while (true);
return referenced.flatMap((root) =>
(gitignores.get(root) ?? [])
.filter((x) => x[0] !== "!")
.map(
(rule) => `${root}${sep}${rule[0] === "/" ? "" : `**${sep}`}${rule}`,
)
);
}
/** @param dir {string} */
function readGitIgnore(dir) {
try {
return fs
.readFileSync(`${dir}${sep}.gitignore`, "utf-8")
.split("\n")
.map((line) => line.replace(/#.*$/, "").trim())
.filter(Boolean);
} catch {
return [];
}
}
function escapeGlob(str) {
return str.replace(/[\\*,{}]/g, "\\$&");
}
import * as child_process from "node:child_process";
import * as fs from "node:fs";
import * as path from "node:path";
import * as readline from "node:readline";
import * as events from "node:events";
import process from "node:process";
import assert from "node:assert";

View file

@ -49,7 +49,7 @@
}@inputs: }@inputs:
let let
inherit (nixpkgs) lib; inherit (nixpkgs) lib;
# TODO: apply these overlays sooner and remove uses of legacyPackages elsewhere.
overlays = [ overlays = [
inputs.zig.overlays.default inputs.zig.overlays.default
inputs.rust-overlay.overlays.default inputs.rust-overlay.overlays.default
@ -66,55 +66,42 @@
}; };
}); });
}) })
# custom packages
(_: pkgs: {
autofmt = pkgs.callPackage ./packages/autofmt.nix { };
})
]; ];
# Users of this flake currently use x86_64 Linux and Apple Silicon # We only use x86_64 on Linux and Apple Silicon Macs
systems = [ # Overlays are applied here, as early as possible.
"x86_64-linux" getNixPkgs = system: import nixpkgs { inherit system overlays; };
"aarch64-darwin" systems = {
]; "x86_64-linux" = getNixPkgs "x86_64-linux";
"aarch64-darwin" = getNixPkgs "aarch64-darwin";
};
forAllSystems = forAllSystems =
f: f:
builtins.listToAttrs ( builtins.mapAttrs # #
builtins.map (system: { (system: pkgs: f (inputs // { inherit system pkgs; }))
name = system; systems;
value = f (
inputs
// {
inherit system;
pkgs = nixpkgs.legacyPackages.${system};
}
);
}) systems
);
mkSystem = import ./lib/mkSystem.nix { # Library Functions
inherit mkNeovim = import ./lib/mkNeovim.nix { inherit self inputs; };
overlays mkSystem = import ./lib/mkSystem.nix { inherit inputs mkNeovim overlays; };
nixpkgs
inputs
mkNeovim
;
};
mkNeovim = import ./lib/mkNeovim.nix {
inherit
self
overlays
nixpkgs
inputs
;
};
in in
rec { rec {
inherit self; inherit self;
# "nix fmt" # "nix fmt"
formatter = forAllSystems (inputs: inputs.pkgs.nixfmt-tree); formatter = forAllSystems (inputs: inputs.pkgs.autofmt);
packages = forAllSystems ( packages = forAllSystems (
{ system, ... }: { system, pkgs, ... }:
{ {
nvim-chloe = mkNeovim "chloe" system; nvim-chloe = mkNeovim "chloe" pkgs;
nvim-natalie = mkNeovim "natalie" system; nvim-natalie = mkNeovim "natalie" pkgs;
nvim-julia = mkNeovim "julia" system; nvim-julia = mkNeovim "julia" pkgs;
inherit (pkgs) autofmt;
} }
// lib.optionalAttrs (system == "aarch64-darwin") { // lib.optionalAttrs (system == "aarch64-darwin") {
# "nix run .#darwin-rebuild" # "nix run .#darwin-rebuild"
@ -127,16 +114,12 @@
user = "natalie"; user = "natalie";
host = "desktop"; host = "desktop";
system = "x86_64-linux"; system = "x86_64-linux";
extraModules = [
];
}; };
# natalie's laptop # natalie's laptop
darwinConfigurations."Natalies-MacBook-Air" = mkSystem "Natalies-MacBook-Air" { darwinConfigurations."Natalies-MacBook-Air" = mkSystem "Natalies-MacBook-Air" {
user = "natalie"; user = "natalie";
host = "laptop"; host = "laptop";
system = "aarch64-darwin"; system = "aarch64-darwin";
extraModules = [
];
}; };
# chloe's mac studio "sandwich" # chloe's mac studio "sandwich"

View file

@ -1,13 +1,7 @@
{ { self, inputs }:
self, user: pkgs:
nixpkgs,
# TODO: apply overlays here
overlays,
inputs,
}:
user: system:
let let
darwin = nixpkgs.lib.strings.hasSuffix "-darwin" system; darwin = pkgs.lib.strings.hasSuffix "-darwin" pkgs.system;
host = { host = {
inherit darwin; inherit darwin;
@ -18,7 +12,7 @@ let
userConfig = import (userDir + "/user.nix"); userConfig = import (userDir + "/user.nix");
in in
(inputs.nvf.lib.neovimConfiguration { (inputs.nvf.lib.neovimConfiguration {
pkgs = nixpkgs.legacyPackages.${system}; inherit pkgs;
modules = builtins.filter (f: f != null) [ modules = builtins.filter (f: f != null) [
(../users + ("/" + user + "/vim.nix")) (../users + ("/" + user + "/vim.nix"))
../modules/neovim ../modules/neovim

View file

@ -1,7 +1,6 @@
# This function creates a NixOS system based on our VM setup for a # This function creates a NixOS system based on our VM setup for a
# particular architecture. # particular architecture.
{ {
nixpkgs,
overlays, overlays,
inputs, inputs,
mkNeovim, mkNeovim,
@ -14,11 +13,11 @@ name:
extraModules ? [ ], extraModules ? [ ],
}: }:
let let
darwin = nixpkgs.lib.strings.hasSuffix "-darwin" system; darwin = inputs.nixpkgs.lib.strings.hasSuffix "-darwin" system;
getInputModule = a: b: inputs.${a}.${if darwin then "darwinModules" else "nixosModules"}.${b}; getInputModule = a: b: inputs.${a}.${if darwin then "darwinModules" else "nixosModules"}.${b};
# NixOS vs nix-darwin functions # NixOS vs nix-darwin functions
systemFunc = if darwin then inputs.darwin.lib.darwinSystem else nixpkgs.lib.nixosSystem; systemFunc = if darwin then inputs.darwin.lib.darwinSystem else inputs.nixpkgs.lib.nixosSystem;
userDir = ../users + "/${user}"; userDir = ../users + "/${user}";
userConfig = import (userDir + "/user.nix"); userConfig = import (userDir + "/user.nix");
@ -69,11 +68,12 @@ let
mainHomeImports = builtins.filter (f: f != null) [ mainHomeImports = builtins.filter (f: f != null) [
(pathOrNull userHomePath) (pathOrNull userHomePath)
(pathOrNull hostHomePath) (pathOrNull hostHomePath)
{ (
home.packages = [ { pkgs, ... }:
(mkNeovim user system) {
]; home.packages = [ (mkNeovim user pkgs) ];
} }
)
inputs.nvf.homeManagerModules.default inputs.nvf.homeManagerModules.default
inputs.android-nixpkgs.hmModule inputs.android-nixpkgs.hmModule
]; ];

View file

@ -5,7 +5,10 @@
... ...
}: }:
{ {
imports = [ ./options.nix ]; imports = [
./options.nix
./formatter.nix
];
# based on default options from upstream: # based on default options from upstream:
# https://github.com/NotAShelf/nvf/blob/main/configuration.nix # https://github.com/NotAShelf/nvf/blob/main/configuration.nix
# #
@ -107,23 +110,9 @@
format.type = "nixfmt"; # looks so much nicer format.type = "nixfmt"; # looks so much nicer
}; };
}; };
formatter.conform-nvim = {
enable = true;
setupOpts = {
formatters_by_ft = {
typescript = [ "deno_fmt" ];
typescriptreact = [ "deno_fmt" ];
javascript = [ "deno_fmt" ];
javascriptreact = [ "deno_fmt" ];
};
formatters.deno_fmt = {
command = lib.meta.getExe pkgs.deno;
};
};
};
filetree = { filetree = {
neo-tree = { neo-tree = {
enable = false; enable = true;
}; };
}; };
tabline = { tabline = {
@ -154,7 +143,6 @@
enable = true; enable = true;
setupOpts = { setupOpts = {
bigfile.enable = true; bigfile.enable = true;
explorer.replace_netrw = true;
dashboard = { dashboard = {
preset.keys = [ preset.keys = [
{ {
@ -209,7 +197,6 @@
picker = { picker = {
enable = true; enable = true;
sources = { sources = {
explorer = { };
}; };
}; };
}; };

View file

@ -0,0 +1,34 @@
{ lib, pkgs, ... }:
{
vim.formatter.conform-nvim = {
enable = true;
setupOpts = {
formatters_by_ft =
let
autofmt = lib.mkLuaInline ''{"autofmt",stop_after_first=true}'';
in
{
css = autofmt;
html = autofmt;
javascript = autofmt;
javascriptreact = autofmt;
json = autofmt;
jsonc = autofmt;
markdown = autofmt;
nix = autofmt;
rust = autofmt;
typescript = autofmt;
typescriptreact = autofmt;
yaml = autofmt;
};
formatters.autofmt = {
"inherit" = false;
args = [
"--stdio"
"$FILENAME"
];
command = "${pkgs.autofmt}/bin/autofmt";
};
};
};
}

View file

@ -7,7 +7,10 @@ let
nvidiaDriverChannel = config.boot.kernelPackages.nvidiaPackages.latest; nvidiaDriverChannel = config.boot.kernelPackages.nvidiaPackages.latest;
in in
{ {
services.xserver.videoDrivers = [ "nvidia" "amdgpu"]; services.xserver.videoDrivers = [
"nvidia"
"amdgpu"
];
nixpkgs.config = { nixpkgs.config = {
nvidia.acceptLicense = true; nvidia.acceptLicense = true;
@ -46,17 +49,17 @@ in
finegrained = true; # More precise power consumption control finegrained = true; # More precise power consumption control
}; };
prime = { prime = {
offload = { offload = {
enable = true; enable = true;
enableOffloadCmd = true; enableOffloadCmd = true;
};
nvidiaBusId = "PCI:1:0:0";
amdgpuBusId = "PCI:15:0:0";
}; };
nvidiaBusId = "PCI:1:0:0";
amdgpuBusId = "PCI:15:0:0";
};
# Use the NVidia open source kernel module (not to be confused with the # Use the NVidia open source kernel module (not to be confused with the
# independent third-party "nouveau" open source driver). # independent third-party "nouveau" open source driver).
# Currently alpha-quality/buggy, so false is currently the recommended setting. # Currently alpha-quality/buggy, so false is currently the recommended setting.

View file

@ -10,13 +10,12 @@
# Set your time zone. # Set your time zone.
time.timeZone = user.timeZone; time.timeZone = user.timeZone;
home-manager.users.${user.username}.home.sessionVariables = home-manager.users.${user.username}.home.sessionVariables = {
{ EDITOR = user.editor;
EDITOR = user.editor; TERMINAL = user.term;
TERMINAL = user.term; }
} // lib.optionalAttrs (user ? "browser") {
// lib.optionalAttrs (user ? "browser") { BROWSER = user.browser;
BROWSER = user.browser; };
};
} }

14
packages/autofmt.nix Normal file
View file

@ -0,0 +1,14 @@
{ pkgs, ... }:
pkgs.writeShellApplication {
name = "autofmt";
runtimeInputs = with pkgs; [
# include only a couple of formatters by default
deno
nixfmt-rfc-style
dprint
rustfmt
zig
clang-tools
];
text = ''exec deno run -A ${../files/autofmt.js} "$@"'';
}

View file

@ -78,4 +78,3 @@ configuration.
nix run .#nvim-natalie # run by user nix run .#nvim-natalie # run by user
./nvim # run based on your username ./nvim # run based on your username
``` ```

View file

@ -16,6 +16,7 @@ in
ripgrep ripgrep
uv uv
nh nh
pkgs.autofmt
]; ];
# packages to install for desktop environments (non-server) # packages to install for desktop environments (non-server)
desktop = [ desktop = [

View file

@ -2,13 +2,18 @@
# your system. Help is available in the configuration.nix(5) man page, on # your system. Help is available in the configuration.nix(5) man page, on
# https://search.nixos.org/options and in the NixOS manual (`nixos-help`). # https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
{ {
imports = imports = [
[ # Include the results of the hardware scan. # Include the results of the hardware scan.
./hardware-configuration.nix ./hardware-configuration.nix
]; ];
# Use the systemd-boot EFI boot loader. # Use the systemd-boot EFI boot loader.
boot.loader.efi.canTouchEfiVariables = true; boot.loader.efi.canTouchEfiVariables = true;
@ -39,7 +44,6 @@
# Enable the GNOME Desktop Environment. # Enable the GNOME Desktop Environment.
services.xserver.displayManager.gdm.enable = true; services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true; services.xserver.desktopManager.gnome.enable = true;
# Configure keymap in X11 # Configure keymap in X11
# services.xserver.xkb.layout = "us"; # services.xserver.xkb.layout = "us";
@ -124,4 +128,3 @@
# For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "25.05"; # Did you read the comment? system.stateVersion = "25.05"; # Did you read the comment?
} }

View file

@ -62,8 +62,8 @@ pages:
location: London, United Kingdom location: London, United Kingdom
units: metric # alternatively "imperial" units: metric # alternatively "imperial"
hour-format: 12h # alternatively "24h" hour-format: 12h # alternatively "24h"
# Optionally hide the location from being displayed in the widget # Optionally hide the location from being displayed in the widget
# hide-location: true # hide-location: true
- type: markets - type: markets
# The link to go to when clicking on the symbol in the UI, # The link to go to when clicking on the symbol in the UI,

View file

@ -9,7 +9,6 @@
withPython3 = true; withPython3 = true;
python3Packages = [ "pynvim" ]; python3Packages = [ "pynvim" ];
autocmds = [ autocmds = [
#Autocommand to fall back to treesitter folding if LSP doesnt support it #Autocommand to fall back to treesitter folding if LSP doesnt support it
{ {

View file

@ -1,10 +1,10 @@
{ ... }: { ... }:
let let
mkKeymap = mode: key: action: desc: { mkKeymap = mode: key: action: desc: {
inherit mode; inherit mode;
inherit key action desc; inherit key action desc;
}; };
n = mkKeymap "n"; # normal mode n = mkKeymap "n"; # normal mode
in in
{ {
vim = { vim = {