feat(cli): prioritize uncommitted solutions in picker ordering

This commit is contained in:
Prad Nukala committed 2026-10-14 11:04:13 -04:00
1 parent 0ccd9794f0
commit a7ab989d57
1 file changed
+38 -5
+38 -5
View File
@@ -9,10 +9,12 @@
* *
* Every run backfills the db's solutions table with created_at (from the * Every run backfills the db's solutions table with created_at (from the
* file's birthtime, mtime when the filesystem has none) so ordering works for * file's birthtime, mtime when the filesystem has none) so ordering works for
* files that predate the db. Ordering is per command — test lists the most * files that predate the db. Files git reports as modified, added or
* recently tested first (never-tested after, by name); submit lists the most * untracked come first, newest edit first — the problem being worked on is
* recently created first. Nothing is ever hidden. Exit 0 of leetcode-cli * the focused option, so Enter runs it without a search. The rest are ordered
* stamps last_tested / last_submitted. * per command — test lists the most recently tested first (never-tested
* after, by name); submit lists the most recently created first. Nothing is
* ever hidden. Exit 0 of leetcode-cli stamps last_tested / last_submitted.
* *
* Library: side-effect-free on import — nothing scans the filesystem until * Library: side-effect-free on import — nothing scans the filesystem until
* runOnSolution() is called. * runOnSolution() is called.
@@ -29,6 +31,29 @@ function lcNumber(rel: string): number | undefined {
return m ? Number(m[1]) : undefined; return m ? Number(m[1]) : undefined;
} }
/**
* work/-relative paths of solutions git sees as modified, added or untracked,
* mapped to their mtime. Empty outside a git checkout.
*/
function dirtySolutions(scanned: Set<string>): Map<string, number> {
const git = Bun.spawnSync(
["git", "status", "--porcelain", "-z", "--untracked-files=all", "--", WORK_DIR],
{ cwd: ROOT },
);
const dirty = new Map<string, number>();
if (!git.success) return dirty;
const entries = git.stdout.toString().split("\0");
for (let i = 0; i < entries.length; i++) {
const entry = entries[i]!;
// renames/copies carry their source path as the next entry; skip it
if (/[RC]/.test(entry.slice(0, 2))) i++;
const rel = relative(WORK_DIR, join(ROOT, entry.slice(3)));
// deletions and non-solution files are not in the scan
if (scanned.has(rel)) dirty.set(rel, statSync(join(WORK_DIR, rel)).mtimeMs);
}
return dirty;
}
/** Fuzzy-pick a solution under work/ and run `leetcode <command>` on it. */ /** Fuzzy-pick a solution under work/ and run `leetcode <command>` on it. */
export async function runOnSolution( export async function runOnSolution(
command: "test" | "submit", command: "test" | "submit",
@@ -61,7 +86,15 @@ export async function runOnSolution(
return (command === "test" ? row?.lastTested : row?.createdAt) ?? undefined; return (command === "test" ? row?.lastTested : row?.createdAt) ?? undefined;
}; };
const dirty = dirtySolutions(new Set(scanned));
const files = scanned.sort((a, b) => { const files = scanned.sort((a, b) => {
const ma = dirty.get(a);
const mb = dirty.get(b);
if (ma !== mb) {
if (ma === undefined) return 1;
if (mb === undefined) return -1;
return mb - ma;
}
const ka = sortKey(a); const ka = sortKey(a);
const kb = sortKey(b); const kb = sortKey(b);
if (ka !== kb) { if (ka !== kb) {
@@ -91,7 +124,7 @@ export async function runOnSolution(
value: f, value: f,
label: basename(f), label: basename(f),
hint: parsed hint: parsed
? `${stage} · ${parsed.difficulty} · ${parsed.category}` ? `${dirty.has(f) ? "uncommitted · " : ""}${stage} · ${parsed.difficulty} · ${parsed.category}`
: f, : f,
}; };
}), }),