From 4b91b37f4aa01a325d2c12979a3f8f3a9467d464 Mon Sep 17 00:00:00 2001 From: chloe caruso Date: Sun, 17 Aug 2025 20:40:58 -0700 Subject: [PATCH] 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 --- files/autofmt.js | 593 ++++++++++++++++++++++++ flake.nix | 69 ++- lib/mkNeovim.nix | 14 +- lib/mkSystem.nix | 16 +- modules/neovim/default.nix | 23 +- modules/neovim/formatter.nix | 34 ++ modules/nixos/nvidia.nix | 23 +- modules/shared/user-system-settings.nix | 15 +- packages/autofmt.nix | 14 + readme.md | 1 - users/chloe/home.nix | 1 + users/julia/cattop/configuration.nix | 17 +- users/natalie/glance.yml | 4 +- users/natalie/vim.nix | 1 - users/natalie/vim/keybinds.nix | 12 +- 15 files changed, 723 insertions(+), 114 deletions(-) create mode 100755 files/autofmt.js create mode 100644 modules/neovim/formatter.nix create mode 100644 packages/autofmt.nix diff --git a/files/autofmt.js b/files/autofmt.js new file mode 100755 index 0000000..34dbdbf --- /dev/null +++ b/files/autofmt.js @@ -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=`. 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= add an exclusion glob`); + console.error(` --stdio= 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} */ +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"; diff --git a/flake.nix b/flake.nix index cfd82ab..8f40bd9 100644 --- a/flake.nix +++ b/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" diff --git a/lib/mkNeovim.nix b/lib/mkNeovim.nix index 72e6d9c..c0e0c70 100644 --- a/lib/mkNeovim.nix +++ b/lib/mkNeovim.nix @@ -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 diff --git a/lib/mkSystem.nix b/lib/mkSystem.nix index e393869..9e3a13c 100644 --- a/lib/mkSystem.nix +++ b/lib/mkSystem.nix @@ -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) - { - home.packages = [ - (mkNeovim user system) - ]; - } + ( + { pkgs, ... }: + { + home.packages = [ (mkNeovim user pkgs) ]; + } + ) inputs.nvf.homeManagerModules.default inputs.android-nixpkgs.hmModule ]; diff --git a/modules/neovim/default.nix b/modules/neovim/default.nix index 54e7b0f..792d1fc 100644 --- a/modules/neovim/default.nix +++ b/modules/neovim/default.nix @@ -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 = { }; }; }; }; diff --git a/modules/neovim/formatter.nix b/modules/neovim/formatter.nix new file mode 100644 index 0000000..aa3e4dd --- /dev/null +++ b/modules/neovim/formatter.nix @@ -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"; + }; + }; + }; +} diff --git a/modules/nixos/nvidia.nix b/modules/nixos/nvidia.nix index fb64da3..88f2252 100644 --- a/modules/nixos/nvidia.nix +++ b/modules/nixos/nvidia.nix @@ -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; @@ -46,17 +49,17 @@ in finegrained = true; # More precise power consumption control }; - prime = { - offload = { - enable = true; - enableOffloadCmd = true; - }; - - nvidiaBusId = "PCI:1:0:0"; - amdgpuBusId = "PCI:15:0:0"; - + prime = { + offload = { + enable = true; + enableOffloadCmd = true; }; + nvidiaBusId = "PCI:1:0:0"; + amdgpuBusId = "PCI:15:0:0"; + + }; + # Use the NVidia open source kernel module (not to be confused with the # independent third-party "nouveau" open source driver). # Currently alpha-quality/buggy, so false is currently the recommended setting. diff --git a/modules/shared/user-system-settings.nix b/modules/shared/user-system-settings.nix index bbd03c9..dd67a48 100644 --- a/modules/shared/user-system-settings.nix +++ b/modules/shared/user-system-settings.nix @@ -10,13 +10,12 @@ # Set your time zone. time.timeZone = user.timeZone; - home-manager.users.${user.username}.home.sessionVariables = - { - EDITOR = user.editor; - TERMINAL = user.term; - } - // lib.optionalAttrs (user ? "browser") { - BROWSER = user.browser; - }; + home-manager.users.${user.username}.home.sessionVariables = { + EDITOR = user.editor; + TERMINAL = user.term; + } + // lib.optionalAttrs (user ? "browser") { + BROWSER = user.browser; + }; } diff --git a/packages/autofmt.nix b/packages/autofmt.nix new file mode 100644 index 0000000..16ade37 --- /dev/null +++ b/packages/autofmt.nix @@ -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} "$@"''; +} diff --git a/readme.md b/readme.md index aca64be..7c7416d 100644 --- a/readme.md +++ b/readme.md @@ -78,4 +78,3 @@ configuration. nix run .#nvim-natalie # run by user ./nvim # run based on your username ``` - diff --git a/users/chloe/home.nix b/users/chloe/home.nix index 1899685..b46fbf4 100644 --- a/users/chloe/home.nix +++ b/users/chloe/home.nix @@ -16,6 +16,7 @@ in ripgrep uv nh + pkgs.autofmt ]; # packages to install for desktop environments (non-server) desktop = [ diff --git a/users/julia/cattop/configuration.nix b/users/julia/cattop/configuration.nix index 4b0e96f..35b2d32 100644 --- a/users/julia/cattop/configuration.nix +++ b/users/julia/cattop/configuration.nix @@ -2,13 +2,18 @@ # 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. - ./hardware-configuration.nix - ]; + imports = [ + # Include the results of the hardware scan. + ./hardware-configuration.nix + ]; # Use the systemd-boot EFI boot loader. boot.loader.efi.canTouchEfiVariables = true; @@ -39,7 +44,6 @@ # Enable the GNOME Desktop Environment. services.xserver.displayManager.gdm.enable = true; services.xserver.desktopManager.gnome.enable = true; - # Configure keymap in X11 # 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 . system.stateVersion = "25.05"; # Did you read the comment? } - diff --git a/users/natalie/glance.yml b/users/natalie/glance.yml index 92b4de1..1f149d6 100644 --- a/users/natalie/glance.yml +++ b/users/natalie/glance.yml @@ -62,8 +62,8 @@ pages: location: London, United Kingdom units: metric # alternatively "imperial" hour-format: 12h # alternatively "24h" - # Optionally hide the location from being displayed in the widget - # hide-location: true + # Optionally hide the location from being displayed in the widget + # hide-location: true - type: markets # The link to go to when clicking on the symbol in the UI, diff --git a/users/natalie/vim.nix b/users/natalie/vim.nix index ec5865f..9104cec 100644 --- a/users/natalie/vim.nix +++ b/users/natalie/vim.nix @@ -9,7 +9,6 @@ withPython3 = true; python3Packages = [ "pynvim" ]; - autocmds = [ #Autocommand to fall back to treesitter folding if LSP doesnt support it { diff --git a/users/natalie/vim/keybinds.nix b/users/natalie/vim/keybinds.nix index cc6d5d4..6caf7ac 100644 --- a/users/natalie/vim/keybinds.nix +++ b/users/natalie/vim/keybinds.nix @@ -1,10 +1,10 @@ { ... }: - let - mkKeymap = mode: key: action: desc: { - inherit mode; - inherit key action desc; - }; - n = mkKeymap "n"; # normal mode +let + mkKeymap = mode: key: action: desc: { + inherit mode; + inherit key action desc; + }; + n = mkKeymap "n"; # normal mode in { vim = {