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:
parent
2e9eda64d4
commit
4b91b37f4a
15 changed files with 723 additions and 114 deletions
593
files/autofmt.js
Executable file
593
files/autofmt.js
Executable 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";
|
69
flake.nix
69
flake.nix
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
];
|
||||
|
|
|
@ -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 = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
34
modules/neovim/formatter.nix
Normal file
34
modules/neovim/formatter.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
14
packages/autofmt.nix
Normal 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} "$@"'';
|
||||
}
|
|
@ -78,4 +78,3 @@ configuration.
|
|||
nix run .#nvim-natalie # run by user
|
||||
./nvim # run based on your username
|
||||
```
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ in
|
|||
ripgrep
|
||||
uv
|
||||
nh
|
||||
pkgs.autofmt
|
||||
];
|
||||
# packages to install for desktop environments (non-server)
|
||||
desktop = [
|
||||
|
|
|
@ -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?
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
withPython3 = true;
|
||||
python3Packages = [ "pynvim" ];
|
||||
|
||||
|
||||
autocmds = [
|
||||
#Autocommand to fall back to treesitter folding if LSP doesnt support it
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{ ... }:
|
||||
let
|
||||
let
|
||||
mkKeymap = mode: key: action: desc: {
|
||||
inherit mode;
|
||||
inherit key action desc;
|
||||
|
|
Loading…
Reference in a new issue