Repository navigation
refactor(rust): draw scan progress with indicatif - #1123
Merged
Merged
Conversation
RED for #1081. The hand-rolled reporter pads a shorter line only over the tail of the line it drew before, so anything else sharing the row survives a redraw. Clearing to the full width is what the indicatif swap brings. skip-changelog: red test commit; the behavior change ships with the implementation
GREEN for #1081. The `\r`-rewritten counter, the 100 ms redraw throttle and the drawn-width padding are indicatif's now. What stays ours is the policy indicatif has no opinion about: the `DIRSQL_PROGRESS` modes, the warmup before the first draw, the floored percentage and the summary line. indicatif refuses to draw to a non-terminal and offers no override (indicatif#87), so `Mode::Always` is served by a `TermLike` over the reporter's own sink. It writes spaces rather than erase escapes, keeping a redirected `always` capture free of ANSI, and the summary goes to the sink directly because indicatif's `println` leaves the line unterminated once the bar is cleared. Invariant 1 verified empirically, not from the docs: piped stdout and redirected stderr captured before and after across `auto`, `never`, `always`, an unrecognized value and an unset variable. Every stream is byte-identical but `always`'s stderr, which gains full-width padding.
Dropping a `ProgressBar` already clears its line (indicatif's default `ProgressFinish::AndClear`), so the reporter's own `Drop` was a no-op wrapped around one -- deleted, with the rationale moved onto the field it belongs to. The test double's `flush` now counts instead of discarding.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1081.
Replaces the drawing and throttling internals of
packages/rust/src/progress.rswith
indicatif, per Kevin's 2026-09-17 decision on the issue.The public surface is unchanged:
PROGRESS_ENV, theModeenum and its fourmodes,
Progress::new's 7-arg signature, and theProgress::update/Progress::finishcall sites all stay exactly as they were.DIRSQL_PROGRESSremains the user-facing knob with the same modes.
What moved
REDRAW_INTERVALhand-throttling and the\r+drawn_widtherase dance aregone; a
ProgressBaron aterm_like_with_hz(_, 10)draw target does both.impl Drop for Progressis deleted. indicatif'sBarState::dropalreadyfinishes with
ProgressFinish::AndClear(its#[default]), so the manualerase-on-drop was redundant. Verified in indicatif's
src/state.rs, and themutation gate confirms no test was relying on our copy.
SinkTerm: TermLikekeepsDIRSQL_PROGRESS=alwaysworking whenstderr is a pipe. indicatif has no override for drawing to a non-TTY
(indicatif#87), so we own the terminal abstraction:
clear_line()writesspaces rather than
\x1b[2K, and cursor moves are no-ops at n == 0. Theresult is that the
alwaysbyte stream carries zero ANSI escapes, same asbefore.
finish()writes the summary straight to the sink withwriteln!rather thanProgressBar::println, which does not terminate its line.indicatif = "0.18.6"andconsole = "0.16.6"are declared inpackages/rust/Cargo.toml.consolewas already transitive via indicatif andadds no new crates; it is declared because
line_widthcallsconsole::Termdirectly and the
declared-depsgate requires a real declaration. The moduleheader is updated — it documented a "No new dependencies" decision that this PR
reverses.
Invariant 1: a pipe stays silent — verified empirically
Captured piped stdout and redirected stderr for every mode against a 200-file
fixture, before (origin/main) and after (this branch's HEAD), and compared byte
for byte:
Every non-TTY path that was silent before is still silent, byte for byte,
including the unrecognized-value fallback. The only stream that changed is
alwayson a pipe — the one mode that is supposed to draw there.That stream is still escape-free and still ends in a whole line:
It grew because each redraw now clears the full line width (80 columns on a
pipe) instead of only the characters it previously drew, and because indicatif's
leaky-bucket rate limiter allows a burst before settling to 10 Hz. Percentages
still floor (
19/200 -> 9%). Harness:/tmp/claude/dirsql-1081/capture.sh.Manual exercise
Ran the real CLI under a pty (
script -qec) over a 300-file fixture with a10 ms/file
on-filecommand:autowaits out the ~500 ms warmup, then throttles to ~10 Hz (counter steps183 -> 192 -> 201, about 90 ms per frame), erases itself and leaves
dirsql: indexed 300 files in 3.3sfollowed by the query result.neverdrawsnothing. All three modes behave as documented.
Line delta
The issue estimated a ~200-line net deletion. That does not hold. Measuring
progress.rsabove its#[cfg(test)]module:The
TermLikeimpl we have to own costs more than the throttling and eraselogic it replaces. The 2026-09-09 spike called this direction correctly (+44);
the issue's −200 premise was optimistic. The change is still worth making — the
throttling and redraw correctness now live upstream — but it is not a deletion.
Coverage
No floor moved.
cargo llvm-cov --lib --features cli --branchon nightly, froma clean profile:
Floors in root
testing-conventions.tomlare lines 92 / functions 91 /regions 91 / branch 72. Every dimension clears with margin, so no floor was
re-pinned. (An intermediate run reported progress.rs at ~80%; that was stale
merged profile data and did not reproduce after
cargo llvm-cov clean --workspace.)unit mutation(cargo-mutants, diff-scoped against origin/main): no survivingmutants, 27 tested.
PARITY.md
Not updated, deliberately. No SDK-visible surface moved —
progressis internalto the Rust core and the Python and TypeScript SDKs expose nothing from it.
E2E Verification
packages/python/e2e-attestations/<branch>.jsonreceipt written ifpackages/pythonchanged (just e2e-attest-python)packages/ts/e2e-attestations/<branch>.jsonreceipt written ifpackages/tschanged (just e2e-attest-ts)cargo test --workspace --features cli;just test-e2e;just e2e-attest-python;just e2e-attest-ts;just preflight;just test-packaging;cargo bench -p dirsqlPython e2e 37 passed; TS e2e 16 passed across 5 files;
just preflight0 failing pairs, 3 skipped (
packaging, whichjust test-packagingcoversseparately and which passed); benchmarks show no regression (progress drawing
is not on any benched path). Receipts committed for both bindings — any
packages/rust/srcchange puts both in scope via[e2e].extra_scope.Changelog / Migrations
<root>/<pkg>/changelog.d/for each changed package (or:skip-changelogtrailer on a commit with reason)<root>/<pkg>/migrations.d/(or: not required -- additive/bugfix only)packages/rust/changelog.d/2026-09-17-indicatif-progress.md(Changed).A migration fragment is required: this changes runtime behavior without an
API change.
packages/rust/migrations.d/2026-09-17-indicatif-progress.mdrecords it — required changes
_None._, behavior changes covering thefull-width padding and the extra frames in the
alwaysbyte stream, plus thelist of things verified byte-identical.