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

View file

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

View file

@ -1,7 +1,6 @@
# This function creates a NixOS system based on our VM setup for a
# particular architecture.
{
nixpkgs,
overlays,
inputs,
mkNeovim,
@ -14,11 +13,11 @@ name:
extraModules ? [ ],
}:
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};
# 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}";
userConfig = import (userDir + "/user.nix");
@ -69,11 +68,12 @@ let
mainHomeImports = builtins.filter (f: f != null) [
(pathOrNull userHomePath)
(pathOrNull hostHomePath)
(
{ pkgs, ... }:
{
home.packages = [
(mkNeovim user system)
];
home.packages = [ (mkNeovim user pkgs) ];
}
)
inputs.nvf.homeManagerModules.default
inputs.android-nixpkgs.hmModule
];

View file

@ -5,7 +5,10 @@
...
}:
{
imports = [ ./options.nix ];
imports = [
./options.nix
./formatter.nix
];
# based on default options from upstream:
# https://github.com/NotAShelf/nvf/blob/main/configuration.nix
#
@ -107,23 +110,9 @@
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 = {
neo-tree = {
enable = false;
enable = true;
};
};
tabline = {
@ -154,7 +143,6 @@
enable = true;
setupOpts = {
bigfile.enable = true;
explorer.replace_netrw = true;
dashboard = {
preset.keys = [
{
@ -209,7 +197,6 @@
picker = {
enable = true;
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;
in
{
services.xserver.videoDrivers = [ "nvidia" "amdgpu"];
services.xserver.videoDrivers = [
"nvidia"
"amdgpu"
];
nixpkgs.config = {
nvidia.acceptLicense = true;

View file

@ -10,8 +10,7 @@
# Set your time zone.
time.timeZone = user.timeZone;
home-manager.users.${user.username}.home.sessionVariables =
{
home-manager.users.${user.username}.home.sessionVariables = {
EDITOR = user.editor;
TERMINAL = user.term;
}

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
./nvim # run based on your username
```

View file

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

View file

@ -2,11 +2,16 @@
# 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`).
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
{
imports =
[ # Include the results of the hardware scan.
imports = [
# Include the results of the hardware scan.
./hardware-configuration.nix
];
@ -40,7 +45,6 @@
services.xserver.displayManager.gdm.enable = true;
services.xserver.desktopManager.gnome.enable = true;
# Configure keymap in X11
# services.xserver.xkb.layout = "us";
# services.xserver.xkb.options = "eurosign:e,caps:escape";
@ -124,4 +128,3 @@
# 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?
}

View file

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

View file

@ -1,5 +1,5 @@
{ ... }:
let
let
mkKeymap = mode: key: action: desc: {
inherit mode;
inherit key action desc;