| author | |
| committer | |
| log | f60c39943834cc26bc64c715c9b94d541e98526a |
| tree | e11c4e5957192a68624e526b980cc5e543c252ce |
| parent | 4d3eb16274be590e53bfb3ebd10f3ffa2db56d56 |
| parent | cec0cd905ddaec1f0ddce5cbd2c022ca6dc82a4c |
20 files changed, 1110 insertions(+), 71 deletions(-)
flake.nix+2-21| ... | @@ -74,31 +74,11 @@ | ... | @@ -74,31 +74,11 @@ |
| 74 | }; | 74 | }; |
| 75 | }); | 75 | }); |
| 76 | }) | 76 | }) |
| 77 | # https://github.com/NixOS/nixpkgs/issues/476669#issuecomment-3707850520 | ||
| 78 | (_: prev: { | ||
| 79 | vesktop = prev.vesktop.overrideAttrs (_: { | ||
| 80 | preBuild = '' | ||
| 81 | cp -r ${prev.electron.dist} electron-dist | ||
| 82 | chmod -R u+w electron-dist | ||
| 83 | ''; | ||
| 84 | buildPhase = '' | ||
| 85 | runHook preBuild | ||
| 86 | |||
| 87 | pnpm build | ||
| 88 | pnpm exec electron-builder \ | ||
| 89 | --dir \ | ||
| 90 | -c.asarUnpack="**/*.node" \ | ||
| 91 | -c.electronDist="electron-dist" \ | ||
| 92 | -c.electronVersion=${prev.electron.version} | ||
| 93 | |||
| 94 | runHook postBuild | ||
| 95 | ''; | ||
| 96 | }); | ||
| 97 | }) | ||
| 98 | 77 | ||
| 99 | # custom packages | 78 | # custom packages |
| 100 | (_: pkgs: { | 79 | (_: pkgs: { |
| 101 | autofmt = pkgs.callPackage ./packages/autofmt { }; | 80 | autofmt = pkgs.callPackage ./packages/autofmt { }; |
| 81 | "jj-import-merge" = pkgs.callPackage ./packages/jj-import-merge { }; | ||
| 102 | exiled_exchange_2 = pkgs.callPackage ./packages/exiled_exchange_2 { }; | 82 | exiled_exchange_2 = pkgs.callPackage ./packages/exiled_exchange_2 { }; |
| 103 | awakened_poe_trade = pkgs.callPackage ./packages/awakened_poe_trade { }; | 83 | awakened_poe_trade = pkgs.callPackage ./packages/awakened_poe_trade { }; |
| 104 | mtga-draft-17lands = pkgs.callPackage ./packages/17lands { }; | 84 | mtga-draft-17lands = pkgs.callPackage ./packages/17lands { }; |
| ... | @@ -133,6 +113,7 @@ | ... | @@ -133,6 +113,7 @@ |
| 133 | nvim-natalie = mkNeovim "natalie" pkgs; | 113 | nvim-natalie = mkNeovim "natalie" pkgs; |
| 134 | 114 | ||
| 135 | inherit (pkgs) autofmt; | 115 | inherit (pkgs) autofmt; |
| 116 | "jj-import-merge" = pkgs."jj-import-merge"; | ||
| 136 | } | 117 | } |
| 137 | // lib.optionalAttrs (system == "aarch64-darwin") { | 118 | // lib.optionalAttrs (system == "aarch64-darwin") { |
| 138 | # "nix run .#darwin-rebuild" | 119 | # "nix run .#darwin-rebuild" |
modules/home/default.nix+1| ... | @@ -149,6 +149,7 @@ in | ... | @@ -149,6 +149,7 @@ in |
| 149 | behavior = "drop"; | 149 | behavior = "drop"; |
| 150 | }; | 150 | }; |
| 151 | git = { | 151 | git = { |
| 152 | colocate = false; | ||
| 152 | sign-on-push = config.programs.git.signing.key != null; | 153 | sign-on-push = config.programs.git.signing.key != null; |
| 153 | }; | 154 | }; |
| 154 | remotes.origin = { | 155 | remotes.origin = { |
modules/macos/tiling/aerospace.nix+1-1| ... | @@ -34,7 +34,7 @@ | ... | @@ -34,7 +34,7 @@ |
| 34 | outer = { | 34 | outer = { |
| 35 | left = 0; | 35 | left = 0; |
| 36 | bottom = 0; | 36 | bottom = 0; |
| 37 | top = 40; | 37 | top = 10; |
| 38 | right = 0; | 38 | right = 0; |
| 39 | }; | 39 | }; |
| 40 | }; | 40 | }; |
packages/autofmt/autofmt.js+8-4| ... | @@ -100,12 +100,14 @@ const formatters = { | ... | @@ -100,12 +100,14 @@ const formatters = { |
| 100 | }, | 100 | }, |
| 101 | prettier: { | 101 | prettier: { |
| 102 | languages: ["javascript", "markdown", "json", "yaml", "mdx", "html", "css"], | 102 | languages: ["javascript", "markdown", "json", "yaml", "mdx", "html", "css"], |
| 103 | mustMatchFile: true, | ||
| 103 | files: ["node_modules/.bin/prettier"], | 104 | files: ["node_modules/.bin/prettier"], |
| 104 | cmd: ["node_modules/.bin/prettier", "--write", "--", "$files"], | 105 | cmd: ["node_modules/.bin/prettier", "--write", "--", "$files"], |
| 105 | stdin: (file) => ["node_modules/.bin/prettier", "--stdin-filepath", file], | 106 | stdin: (file) => ["node_modules/.bin/prettier", "--stdin-filepath", file], |
| 106 | }, | 107 | }, |
| 107 | biome: { | 108 | biome: { |
| 108 | languages: ["javascript", "json", "css"], | 109 | languages: ["javascript", "json", "css"], |
| 110 | mustMatchFile: true, | ||
| 109 | files: ["node_modules/.bin/biome"], | 111 | files: ["node_modules/.bin/biome"], |
| 110 | cmd: ["node_modules/.bin/biome", "format", "--", "$files"], | 112 | cmd: ["node_modules/.bin/biome", "format", "--", "$files"], |
| 111 | stdin: (file) => [ | 113 | stdin: (file) => [ |
| ... | @@ -542,20 +544,22 @@ function pickFormatter(file) { | ... | @@ -542,20 +544,22 @@ function pickFormatter(file) { |
| 542 | : Object.entries(formatters[fmt].files) | 544 | : Object.entries(formatters[fmt].files) |
| 543 | .map(([base, check]) => ({ base, check })); | 545 | .map(([base, check]) => ({ base, check })); |
| 544 | for (const { base, check } of filesToCheck) { | 546 | for (const { base, check } of filesToCheck) { |
| 547 | let fileMatches; | ||
| 545 | if (base.includes("/")) { | 548 | if (base.includes("/")) { |
| 546 | matches = readDir(path.join(x, path.dirname(base))) // | 549 | fileMatches = readDir(path.join(x, path.dirname(base))) // |
| 547 | .includes(path.basename(base)); | 550 | .includes(path.basename(base)); |
| 548 | } else { | 551 | } else { |
| 549 | matches = children.includes(base); | 552 | fileMatches = children.includes(base); |
| 550 | } | 553 | } |
| 551 | if (matches && typeof check === "function") { | 554 | if (fileMatches && typeof check === "function") { |
| 552 | let text; | 555 | let text; |
| 553 | matches = !!check({ | 556 | fileMatches = !!check({ |
| 554 | get text() { | 557 | get text() { |
| 555 | return text ??= fs.readFileSync(path.join(x, base), "utf-8"); | 558 | return text ??= fs.readFileSync(path.join(x, base), "utf-8"); |
| 556 | }, | 559 | }, |
| 557 | }); | 560 | }); |
| 558 | } | 561 | } |
| 562 | matches ||= fileMatches; | ||
| 559 | } | 563 | } |
| 560 | } | 564 | } |
| 561 | if (matches) { | 565 | if (matches) { |
packages/autofmt/default.nix+13-3| ... | @@ -1,6 +1,5 @@ | ... | @@ -1,6 +1,5 @@ |
| 1 | { pkgs, ... }: | 1 | { pkgs, ... }: |
| 2 | pkgs.writeShellApplication { | 2 | let |
| 3 | name = "autofmt"; | ||
| 4 | runtimeInputs = with pkgs; [ | 3 | runtimeInputs = with pkgs; [ |
| 5 | # include only a couple of formatters by default | 4 | # include only a couple of formatters by default |
| 6 | clang-tools | 5 | clang-tools |
| ... | @@ -11,5 +10,16 @@ pkgs.writeShellApplication { | ... | @@ -11,5 +10,16 @@ pkgs.writeShellApplication { |
| 11 | rustfmt | 10 | rustfmt |
| 12 | zig | 11 | zig |
| 13 | ]; | 12 | ]; |
| 14 | text = ''exec ${pkgs.nodejs}/bin/node ${./autofmt.js} "$@"''; | 13 | in |
| 14 | pkgs.writeTextFile { | ||
| 15 | name = "autofmt"; | ||
| 16 | destination = "/bin/autofmt"; | ||
| 17 | executable = true; | ||
| 18 | text = builtins.replaceStrings | ||
| 19 | [ "#!/usr/bin/env node" ] | ||
| 20 | [ | ||
| 21 | ''#!${pkgs.nodejs}/bin/node | ||
| 22 | process.env.PATH = "${pkgs.lib.makeBinPath runtimeInputs}:" + (process.env.PATH ?? "");'' | ||
| 23 | ] | ||
| 24 | (builtins.readFile ./autofmt.js); | ||
| 15 | } | 25 | } |
packages/jj-import-merge/default.nix created+9| ... | @@ -0,0 +1,9 @@ | ||
| 1 | { pkgs, ... }: | ||
| 2 | pkgs.writeShellApplication { | ||
| 3 | name = "jj-import-merge"; | ||
| 4 | runtimeInputs = with pkgs; [ | ||
| 5 | autofmt | ||
| 6 | nodejs | ||
| 7 | ]; | ||
| 8 | text = ''exec ${pkgs.nodejs}/bin/node ${./jj-import-merge.js} "$@"''; | ||
| 9 | } | ||
packages/jj-import-merge/jj-import-merge.js created+871| ... | @@ -0,0 +1,871 @@ | ||
| 1 | #!/usr/bin/env node | ||
| 2 | // jj-import-merge | ||
| 3 | // Merge top-of-file JS/TS imports from 2-sided Jujutsu conflicts. | ||
| 4 | |||
| 5 | import * as child_process from "node:child_process"; | ||
| 6 | import * as fs from "node:fs"; | ||
| 7 | import * as path from "node:path"; | ||
| 8 | import process from "node:process"; | ||
| 9 | |||
| 10 | const SUPPORTED_EXTENSIONS = new Set([ | ||
| 11 | ".cjs", | ||
| 12 | ".cts", | ||
| 13 | ".js", | ||
| 14 | ".jsx", | ||
| 15 | ".mjs", | ||
| 16 | ".mts", | ||
| 17 | ".ts", | ||
| 18 | ".tsx", | ||
| 19 | ]); | ||
| 20 | |||
| 21 | const CONFLICT_START_RE = /^<<<<<<< conflict \d+ of \d+/m; | ||
| 22 | const CONFLICT_END_RE = /^>>>>>>> conflict \d+ of \d+ ends$/m; | ||
| 23 | |||
| 24 | main(); | ||
| 25 | |||
| 26 | function main() { | ||
| 27 | const options = parseArgs(process.argv.slice(2)); | ||
| 28 | let files = options.files.map((file) => path.resolve(file)); | ||
| 29 | |||
| 30 | if (options.allJj) { | ||
| 31 | files.push(...listJjConflictFiles().map((file) => path.resolve(file))); | ||
| 32 | } | ||
| 33 | |||
| 34 | files = [...new Set(files)]; | ||
| 35 | if (files.length === 0) { | ||
| 36 | usage(1); | ||
| 37 | } | ||
| 38 | |||
| 39 | let failed = false; | ||
| 40 | for (const file of files) { | ||
| 41 | try { | ||
| 42 | const result = processFile(file); | ||
| 43 | if (result.kind === "formatted") { | ||
| 44 | console.info(`formatted ${path.relative(process.cwd(), file)}`); | ||
| 45 | } else if (result.kind === "rewritten") { | ||
| 46 | console.info(`rewrote ${path.relative(process.cwd(), file)}`); | ||
| 47 | } | ||
| 48 | } catch (error) { | ||
| 49 | failed = true; | ||
| 50 | const message = error instanceof Error ? error.message : String(error); | ||
| 51 | console.error(`error: ${message}`); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | process.exit(failed ? 1 : 0); | ||
| 56 | } | ||
| 57 | |||
| 58 | function parseArgs(argv) { | ||
| 59 | const options = { | ||
| 60 | allJj: false, | ||
| 61 | files: [], | ||
| 62 | }; | ||
| 63 | |||
| 64 | while (argv.length > 0) { | ||
| 65 | const arg = argv.shift(); | ||
| 66 | if (arg === "-h" || arg === "--help") { | ||
| 67 | usage(0); | ||
| 68 | } else if (arg === "--all-jj") { | ||
| 69 | options.allJj = true; | ||
| 70 | } else if (arg === "--") { | ||
| 71 | options.files.push(...argv); | ||
| 72 | break; | ||
| 73 | } else if (arg.startsWith("-")) { | ||
| 74 | console.error(`error: unknown option ${JSON.stringify(arg)}`); | ||
| 75 | usage(1); | ||
| 76 | } else { | ||
| 77 | options.files.push(arg); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | return options; | ||
| 82 | } | ||
| 83 | |||
| 84 | function usage(code) { | ||
| 85 | const exe = "jj-import-merge"; | ||
| 86 | const stream = code === 0 ? process.stdout : process.stderr; | ||
| 87 | stream.write(`usage: ${exe} [--all-jj] [--] <file>...\n`); | ||
| 88 | stream.write(`\n`); | ||
| 89 | stream.write(`merge top-of-file JS/TS imports from 2-sided Jujutsu conflicts\n`); | ||
| 90 | stream.write(`\n`); | ||
| 91 | stream.write(`options:\n`); | ||
| 92 | stream.write(` --all-jj resolve all conflicted JS/TS files from 'jj resolve --list'\n`); | ||
| 93 | stream.write(` -h show help\n`); | ||
| 94 | process.exit(code); | ||
| 95 | } | ||
| 96 | |||
| 97 | function listJjConflictFiles() { | ||
| 98 | const result = child_process.spawnSync( | ||
| 99 | "jj", | ||
| 100 | ["--color=never", "--quiet", "resolve", "--list"], | ||
| 101 | { encoding: "utf8" }, | ||
| 102 | ); | ||
| 103 | |||
| 104 | if (result.error) { | ||
| 105 | throw new Error(`failed to run jj: ${result.error.message}`); | ||
| 106 | } | ||
| 107 | if (result.status !== 0) { | ||
| 108 | throw new Error(result.stderr.trim() || "jj resolve --list failed"); | ||
| 109 | } | ||
| 110 | |||
| 111 | return result.stdout | ||
| 112 | .split(/\r?\n/) | ||
| 113 | .map((line) => line.trimEnd()) | ||
| 114 | .filter(Boolean) | ||
| 115 | .map((line) => line.match(/^(.*?)\s{2,}.+conflict$/)) | ||
| 116 | .filter(Boolean) | ||
| 117 | .map((match) => match[1]); | ||
| 118 | } | ||
| 119 | |||
| 120 | function processFile(file) { | ||
| 121 | if (!SUPPORTED_EXTENSIONS.has(path.extname(file))) { | ||
| 122 | return { kind: "noop" }; | ||
| 123 | } | ||
| 124 | |||
| 125 | let original; | ||
| 126 | try { | ||
| 127 | original = fs.readFileSync(file, "utf8"); | ||
| 128 | } catch (error) { | ||
| 129 | const message = error instanceof Error ? error.message : String(error); | ||
| 130 | throw new Error(`cannot read ${file}: ${message}`); | ||
| 131 | } | ||
| 132 | |||
| 133 | if (!CONFLICT_START_RE.test(original)) { | ||
| 134 | return { kind: "noop" }; | ||
| 135 | } | ||
| 136 | |||
| 137 | const parsed = parseConflictFile(original); | ||
| 138 | if (!parsed.supported) { | ||
| 139 | return { kind: "noop" }; | ||
| 140 | } | ||
| 141 | |||
| 142 | const leftText = parsed.parts.map((part) => part.a).join(""); | ||
| 143 | const rightText = parsed.parts.map((part) => part.b).join(""); | ||
| 144 | const leftImports = extractTopImportBlock(leftText); | ||
| 145 | const rightImports = extractTopImportBlock(rightText); | ||
| 146 | if (!leftImports || !rightImports) { | ||
| 147 | return { kind: "noop" }; | ||
| 148 | } | ||
| 149 | |||
| 150 | const leftPrelude = leftText.slice(0, leftImports.start); | ||
| 151 | const rightPrelude = rightText.slice(0, rightImports.start); | ||
| 152 | if (leftPrelude !== rightPrelude) { | ||
| 153 | return { kind: "noop" }; | ||
| 154 | } | ||
| 155 | |||
| 156 | const start = projectBoundaryToOriginal( | ||
| 157 | parsed.parts, | ||
| 158 | leftImports.start, | ||
| 159 | rightImports.start, | ||
| 160 | ); | ||
| 161 | const end = projectBoundaryToOriginal(parsed.parts, leftImports.end, rightImports.end); | ||
| 162 | if (start == null || end == null || end < start) { | ||
| 163 | return { kind: "noop" }; | ||
| 164 | } | ||
| 165 | |||
| 166 | const mergedImports = mergeImportStatements( | ||
| 167 | leftImports.statements, | ||
| 168 | rightImports.statements, | ||
| 169 | detectNewline(original), | ||
| 170 | ); | ||
| 171 | const updated = original.slice(0, start) + mergedImports + original.slice(end); | ||
| 172 | |||
| 173 | if (updated === original) { | ||
| 174 | return { kind: "noop" }; | ||
| 175 | } | ||
| 176 | |||
| 177 | try { | ||
| 178 | fs.writeFileSync(file, updated, "utf8"); | ||
| 179 | } catch (error) { | ||
| 180 | const message = error instanceof Error ? error.message : String(error); | ||
| 181 | throw new Error(`cannot write ${file}: ${message}`); | ||
| 182 | } | ||
| 183 | |||
| 184 | if (hasConflictMarkers(updated)) { | ||
| 185 | return { kind: "rewritten" }; | ||
| 186 | } | ||
| 187 | |||
| 188 | const formatResult = child_process.spawnSync("autofmt", [file], { | ||
| 189 | encoding: "utf8", | ||
| 190 | stdio: "inherit", | ||
| 191 | }); | ||
| 192 | |||
| 193 | if (formatResult.error || formatResult.status !== 0) { | ||
| 194 | fs.writeFileSync(file, original, "utf8"); | ||
| 195 | if (formatResult.error) { | ||
| 196 | throw new Error(`autofmt failed for ${file}: ${formatResult.error.message}`); | ||
| 197 | } | ||
| 198 | throw new Error(`autofmt failed for ${file}`); | ||
| 199 | } | ||
| 200 | |||
| 201 | return { kind: "formatted" }; | ||
| 202 | } | ||
| 203 | |||
| 204 | function parseConflictFile(text) { | ||
| 205 | const lines = splitLines(text); | ||
| 206 | const parts = []; | ||
| 207 | |||
| 208 | let index = 0; | ||
| 209 | while (index < lines.length) { | ||
| 210 | const line = lines[index].text; | ||
| 211 | if (!line.startsWith("<<<<<<< conflict ")) { | ||
| 212 | const start = index; | ||
| 213 | index += 1; | ||
| 214 | while ( | ||
| 215 | index < lines.length && | ||
| 216 | !lines[index].text.startsWith("<<<<<<< conflict ") | ||
| 217 | ) { | ||
| 218 | index += 1; | ||
| 219 | } | ||
| 220 | const raw = joinLines(lines, start, index); | ||
| 221 | parts.push({ type: "plain", orig: raw, a: raw, b: raw }); | ||
| 222 | continue; | ||
| 223 | } | ||
| 224 | |||
| 225 | const block = parseConflictBlock(lines, index); | ||
| 226 | if (!block.supported) { | ||
| 227 | return { supported: false, parts: [] }; | ||
| 228 | } | ||
| 229 | parts.push(block.part); | ||
| 230 | index = block.nextIndex; | ||
| 231 | } | ||
| 232 | |||
| 233 | return { supported: true, parts }; | ||
| 234 | } | ||
| 235 | |||
| 236 | function parseConflictBlock(lines, startIndex) { | ||
| 237 | let index = startIndex + 1; | ||
| 238 | const entries = []; | ||
| 239 | |||
| 240 | while (index < lines.length) { | ||
| 241 | const line = lines[index].text; | ||
| 242 | if (line.startsWith(">>>>>>> conflict ")) { | ||
| 243 | break; | ||
| 244 | } | ||
| 245 | if (line.startsWith("+++++++ ")) { | ||
| 246 | const contentStart = index + 1; | ||
| 247 | index += 1; | ||
| 248 | while ( | ||
| 249 | index < lines.length && | ||
| 250 | !isConflictEntryStart(lines[index].text) && | ||
| 251 | !lines[index].text.startsWith(">>>>>>> conflict ") | ||
| 252 | ) { | ||
| 253 | index += 1; | ||
| 254 | } | ||
| 255 | entries.push({ | ||
| 256 | kind: "explicit", | ||
| 257 | text: joinLines(lines, contentStart, index), | ||
| 258 | }); | ||
| 259 | continue; | ||
| 260 | } | ||
| 261 | if (line.startsWith("%%%%%%% diff from:")) { | ||
| 262 | if (index + 1 >= lines.length || !/^\\\\+\s*to:/.test(lines[index + 1].text)) { | ||
| 263 | return { supported: false, nextIndex: startIndex + 1 }; | ||
| 264 | } | ||
| 265 | index += 2; | ||
| 266 | let text = ""; | ||
| 267 | while ( | ||
| 268 | index < lines.length && | ||
| 269 | !isConflictEntryStart(lines[index].text) && | ||
| 270 | !lines[index].text.startsWith(">>>>>>> conflict ") | ||
| 271 | ) { | ||
| 272 | const diffLine = lines[index].text; | ||
| 273 | const prefix = diffLine[0]; | ||
| 274 | if (prefix === " " || prefix === "+") { | ||
| 275 | text += diffLine.slice(1); | ||
| 276 | } else if (prefix !== "-") { | ||
| 277 | return { supported: false, nextIndex: startIndex + 1 }; | ||
| 278 | } | ||
| 279 | index += 1; | ||
| 280 | } | ||
| 281 | entries.push({ kind: "diff", text }); | ||
| 282 | continue; | ||
| 283 | } | ||
| 284 | return { supported: false, nextIndex: startIndex + 1 }; | ||
| 285 | } | ||
| 286 | |||
| 287 | if ( | ||
| 288 | index >= lines.length || | ||
| 289 | !lines[index].text.startsWith(">>>>>>> conflict ") || | ||
| 290 | entries.length !== 2 | ||
| 291 | ) { | ||
| 292 | return { supported: false, nextIndex: startIndex + 1 }; | ||
| 293 | } | ||
| 294 | |||
| 295 | return { | ||
| 296 | supported: true, | ||
| 297 | nextIndex: index + 1, | ||
| 298 | part: { | ||
| 299 | type: "conflict", | ||
| 300 | orig: joinLines(lines, startIndex, index + 1), | ||
| 301 | a: entries[0].text, | ||
| 302 | b: entries[1].text, | ||
| 303 | }, | ||
| 304 | }; | ||
| 305 | } | ||
| 306 | |||
| 307 | function isConflictEntryStart(line) { | ||
| 308 | return line.startsWith("+++++++ ") || line.startsWith("%%%%%%% diff from:"); | ||
| 309 | } | ||
| 310 | |||
| 311 | function splitLines(text) { | ||
| 312 | const lines = []; | ||
| 313 | let start = 0; | ||
| 314 | |||
| 315 | for (let index = 0; index < text.length; index += 1) { | ||
| 316 | if (text[index] !== "\n") continue; | ||
| 317 | lines.push({ | ||
| 318 | start, | ||
| 319 | end: index + 1, | ||
| 320 | text: text.slice(start, index + 1), | ||
| 321 | }); | ||
| 322 | start = index + 1; | ||
| 323 | } | ||
| 324 | |||
| 325 | if (start < text.length) { | ||
| 326 | lines.push({ start, end: text.length, text: text.slice(start) }); | ||
| 327 | } | ||
| 328 | |||
| 329 | return lines; | ||
| 330 | } | ||
| 331 | |||
| 332 | function joinLines(lines, start, end) { | ||
| 333 | let text = ""; | ||
| 334 | for (let index = start; index < end; index += 1) { | ||
| 335 | text += lines[index].text; | ||
| 336 | } | ||
| 337 | return text; | ||
| 338 | } | ||
| 339 | |||
| 340 | function extractTopImportBlock(text) { | ||
| 341 | const lines = splitLines(text); | ||
| 342 | if (lines.length === 0) return null; | ||
| 343 | |||
| 344 | let index = 0; | ||
| 345 | if (lines[0]?.text.startsWith("#!")) { | ||
| 346 | index += 1; | ||
| 347 | } | ||
| 348 | |||
| 349 | index = skipPrelude(lines, index); | ||
| 350 | if (index == null || index >= lines.length) { | ||
| 351 | return null; | ||
| 352 | } | ||
| 353 | |||
| 354 | const first = parseImportStatement(lines, index); | ||
| 355 | if (!first) { | ||
| 356 | return null; | ||
| 357 | } | ||
| 358 | |||
| 359 | const start = lines[index].start; | ||
| 360 | const statements = [first.statement]; | ||
| 361 | let end = offsetForLine(lines, first.nextIndex); | ||
| 362 | index = first.nextIndex; | ||
| 363 | |||
| 364 | while (true) { | ||
| 365 | const afterTrivia = skipBlankAndComments(lines, index); | ||
| 366 | if (afterTrivia == null) { | ||
| 367 | return null; | ||
| 368 | } | ||
| 369 | |||
| 370 | const next = parseImportStatement(lines, afterTrivia); | ||
| 371 | if (!next) { | ||
| 372 | break; | ||
| 373 | } | ||
| 374 | |||
| 375 | statements.push(next.statement); | ||
| 376 | end = offsetForLine(lines, next.nextIndex); | ||
| 377 | index = next.nextIndex; | ||
| 378 | } | ||
| 379 | |||
| 380 | return { start, end, statements }; | ||
| 381 | } | ||
| 382 | |||
| 383 | function skipPrelude(lines, startIndex) { | ||
| 384 | let index = startIndex; | ||
| 385 | while (index < lines.length) { | ||
| 386 | const afterTrivia = skipBlankAndComments(lines, index); | ||
| 387 | if (afterTrivia == null) { | ||
| 388 | return null; | ||
| 389 | } | ||
| 390 | if (afterTrivia >= lines.length) { | ||
| 391 | return afterTrivia; | ||
| 392 | } | ||
| 393 | |||
| 394 | const trimmed = lines[afterTrivia].text.trim(); | ||
| 395 | if (isDirectiveLine(trimmed)) { | ||
| 396 | index = afterTrivia + 1; | ||
| 397 | continue; | ||
| 398 | } | ||
| 399 | return afterTrivia; | ||
| 400 | } | ||
| 401 | return index; | ||
| 402 | } | ||
| 403 | |||
| 404 | function skipBlankAndComments(lines, startIndex) { | ||
| 405 | let index = startIndex; | ||
| 406 | |||
| 407 | while (index < lines.length) { | ||
| 408 | const trimmed = lines[index].text.trim(); | ||
| 409 | if (trimmed === "") { | ||
| 410 | index += 1; | ||
| 411 | continue; | ||
| 412 | } | ||
| 413 | if (trimmed.startsWith("//")) { | ||
| 414 | index += 1; | ||
| 415 | continue; | ||
| 416 | } | ||
| 417 | if (trimmed.startsWith("/*")) { | ||
| 418 | index = skipBlockComment(lines, index); | ||
| 419 | if (index == null) { | ||
| 420 | return null; | ||
| 421 | } | ||
| 422 | continue; | ||
| 423 | } | ||
| 424 | break; | ||
| 425 | } | ||
| 426 | |||
| 427 | return index; | ||
| 428 | } | ||
| 429 | |||
| 430 | function skipBlockComment(lines, startIndex) { | ||
| 431 | for (let index = startIndex; index < lines.length; index += 1) { | ||
| 432 | if (lines[index].text.includes("*/")) { | ||
| 433 | return index + 1; | ||
| 434 | } | ||
| 435 | } | ||
| 436 | return null; | ||
| 437 | } | ||
| 438 | |||
| 439 | function isDirectiveLine(line) { | ||
| 440 | return /^["'][^"'\\]*(?:\\.[^"'\\]*)*["'];?$/.test(line); | ||
| 441 | } | ||
| 442 | |||
| 443 | function parseImportStatement(lines, startIndex) { | ||
| 444 | if (startIndex >= lines.length) { | ||
| 445 | return null; | ||
| 446 | } | ||
| 447 | |||
| 448 | const firstLine = lines[startIndex].text.trimStart(); | ||
| 449 | if (!firstLine.startsWith("import")) { | ||
| 450 | return null; | ||
| 451 | } | ||
| 452 | if (/^import(?:\s*\(|\s*\.)/.test(firstLine)) { | ||
| 453 | return null; | ||
| 454 | } | ||
| 455 | |||
| 456 | let raw = ""; | ||
| 457 | const state = { | ||
| 458 | braceDepth: 0, | ||
| 459 | inBlockComment: false, | ||
| 460 | stringQuote: null, | ||
| 461 | }; | ||
| 462 | |||
| 463 | for (let index = startIndex; index < lines.length; index += 1) { | ||
| 464 | raw += lines[index].text; | ||
| 465 | scanLine(lines[index].text, state); | ||
| 466 | |||
| 467 | if (state.inBlockComment || state.braceDepth !== 0) { | ||
| 468 | continue; | ||
| 469 | } | ||
| 470 | |||
| 471 | const analysis = parseImportSemantics(raw); | ||
| 472 | if (analysis) { | ||
| 473 | return { | ||
| 474 | nextIndex: index + 1, | ||
| 475 | statement: raw, | ||
| 476 | }; | ||
| 477 | } | ||
| 478 | } | ||
| 479 | |||
| 480 | return null; | ||
| 481 | } | ||
| 482 | |||
| 483 | function scanLine(line, state) { | ||
| 484 | for (let index = 0; index < line.length; index += 1) { | ||
| 485 | const char = line[index]; | ||
| 486 | const next = line[index + 1]; | ||
| 487 | |||
| 488 | if (state.inBlockComment) { | ||
| 489 | if (char === "*" && next === "/") { | ||
| 490 | state.inBlockComment = false; | ||
| 491 | index += 1; | ||
| 492 | } | ||
| 493 | continue; | ||
| 494 | } | ||
| 495 | |||
| 496 | if (state.stringQuote) { | ||
| 497 | if (char === "\\") { | ||
| 498 | index += 1; | ||
| 499 | continue; | ||
| 500 | } | ||
| 501 | if (char === state.stringQuote) { | ||
| 502 | state.stringQuote = null; | ||
| 503 | } | ||
| 504 | continue; | ||
| 505 | } | ||
| 506 | |||
| 507 | if (char === "/" && next === "/") { | ||
| 508 | break; | ||
| 509 | } | ||
| 510 | if (char === "/" && next === "*") { | ||
| 511 | state.inBlockComment = true; | ||
| 512 | index += 1; | ||
| 513 | continue; | ||
| 514 | } | ||
| 515 | if (char === "'" || char === "\"" || char === "`") { | ||
| 516 | state.stringQuote = char; | ||
| 517 | continue; | ||
| 518 | } | ||
| 519 | if (char === "{") { | ||
| 520 | state.braceDepth += 1; | ||
| 521 | } else if (char === "}") { | ||
| 522 | state.braceDepth = Math.max(0, state.braceDepth - 1); | ||
| 523 | } | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 527 | function parseImportSemantics(raw) { | ||
| 528 | const normalized = collapseWhitespace(raw); | ||
| 529 | if (!normalized.startsWith("import ")) { | ||
| 530 | return null; | ||
| 531 | } | ||
| 532 | |||
| 533 | const sideEffectMatch = normalized.match( | ||
| 534 | /^import\s+(["'])(.*?)\1(\s+with\s+\{.*\})?\s*;?$/, | ||
| 535 | ); | ||
| 536 | if (sideEffectMatch) { | ||
| 537 | return { | ||
| 538 | kind: "side-effect", | ||
| 539 | source: sideEffectMatch[2], | ||
| 540 | quote: sideEffectMatch[1], | ||
| 541 | attributes: sideEffectMatch[3] ?? "", | ||
| 542 | key: `side:${sideEffectMatch[2]}:${sideEffectMatch[3] ?? ""}`, | ||
| 543 | }; | ||
| 544 | } | ||
| 545 | |||
| 546 | const bindingMatch = normalized.match( | ||
| 547 | /^import\s+(type\s+)?(.+?)\s+from\s+(["'])(.*?)\3(\s+with\s+\{.*\})?\s*;?$/, | ||
| 548 | ); | ||
| 549 | if (!bindingMatch) { | ||
| 550 | return null; | ||
| 551 | } | ||
| 552 | |||
| 553 | const parsed = parseBindingSpec(bindingMatch[2].trim()); | ||
| 554 | if (!parsed) { | ||
| 555 | return null; | ||
| 556 | } | ||
| 557 | |||
| 558 | const named = parsed.namedSpecifiers.join(","); | ||
| 559 | return { | ||
| 560 | kind: "binding", | ||
| 561 | isTypeImport: !!bindingMatch[1], | ||
| 562 | defaultBinding: parsed.defaultBinding, | ||
| 563 | namespaceImport: parsed.namespaceImport, | ||
| 564 | namedSpecifiers: parsed.namedSpecifiers, | ||
| 565 | source: bindingMatch[4], | ||
| 566 | quote: bindingMatch[3], | ||
| 567 | attributes: bindingMatch[5] ?? "", | ||
| 568 | key: [ | ||
| 569 | "binding", | ||
| 570 | bindingMatch[1] ? "type" : "value", | ||
| 571 | parsed.defaultBinding ?? "", | ||
| 572 | parsed.namespaceImport ?? "", | ||
| 573 | named, | ||
| 574 | bindingMatch[4], | ||
| 575 | bindingMatch[5] ?? "", | ||
| 576 | ].join(":"), | ||
| 577 | }; | ||
| 578 | } | ||
| 579 | |||
| 580 | function parseBindingSpec(specPart) { | ||
| 581 | let defaultBinding = null; | ||
| 582 | let namespaceImport = null; | ||
| 583 | let namedSpecifiers = []; | ||
| 584 | |||
| 585 | const braceStart = specPart.indexOf("{"); | ||
| 586 | const braceEnd = specPart.lastIndexOf("}"); | ||
| 587 | if (braceStart !== -1 || braceEnd !== -1) { | ||
| 588 | if (braceStart === -1 || braceEnd === -1 || braceEnd < braceStart) { | ||
| 589 | return null; | ||
| 590 | } | ||
| 591 | |||
| 592 | const beforeBrace = specPart | ||
| 593 | .slice(0, braceStart) | ||
| 594 | .trim() | ||
| 595 | .replace(/,\s*$/, "") | ||
| 596 | .trim(); | ||
| 597 | const afterBrace = specPart.slice(braceEnd + 1).trim(); | ||
| 598 | if (afterBrace !== "") { | ||
| 599 | return null; | ||
| 600 | } | ||
| 601 | |||
| 602 | const head = parseBindingHead(beforeBrace); | ||
| 603 | if (!head) { | ||
| 604 | return null; | ||
| 605 | } | ||
| 606 | defaultBinding = head.defaultBinding; | ||
| 607 | namespaceImport = head.namespaceImport; | ||
| 608 | namedSpecifiers = splitCommaList(specPart.slice(braceStart + 1, braceEnd)); | ||
| 609 | } else { | ||
| 610 | const head = parseBindingHead(specPart); | ||
| 611 | if (!head) { | ||
| 612 | return null; | ||
| 613 | } | ||
| 614 | defaultBinding = head.defaultBinding; | ||
| 615 | namespaceImport = head.namespaceImport; | ||
| 616 | } | ||
| 617 | |||
| 618 | return { | ||
| 619 | defaultBinding, | ||
| 620 | namespaceImport, | ||
| 621 | namedSpecifiers, | ||
| 622 | }; | ||
| 623 | } | ||
| 624 | |||
| 625 | function parseBindingHead(head) { | ||
| 626 | if (head === "") { | ||
| 627 | return { defaultBinding: null, namespaceImport: null }; | ||
| 628 | } | ||
| 629 | |||
| 630 | if (head.startsWith("* as ")) { | ||
| 631 | return { defaultBinding: null, namespaceImport: head }; | ||
| 632 | } | ||
| 633 | |||
| 634 | const parts = head.split(/\s*,\s*/).filter(Boolean); | ||
| 635 | if (parts.length === 1) { | ||
| 636 | return { | ||
| 637 | defaultBinding: parts[0], | ||
| 638 | namespaceImport: null, | ||
| 639 | }; | ||
| 640 | } | ||
| 641 | |||
| 642 | if (parts.length === 2 && parts[1].startsWith("* as ")) { | ||
| 643 | return { | ||
| 644 | defaultBinding: parts[0], | ||
| 645 | namespaceImport: parts[1], | ||
| 646 | }; | ||
| 647 | } | ||
| 648 | |||
| 649 | return null; | ||
| 650 | } | ||
| 651 | |||
| 652 | function splitCommaList(text) { | ||
| 653 | return text | ||
| 654 | .split(",") | ||
| 655 | .map((part) => collapseWhitespace(part)) | ||
| 656 | .filter(Boolean); | ||
| 657 | } | ||
| 658 | |||
| 659 | function mergeImportStatements(leftStatements, rightStatements, newline) { | ||
| 660 | const merged = []; | ||
| 661 | |||
| 662 | for (const statement of [...leftStatements, ...rightStatements]) { | ||
| 663 | const analysis = parseImportSemantics(statement); | ||
| 664 | if (!analysis) { | ||
| 665 | const key = `raw:${collapseWhitespace(statement)}`; | ||
| 666 | if (!merged.some((entry) => entry.key === key)) { | ||
| 667 | merged.push({ | ||
| 668 | key, | ||
| 669 | raw: ensureTrailingNewline(statement.trim(), newline), | ||
| 670 | analysis: null, | ||
| 671 | }); | ||
| 672 | } | ||
| 673 | continue; | ||
| 674 | } | ||
| 675 | |||
| 676 | const exactIndex = merged.findIndex((entry) => entry.key === analysis.key); | ||
| 677 | if (exactIndex !== -1) { | ||
| 678 | continue; | ||
| 679 | } | ||
| 680 | |||
| 681 | const mergeIndex = merged.findIndex( | ||
| 682 | (entry) => entry.analysis && canMergeImports(entry.analysis, analysis), | ||
| 683 | ); | ||
| 684 | if (mergeIndex !== -1) { | ||
| 685 | const nextAnalysis = mergeImports(merged[mergeIndex].analysis, analysis); | ||
| 686 | merged[mergeIndex] = { | ||
| 687 | key: nextAnalysis.key, | ||
| 688 | raw: renderImport(nextAnalysis, newline), | ||
| 689 | analysis: nextAnalysis, | ||
| 690 | }; | ||
| 691 | continue; | ||
| 692 | } | ||
| 693 | |||
| 694 | merged.push({ | ||
| 695 | key: analysis.key, | ||
| 696 | raw: renderImport(analysis, newline), | ||
| 697 | analysis, | ||
| 698 | }); | ||
| 699 | } | ||
| 700 | |||
| 701 | return merged | ||
| 702 | .map((entry) => entry.raw.trimEnd()) | ||
| 703 | .join(newline) + newline; | ||
| 704 | } | ||
| 705 | |||
| 706 | function canMergeImports(left, right) { | ||
| 707 | if (left.kind !== "binding" || right.kind !== "binding") { | ||
| 708 | return false; | ||
| 709 | } | ||
| 710 | if ( | ||
| 711 | left.source !== right.source || | ||
| 712 | left.attributes !== right.attributes || | ||
| 713 | left.isTypeImport !== right.isTypeImport | ||
| 714 | ) { | ||
| 715 | return false; | ||
| 716 | } | ||
| 717 | |||
| 718 | const defaultBinding = mergeField(left.defaultBinding, right.defaultBinding); | ||
| 719 | const namespaceImport = mergeField(left.namespaceImport, right.namespaceImport); | ||
| 720 | if (defaultBinding === undefined || namespaceImport === undefined) { | ||
| 721 | return false; | ||
| 722 | } | ||
| 723 | |||
| 724 | const namedSpecifiers = unique([...left.namedSpecifiers, ...right.namedSpecifiers]); | ||
| 725 | if (namespaceImport && namedSpecifiers.length > 0) { | ||
| 726 | return false; | ||
| 727 | } | ||
| 728 | |||
| 729 | return true; | ||
| 730 | } | ||
| 731 | |||
| 732 | function mergeImports(left, right) { | ||
| 733 | const defaultBinding = mergeField(left.defaultBinding, right.defaultBinding); | ||
| 734 | const namespaceImport = mergeField(left.namespaceImport, right.namespaceImport); | ||
| 735 | const namedSpecifiers = unique([...left.namedSpecifiers, ...right.namedSpecifiers]); | ||
| 736 | |||
| 737 | return { | ||
| 738 | kind: "binding", | ||
| 739 | isTypeImport: left.isTypeImport, | ||
| 740 | defaultBinding, | ||
| 741 | namespaceImport, | ||
| 742 | namedSpecifiers, | ||
| 743 | source: left.source, | ||
| 744 | quote: left.quote, | ||
| 745 | attributes: left.attributes, | ||
| 746 | key: [ | ||
| 747 | "binding", | ||
| 748 | left.isTypeImport ? "type" : "value", | ||
| 749 | defaultBinding ?? "", | ||
| 750 | namespaceImport ?? "", | ||
| 751 | namedSpecifiers.join(","), | ||
| 752 | left.source, | ||
| 753 | left.attributes, | ||
| 754 | ].join(":"), | ||
| 755 | }; | ||
| 756 | } | ||
| 757 | |||
| 758 | function mergeField(left, right) { | ||
| 759 | if (left == null) return right ?? null; | ||
| 760 | if (right == null) return left; | ||
| 761 | if (left === right) return left; | ||
| 762 | return undefined; | ||
| 763 | } | ||
| 764 | |||
| 765 | function renderImport(analysis, newline) { | ||
| 766 | if (analysis.kind === "side-effect") { | ||
| 767 | return ensureTrailingNewline( | ||
| 768 | `import ${analysis.quote}${analysis.source}${analysis.quote}${analysis.attributes};`, | ||
| 769 | newline, | ||
| 770 | ); | ||
| 771 | } | ||
| 772 | |||
| 773 | const pieces = []; | ||
| 774 | if (analysis.defaultBinding) { | ||
| 775 | pieces.push(analysis.defaultBinding); | ||
| 776 | } | ||
| 777 | if (analysis.namespaceImport) { | ||
| 778 | pieces.push(analysis.namespaceImport); | ||
| 779 | } | ||
| 780 | if (analysis.namedSpecifiers.length > 0) { | ||
| 781 | if (analysis.namedSpecifiers.length === 1) { | ||
| 782 | pieces.push(`{ ${analysis.namedSpecifiers[0]} }`); | ||
| 783 | } else { | ||
| 784 | pieces.push( | ||
| 785 | `{${newline}${analysis.namedSpecifiers.map((spec) => ` ${spec},`).join(newline)}${newline}}`, | ||
| 786 | ); | ||
| 787 | } | ||
| 788 | } | ||
| 789 | |||
| 790 | return ensureTrailingNewline( | ||
| 791 | `import ${analysis.isTypeImport ? "type " : ""}${pieces.join(", ")} from ${analysis.quote}${analysis.source}${analysis.quote}${analysis.attributes};`, | ||
| 792 | newline, | ||
| 793 | ); | ||
| 794 | } | ||
| 795 | |||
| 796 | function projectBoundaryToOriginal(parts, targetA, targetB) { | ||
| 797 | let offsetA = 0; | ||
| 798 | let offsetB = 0; | ||
| 799 | let offsetOrig = 0; | ||
| 800 | |||
| 801 | if (targetA === 0 && targetB === 0) { | ||
| 802 | return 0; | ||
| 803 | } | ||
| 804 | |||
| 805 | for (const part of parts) { | ||
| 806 | if (targetA === offsetA && targetB === offsetB) { | ||
| 807 | return offsetOrig; | ||
| 808 | } | ||
| 809 | |||
| 810 | const nextA = offsetA + part.a.length; | ||
| 811 | const nextB = offsetB + part.b.length; | ||
| 812 | const nextOrig = offsetOrig + part.orig.length; | ||
| 813 | |||
| 814 | if (part.type === "plain") { | ||
| 815 | if ( | ||
| 816 | targetA >= offsetA && | ||
| 817 | targetA <= nextA && | ||
| 818 | targetB >= offsetB && | ||
| 819 | targetB <= nextB | ||
| 820 | ) { | ||
| 821 | const deltaA = targetA - offsetA; | ||
| 822 | const deltaB = targetB - offsetB; | ||
| 823 | if (deltaA !== deltaB) { | ||
| 824 | return null; | ||
| 825 | } | ||
| 826 | return offsetOrig + deltaA; | ||
| 827 | } | ||
| 828 | } | ||
| 829 | |||
| 830 | if (targetA === nextA && targetB === nextB) { | ||
| 831 | return nextOrig; | ||
| 832 | } | ||
| 833 | |||
| 834 | offsetA = nextA; | ||
| 835 | offsetB = nextB; | ||
| 836 | offsetOrig = nextOrig; | ||
| 837 | } | ||
| 838 | |||
| 839 | if (targetA === offsetA && targetB === offsetB) { | ||
| 840 | return offsetOrig; | ||
| 841 | } | ||
| 842 | |||
| 843 | return null; | ||
| 844 | } | ||
| 845 | |||
| 846 | function hasConflictMarkers(text) { | ||
| 847 | return CONFLICT_START_RE.test(text) || CONFLICT_END_RE.test(text); | ||
| 848 | } | ||
| 849 | |||
| 850 | function detectNewline(text) { | ||
| 851 | return text.includes("\r\n") ? "\r\n" : "\n"; | ||
| 852 | } | ||
| 853 | |||
| 854 | function ensureTrailingNewline(text, newline) { | ||
| 855 | return text.endsWith(newline) ? text : `${text}${newline}`; | ||
| 856 | } | ||
| 857 | |||
| 858 | function offsetForLine(lines, lineIndex) { | ||
| 859 | if (lineIndex >= lines.length) { | ||
| 860 | return lines.length === 0 ? 0 : lines[lines.length - 1].end; | ||
| 861 | } | ||
| 862 | return lines[lineIndex].start; | ||
| 863 | } | ||
| 864 | |||
| 865 | function collapseWhitespace(text) { | ||
| 866 | return text.replace(/\s+/g, " ").trim(); | ||
| 867 | } | ||
| 868 | |||
| 869 | function unique(values) { | ||
| 870 | return [...new Set(values)]; | ||
| 871 | } | ||
users/clover/AGENTS.md created+55| ... | @@ -0,0 +1,55 @@ | ||
| 1 | # Clover's Preferences | ||
| 2 | |||
| 3 | You are working with Clover, an intelligent web/systems engineer. Please avoid explaining things to her unless she asks and is curious. Her weak points are around writing good database queries and sometimes overcomplicating things. Make sure that she doesn't pull the session into an overcomplicated solution; not to say it's bad to do large reworks -- refactors can be complex but put the code at a simpler end state. You and her should plan strongly. | ||
| 4 | |||
| 5 | ## Responses | ||
| 6 | |||
| 7 | - When writing output responses, please keep responses very dense. Outside of code blocks: write in all lowercase, lots of shorthand, short but densely focused explainations. Razor sharp accuracy. It's okay to mimic how Clover speaks to you. | ||
| 8 | - Terminology: something is "cooked" = bad, someone is "cooking" = good, gotta "lock in" = gotta focus, "locked in" = good, solid, or high quality, high/low "aura" = quality, "wtf" = what the fuck, "bait used to be believable" = when you find something surprising or wrong, that's "based" = good, "this is peak" / "peak design" / etc = good, "blow up" = remove / crash / errored, "nuke" = remove a large thing, "goated" = greatest of all time, "my live reaction" = my opinion, "disaster" = mistake, "holy shit" = surprise, "strat" = strategy, "propaganda" = fake information, "it's over" or "it's so over" = bad, "we're so back" = it's better now, "that's wraps" = it's even more over, "chat" = twitch live chat not chatgpt. | ||
| 9 | - Use the above terminologies ALL of the time, it's incredible. | ||
| 10 | - Verbiage that models tend to use; "[checking ground truth] instead of just guessing" -> "to make sure i'm not trolling" | ||
| 11 | - We have fun in our process, but write to the codebase professionally. | ||
| 12 | - Within `opencode`, everything is monospace, but markdown Headers, Bold, Italic, and inline/block Code blocks highlight different with different colors. Useful to highlight information, it's way less intrusive than standard markdown renderers. | ||
| 13 | - Never include a section on checks saying "pnpm test passed" or "checks passed" or whatever. Just make sure your validation is strong in the first place; You're expected to have that done. | ||
| 14 | - Feedback should be useful and actionable. A couple words is OK if there is genuinely nothing to add. | ||
| 15 | |||
| 16 | ## Comments | ||
| 17 | |||
| 18 | Comments must always be placed and edited with intent to serialize codebase theory for future versions of ourselves and coworkers. They are professionally written, containing relevant context to the current iteration. Clover's comments are always timeless and rarely written from the first person (no `I`, `we`) -- refer directly to the code. Rewording comments this way helps solidify your own understanding, and can even reveal logic bugs in the designed systems. Prefer documentation comments over line comments. | ||
| 19 | |||
| 20 | ## Code Quality | ||
| 21 | |||
| 22 | - Prefer fewer moving parts. Do not introduce helpers, methods, state fields, abstractions, or types unless they remove real duplication or encode a meaningful invariant. If a helper has one caller, inline it unless it makes a tricky operation substantially safer. | ||
| 23 | - Actively look for dead code and fake state while editing. If a value is always derived from another source, expose it as derived behavior instead of storing it. If a field is only written for type appeasement and never read, nuke it. | ||
| 24 | - Be suspicious of helper functions that hide one line of logic, especially wrappers around another object's property, trivial context builders, and "just in case" extension points. These are low aura unless they prevent a concrete bug. | ||
| 25 | - Before calling a change done, do one cleanup pass asking: can this be fewer names, fewer branches, fewer casts, fewer helper functions, or fewer public API promises? Prefer the smaller locked-in version. | ||
| 26 | - Type safety should constrain real runtime behavior. Avoid casts that paper over a mismatch; if a cast remains, it should be localized and justified by an actual API boundary. | ||
| 27 | - It is okay to spend more time to have a more correct response. | ||
| 28 | |||
| 29 | ## Jujutsu | ||
| 30 | |||
| 31 | - My repos use Jujutsu and usually don't have a visible `.git` folder. Never run | ||
| 32 | `git`, and be cautious about jujutsu commands. Here are the standard commands. | ||
| 33 | By default, don't use any other mutating commands. | ||
| 34 | - `jj st` - show current commit | ||
| 35 | - `jj log -r @ -T description --no-graph` show the description for the current commit, which is important for review. consider running `jj st && jj log -r ...` to get both views in a single command. | ||
| 36 | - `jj diff --git`: diff current commit, can take a file/fileset - Drizzle | ||
| 37 | migrations are quite verbose. In alphaXiv (~/dev/1, ~/dev/2, etc), use | ||
| 38 | `jj diff '. ~ shared/db/migrations'` to subtract verbose migrations. | ||
| 39 | - `jj file show <path> -r <revision>` - read a file at a revision, such as | ||
| 40 | `main` or previous commit `@-` or change ID. | ||
| 41 | - `jj log` shows a set of commits | ||
| 42 | - When I say solve all merge conflicts, I mean: | ||
| 43 | 1. `jj st` to list files alongside the conflicts | ||
| 44 | 2. Resolve merge conflicts. Either edit manually or you could use | ||
| 45 | `jj restore <path> --from <revision>` to take one side. | ||
| 46 | 3. `jj st` to confirm no conflicts + project-specific checks | ||
| 47 | 4. You should resolve everything in the stack, check `jj log -r "@::"` to spot | ||
| 48 | future commits in the stack by me, then incrementally edit them with | ||
| 49 | `jj edit <change-id>`. Always resolve bottom ones first, as that may | ||
| 50 | auto-resolve future merge conflicts. | ||
| 51 | - When I say "rebase <change_id>", I mean: | ||
| 52 | 1. `jj log -r '<change_id>'`, observe what kind of commit. | ||
| 53 | 2. `jj git fetch`. If a parent commit was merged as a PR, then the parent will disappear and you'll have to rebase it onto main first or else you'll have incorrect conflicts. | ||
| 54 | 3. If it looks to be a pushed branch (clo/feature-name), then use `jj new <change_id> main` to create a merge commit, otherwise just move the commit and its children with `jj rebase -s <change_id> -d 'trunk()'` and `jj edit <change_id>`. | ||
| 55 | 4. Solve merge conflicts if any arise following "solve all merge conflicts" rules. | ||
users/clover/home.nix+76-14| ... | @@ -12,6 +12,7 @@ in | ... | @@ -12,6 +12,7 @@ in |
| 12 | stateVersion = "23.05"; # Please read the comment before changing. | 12 | stateVersion = "23.05"; # Please read the comment before changing. |
| 13 | sessionPath = [ | 13 | sessionPath = [ |
| 14 | "$HOME/.vite-plus/bin" | 14 | "$HOME/.vite-plus/bin" |
| 15 | "$HOME/.local/bin" | ||
| 15 | ]; | 16 | ]; |
| 16 | packages = | 17 | packages = |
| 17 | with pkgs; | 18 | with pkgs; |
| ... | @@ -23,8 +24,9 @@ in | ... | @@ -23,8 +24,9 @@ in |
| 23 | uv | 24 | uv |
| 24 | nh | 25 | nh |
| 25 | autofmt | 26 | autofmt |
| 27 | pkgs."jj-import-merge" | ||
| 26 | gnupg | 28 | gnupg |
| 27 | nodejs_24 | 29 | just |
| 28 | ]; | 30 | ]; |
| 29 | # packages to install for desktop environments (non-server) | 31 | # packages to install for desktop environments (non-server) |
| 30 | desktop = [ | 32 | desktop = [ |
| ... | @@ -39,6 +41,9 @@ in | ... | @@ -39,6 +41,9 @@ in |
| 39 | in | 41 | in |
| 40 | all ++ (if host.darwin then darwin else linux) ++ (if hostServer then server else desktop); | 42 | all ++ (if host.darwin then darwin else linux) ++ (if hostServer then server else desktop); |
| 41 | }; | 43 | }; |
| 44 | |||
| 45 | xdg.configFile."opencode/AGENTS.md".source = ./AGENTS.md; | ||
| 46 | |||
| 42 | programs = { | 47 | programs = { |
| 43 | # sort-lines:start | 48 | # sort-lines:start |
| 44 | # bat.enable = true; | 49 | # bat.enable = true; |
| ... | @@ -72,13 +77,29 @@ in | ... | @@ -72,13 +77,29 @@ in |
| 72 | jf = "jj git fetch"; | 77 | jf = "jj git fetch"; |
| 73 | e = "jj edit"; | 78 | e = "jj edit"; |
| 74 | d = "jj desc"; | 79 | d = "jj desc"; |
| 75 | bup = "jj b m --from 'heads(::@- & bookmarks())' --to '@'"; | 80 | bup = "jj b m --from 'heads(::@- & bookmarks()) ~ (master | bookmarks(glob:\"deployment*\"))' --to '@'"; |
| 76 | moveself = "jj rebase -r @ -d"; | 81 | moveself = "jj rebase -s @ -d"; |
| 77 | logall = "jj log -r 'all()'"; | 82 | logall = "jj log -r 'all()'"; |
| 78 | cherry = "jj git fetch && jj split -i -p -m 'SPLIT_CHANGES' && jj edit -r 'description(\"SPLIT_CHANGES\")' && jj desc -m '' --edit"; | 83 | cherry = "jj git fetch && jj split -i -p -m 'SPLIT_CHANGES' && jj edit -r 'description(\"SPLIT_CHANGES\")' && jj desc -m '' --edit"; |
| 79 | }; | 84 | }; |
| 80 | initContent = '' | 85 | initContent = '' |
| 81 | export PS1="%{$(tput setaf 207)%}%m%{$(tput setaf 15)%}:%{$(tput setaf 87)%}%~ %{$(tput sgr0)%}$ " | 86 | function _update_prompt_theme() { |
| 87 | local dark=false | ||
| 88 | if [[ "$OSTYPE" == darwin* ]]; then | ||
| 89 | [[ "$(defaults read -g AppleInterfaceStyle 2>/dev/null)" == "Dark" ]] && dark=true | ||
| 90 | else | ||
| 91 | local scheme | ||
| 92 | scheme=$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null) | ||
| 93 | [[ "$scheme" == *"dark"* ]] && dark=true | ||
| 94 | fi | ||
| 95 | if $dark; then | ||
| 96 | PS1="%{$(tput setaf 207)%}%m%{$(tput setaf 15)%}:%{$(tput setaf 87)%}%~ %{$(tput sgr0)%}$ " | ||
| 97 | else | ||
| 98 | PS1="%{$(tput setaf 168)%}%m%{$(tput setaf 238)%}:%{$(tput setaf 33)%}%~ %{$(tput sgr0)%}$ " | ||
| 99 | fi | ||
| 100 | } | ||
| 101 | precmd_functions+=(_update_prompt_theme) | ||
| 102 | _update_prompt_theme | ||
| 82 | 103 | ||
| 83 | function python() { | 104 | function python() { |
| 84 | dirname=$(dirname $1 2>/dev/null) | 105 | dirname=$(dirname $1 2>/dev/null) |
| ... | @@ -99,14 +120,45 @@ in | ... | @@ -99,14 +120,45 @@ in |
| 99 | fi | 120 | fi |
| 100 | } | 121 | } |
| 101 | 122 | ||
| 123 | function wrapWithAutoInstall() { | ||
| 124 | root="$(command jj workspace root)" | ||
| 125 | if [ -n "$root" ]; then | ||
| 126 | if [ -e "$root/bun.lock" ]; then | ||
| 127 | file="$root/bun.lock" | ||
| 128 | cmd="bun i -f" | ||
| 129 | elif [ -e "$root/package-lock.json" ]; then | ||
| 130 | file="$root/package-lock.json" | ||
| 131 | cmd="npm i" | ||
| 132 | elif [ -e "$root/pnpm-lock.yaml" ]; then | ||
| 133 | file="$root/pnpm-lock.yaml" | ||
| 134 | cmd="pnpm i" | ||
| 135 | elif [ -e "$root/Cargo.lock" ]; then | ||
| 136 | file="$root/Cargo.lock" | ||
| 137 | cmd="cargo fetch" | ||
| 138 | fi | ||
| 139 | fi | ||
| 140 | if [ -n "$file" ]; then | ||
| 141 | before="$(sha1sum "$file")" | ||
| 142 | fi | ||
| 143 | command "$@" | ||
| 144 | if [ -n "$file" ] && [ -e "$file" ]; then | ||
| 145 | after="$(sha1sum "$file")" | ||
| 146 | if [ "$after" != "$before" ]; then | ||
| 147 | eval "$cmd" | ||
| 148 | fi | ||
| 149 | fi | ||
| 150 | } | ||
| 151 | |||
| 102 | function jj() { | 152 | function jj() { |
| 103 | if [[ "$*" = "git push"* ]] && [[ "$*" != *"--dry-run"* ]]; then | 153 | if [[ "$*" = "git push"* ]] && [[ "$*" != *"--dry-run"* ]]; then |
| 104 | command jj "$@" --dry-run | 154 | if command jj "$@" --dry-run; then |
| 105 | printf "Press any key to continue... " | 155 | printf "Press any key to continue... " |
| 106 | read -r | 156 | read -r |
| 107 | command jj "$@" | 157 | command jj "$@" |
| 108 | else | 158 | fi |
| 109 | command jj "$@" | 159 | else if [[ "$*" != "undo" ]] || [[ "$*" != "redo" ]] |
| 160 | # auto-install deps when switching commits | ||
| 161 | wrapWithAutoInstall jj "$@" | ||
| 110 | fi | 162 | fi |
| 111 | } | 163 | } |
| 112 | ''; | 164 | ''; |
| ... | @@ -114,6 +166,7 @@ in | ... | @@ -114,6 +166,7 @@ in |
| 114 | 166 | ||
| 115 | git = { | 167 | git = { |
| 116 | # use a git-specific email | 168 | # use a git-specific email |
| 169 | ignores = [ "AGENTS.md" ]; | ||
| 117 | settings.user.email = "git@paperclover.net"; | 170 | settings.user.email = "git@paperclover.net"; |
| 118 | settings.gpg.ssh.allowedSignersFile = "${pkgs.writeText "allowed_signers" '' | 171 | settings.gpg.ssh.allowedSignersFile = "${pkgs.writeText "allowed_signers" '' |
| 119 | git@paperclover.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAVa1aq1LEj/VXHJYlR5HJpK65lS/9jOL/dAB9ZS1chd | 172 | git@paperclover.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAVa1aq1LEj/VXHJYlR5HJpK65lS/9jOL/dAB9ZS1chd |
| ... | @@ -142,15 +195,19 @@ in | ... | @@ -142,15 +195,19 @@ in |
| 142 | "working_copy remote_bookmarks" = colors."working_copy bookmark"; | 195 | "working_copy remote_bookmarks" = colors."working_copy bookmark"; |
| 143 | 196 | ||
| 144 | "author" = { | 197 | "author" = { |
| 145 | fg = "white"; | 198 | fg = "bright black"; |
| 146 | bold = false; | 199 | bold = false; |
| 147 | italic = true; | 200 | italic = true; |
| 148 | }; | 201 | }; |
| 149 | "working_copy author" = colors.author; | 202 | "working_copy author" = colors.author; |
| 203 | committer = { | ||
| 204 | fg = "bright black"; | ||
| 205 | bold = false; | ||
| 206 | }; | ||
| 150 | 207 | ||
| 151 | desc = { | 208 | desc = { |
| 152 | bold = false; | 209 | bold = true; |
| 153 | fg = "bright white"; | 210 | fg = "default"; |
| 154 | }; | 211 | }; |
| 155 | description = colors.desc; | 212 | description = colors.desc; |
| 156 | cc_wip = { | 213 | cc_wip = { |
| ... | @@ -160,7 +217,7 @@ in | ... | @@ -160,7 +217,7 @@ in |
| 160 | }; | 217 | }; |
| 161 | cc_header = { | 218 | cc_header = { |
| 162 | bold = true; | 219 | bold = true; |
| 163 | fg = "bright white"; | 220 | fg = "default"; |
| 164 | }; | 221 | }; |
| 165 | cc_feat = "green"; | 222 | cc_feat = "green"; |
| 166 | cc_fix = "red"; | 223 | cc_fix = "red"; |
| ... | @@ -179,6 +236,10 @@ in | ... | @@ -179,6 +236,10 @@ in |
| 179 | bold = true; | 236 | bold = true; |
| 180 | reverse = true; | 237 | reverse = true; |
| 181 | }; | 238 | }; |
| 239 | "change_id shortest rest" = { | ||
| 240 | fg = "bright black"; | ||
| 241 | bold = true; | ||
| 242 | }; | ||
| 182 | conflict_marker = { | 243 | conflict_marker = { |
| 183 | fg = "bright red"; | 244 | fg = "bright red"; |
| 184 | bold = true; | 245 | bold = true; |
| ... | @@ -186,6 +247,7 @@ in | ... | @@ -186,6 +247,7 @@ in |
| 186 | }; | 247 | }; |
| 187 | 248 | ||
| 188 | timestamp = "bright black"; | 249 | timestamp = "bright black"; |
| 250 | "committer timestamp" = colors.committer; | ||
| 189 | commit_id = { | 251 | commit_id = { |
| 190 | fg = "bright black"; | 252 | fg = "bright black"; |
| 191 | bold = false; | 253 | bold = false; |
users/clover/paperback/home.nix+1-12| ... | @@ -1,23 +1,12 @@ | ... | @@ -1,23 +1,12 @@ |
| 1 | { pkgs, ... }: | 1 | { pkgs, ... }: |
| 2 | { | 2 | { |
| 3 | # these programs are not globally installed to reduce distractions. | ||
| 4 | # most of these are needed for my work environment. | ||
| 5 | programs = { | ||
| 6 | zsh.profileExtra = '' | ||
| 7 | _bun() { | ||
| 8 | local context state line | ||
| 9 | _arguments -C '*:file:_files' | ||
| 10 | } | ||
| 11 | ''; | ||
| 12 | }; | ||
| 13 | home.packages = with pkgs; [ | 3 | home.packages = with pkgs; [ |
| 14 | deno | ||
| 15 | rustup | 4 | rustup |
| 16 | typescript | 5 | typescript |
| 17 | pm2 | ||
| 18 | pnpm | 6 | pnpm |
| 19 | yt-dlp | 7 | yt-dlp |
| 20 | spotdl | 8 | spotdl |
| 21 | zig | 9 | zig |
| 10 | nodejs_24 | ||
| 22 | ]; | 11 | ]; |
| 23 | } | 12 | } |
users/clover/sandwich/home.nix created+7| ... | @@ -0,0 +1,7 @@ | ||
| 1 | { pkgs, ... }: | ||
| 2 | { | ||
| 3 | |||
| 4 | home.packages = with pkgs; [ | ||
| 5 | nodejs_24 | ||
| 6 | ]; | ||
| 7 | } | ||
users/clover/switch-admin.sh created+26| ... | @@ -0,0 +1,26 @@ | ||
| 1 | #!/bin/sh | ||
| 2 | set -eu | ||
| 3 | |||
| 4 | cd /Users/clo/config | ||
| 5 | |||
| 6 | target_host="${1:-}" | ||
| 7 | if [ -z "$target_host" ]; then | ||
| 8 | if [ -f .hostname ]; then | ||
| 9 | target_host="$(cat .hostname)" | ||
| 10 | else | ||
| 11 | target_host="$(hostname)" | ||
| 12 | fi | ||
| 13 | fi | ||
| 14 | |||
| 15 | case "$target_host" in | ||
| 16 | '' | *[!A-Za-z0-9._-]*) | ||
| 17 | printf 'invalid host: %s\n' "$target_host" >&2 | ||
| 18 | exit 2 | ||
| 19 | ;; | ||
| 20 | esac | ||
| 21 | |||
| 22 | if [ "$(uname -s)" != Darwin ]; then | ||
| 23 | exec ./switch "$target_host" | ||
| 24 | fi | ||
| 25 | |||
| 26 | exec osascript -e "do shell script \"cd /Users/clo/config && /nix/var/nix/profiles/default/bin/nix run .#darwin-rebuild -- switch --flake .#$target_host\" with administrator privileges" | ||
users/clover/work/home.nix+10-6| ... | @@ -8,20 +8,24 @@ | ... | @@ -8,20 +8,24 @@ |
| 8 | } | 8 | } |
| 9 | ''; | 9 | ''; |
| 10 | }; | 10 | }; |
| 11 | programs.git.signing.key = "CE485807AE4068AF8508C8DC5CBB21841C7920AD"; | ||
| 12 | home.packages = with pkgs; [ | 11 | home.packages = with pkgs; [ |
| 13 | (bun.overrideAttrs (_: rec { | 12 | (bun.overrideAttrs (_: rec { |
| 14 | version = "1.3.0"; | 13 | version = "1.3.5"; |
| 15 | src = fetchurl { | 14 | src = fetchurl { |
| 16 | url = "https://github.com/oven-sh/bun/releases/download/bun-v${version}/bun-darwin-aarch64.zip"; | 15 | url = "https://github.com/oven-sh/bun/releases/download/bun-v${version}/bun-darwin-aarch64.zip"; |
| 17 | hash = "sha256-hYSOP5ZIHvyr51pQD9O5S5u5VoaretCjiSl2x74VA2o="; | 16 | hash = "sha256-2xdYikrqiASFaCXUvq0/BeHzcnbKYG8342m09y810/s="; |
| 18 | }; | 17 | }; |
| 19 | })) | 18 | })) |
| 20 | doppler | 19 | awscli |
| 21 | deno | 20 | deno |
| 21 | doppler | ||
| 22 | nodejs_25 | ||
| 22 | rustup | 23 | rustup |
| 23 | typescript | ||
| 24 | ssm-session-manager-plugin | 24 | ssm-session-manager-plugin |
| 25 | awscli | 25 | typescript |
| 26 | zig | ||
| 27 | ]; | ||
| 28 | home.sessionPath = [ | ||
| 29 | "$HOME/.opencode/bin" | ||
| 26 | ]; | 30 | ]; |
| 27 | } | 31 | } |
users/natalie/home.nix+11-3| ... | @@ -17,12 +17,20 @@ | ... | @@ -17,12 +17,20 @@ |
| 17 | fish.enable = true; | 17 | fish.enable = true; |
| 18 | man.generateCaches = false; | 18 | man.generateCaches = false; |
| 19 | lsd.enable = true; | 19 | lsd.enable = true; |
| 20 | android-sdk.enable = true; | 20 | android-sdk.enable = false; |
| 21 | # sort-lines:end | ||
| 22 | ghostty = { | 21 | ghostty = { |
| 23 | enable = true; | 22 | enable = true; |
| 24 | settings.theme = "Catppuccin Mocha"; | 23 | package = null; |
| 24 | settings = { | ||
| 25 | theme = "light:catppuccin-latte,dark:catppuccin-macchiato"; | ||
| 26 | font-family = "AT Name Mono"; | ||
| 27 | adjust-cursor-thickness = 1; | ||
| 28 | minimum-contrast = 1.1; | ||
| 29 | background-opacity = 0.9; | ||
| 30 | background-blur = true; | ||
| 31 | }; | ||
| 25 | }; | 32 | }; |
| 33 | # sort-lines:end | ||
| 26 | 34 | ||
| 27 | fish = { | 35 | fish = { |
| 28 | shellAliases = | 36 | shellAliases = |
users/natalie/home/vicinae.nix+8-2| ... | @@ -1,7 +1,13 @@ | ... | @@ -1,7 +1,13 @@ |
| 1 | { inputs, pkgs, ... }: | 1 | { |
| 2 | inputs, | ||
| 3 | pkgs, | ||
| 4 | lib, | ||
| 5 | host, | ||
| 6 | ... | ||
| 7 | }: | ||
| 2 | { | 8 | { |
| 3 | services.vicinae = { | 9 | services.vicinae = { |
| 4 | enable = true; | 10 | enable = host.linux; |
| 5 | systemd = { | 11 | systemd = { |
| 6 | enable = true; | 12 | enable = true; |
| 7 | autoStart = true; | 13 | autoStart = true; |
users/natalie/laptop/configuration.nix+3-2| ... | @@ -7,7 +7,7 @@ | ... | @@ -7,7 +7,7 @@ |
| 7 | # Custom configuration modules in "modules" are shared between users, | 7 | # Custom configuration modules in "modules" are shared between users, |
| 8 | # and can be configured in this "shared" namespace | 8 | # and can be configured in this "shared" namespace |
| 9 | shared.darwin = { | 9 | shared.darwin = { |
| 10 | macAppStoreApps = [ "wireguard" ]; | 10 | # macAppStoreApps = [ "wireguard" ]; |
| 11 | }; | 11 | }; |
| 12 | 12 | ||
| 13 | system.defaults.NSGlobalDomain."com.apple.trackpad.scaling" = 1.0; | 13 | system.defaults.NSGlobalDomain."com.apple.trackpad.scaling" = 1.0; |
| ... | @@ -40,12 +40,13 @@ | ... | @@ -40,12 +40,13 @@ |
| 40 | casks = [ | 40 | casks = [ |
| 41 | "battle-net" | 41 | "battle-net" |
| 42 | "stremio" | 42 | "stremio" |
| 43 | "alt-tab" | 43 | # "alt-tab" |
| 44 | "legcord" | 44 | "legcord" |
| 45 | "zulip" | 45 | "zulip" |
| 46 | "zen" | 46 | "zen" |
| 47 | "supertuxkart" | 47 | "supertuxkart" |
| 48 | "sf-symbols" | 48 | "sf-symbols" |
| 49 | "todoist" | ||
| 49 | 50 | ||
| 50 | "mediosz/tap/swipeaerospace" | 51 | "mediosz/tap/swipeaerospace" |
| 51 | ]; | 52 | ]; |
users/natalie/laptop/home.nix+3| ... | @@ -8,6 +8,9 @@ | ... | @@ -8,6 +8,9 @@ |
| 8 | ... | 8 | ... |
| 9 | }@args: | 9 | }@args: |
| 10 | { | 10 | { |
| 11 | programs.atuin = { | ||
| 12 | settings.data_dir = "/Users/${user.username}/.local/share/atuin"; | ||
| 13 | }; | ||
| 11 | home = { | 14 | home = { |
| 12 | stateVersion = "23.05"; # Please read the comment before changing. | 15 | stateVersion = "23.05"; # Please read the comment before changing. |
| 13 | # shell = pkgs.fish; | 16 | # shell = pkgs.fish; |
users/natalie/packages.nix-2| ... | @@ -31,8 +31,6 @@ with pkgs; | ... | @@ -31,8 +31,6 @@ with pkgs; |
| 31 | gtk4 | 31 | gtk4 |
| 32 | 32 | ||
| 33 | #ides | 33 | #ides |
| 34 | zed-editor | ||
| 35 | emacs | ||
| 36 | 34 | ||
| 37 | #virtualization | 35 | #virtualization |
| 38 | qemu | 36 | qemu |
users/natalie/vim/languages.nix+2| ... | @@ -106,6 +106,8 @@ | ... | @@ -106,6 +106,8 @@ |
| 106 | scrollDocsDown = null; | 106 | scrollDocsDown = null; |
| 107 | scrollDocsUp = null; | 107 | scrollDocsUp = null; |
| 108 | }; | 108 | }; |
| 109 | sourcePlugins = { | ||
| 110 | }; | ||
| 109 | 111 | ||
| 110 | setupOpts = { | 112 | setupOpts = { |
| 111 | keymap = { | 113 | keymap = { |
users/natalie/vim/latex.nix+3-1| ... | @@ -60,7 +60,9 @@ | ... | @@ -60,7 +60,9 @@ |
| 60 | }; | 60 | }; |
| 61 | setupOpts = { | 61 | setupOpts = { |
| 62 | sources = { | 62 | sources = { |
| 63 | default = [ "vimtex" ]; | 63 | default = [ |
| 64 | "vimtex" | ||
| 65 | ]; | ||
| 64 | providers = { | 66 | providers = { |
| 65 | vimtex = { | 67 | vimtex = { |
| 66 | name = "vimtex"; | 68 | name = "vimtex"; |