Merge branch 'main' of paperclover.dev:nix/config
This commit is contained in:
commit
d3c18bc88c
15 changed files with 722 additions and 113 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/src/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:
|
}@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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
];
|
];
|
||||||
|
|
|
@ -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,20 +110,6 @@
|
||||||
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 = false;
|
||||||
|
@ -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 = { };
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
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;
|
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.
|
||||||
|
|
|
@ -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
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
|
nix run .#nvim-natalie # run by user
|
||||||
./nvim # run based on your username
|
./nvim # run based on your username
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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 = [
|
||||||
|
|
|
@ -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?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
Loading…
Reference in a new issue