Move diff rendering to ruff_db (#20006)

Summary
--

This is a preparatory PR in support of #19919. It moves our `Diff`
rendering code from `ruff_linter` to `ruff_db`, where we have direct
access to the `DiagnosticStylesheet` used by our other diagnostic
rendering code. As shown by the tests, this shouldn't cause any visible
changes. The colors aren't exactly the same, as I note in a TODO
comment, but I don't think there's any existing way to see those, even
in tests.

The `Diff` implementation is mostly unchanged. I just switched from a
Ruff-specific `SourceFile` to a `DiagnosticSource` (removing an
`expect_ruff_source_file` call) and updated the `LineStyle` struct and
other styling calls to use `fmt_styled` and our existing stylesheet.

In support of these changes, I added three styles to our stylesheet:
`insertion` and `deletion` for the corresponding diff operations, and
`underline`, which apparently we _can_ use, as I hoped on Discord. This
isn't supported in all terminals, though. It worked in ghostty but not
in st for me.

I moved the `calculate_print_width` function from the now-deleted
`diff.rs` to a method on `OneIndexed`, where it was available everywhere
we needed it. I'm not sure if that's desirable, or if my other changes
to the function are either (using `ilog10` instead of a loop). This does
make it `const` and slightly simplifies things in my opinion, but I'm
happy to revert it if preferred.

I also inlined a version of `show_nonprinting` from the
`ShowNonprinting` trait in `ruff_linter`:


f4be05a83b/crates/ruff_linter/src/text_helpers.rs (L3-L5)

This trait is now only used in `source_kind.rs`, so I'm not sure it's
worth having the trait or the macro-generated implementation (which is
only called once). This is obviously closely related to our unprintable
character handling in diagnostic rendering, but the usage seems
different enough not to try to combine them.


f4be05a83b/crates/ruff_db/src/diagnostic/render.rs (L990-L998)

We could also move the trait to another crate where we can use it in
`ruff_db` instead of inlining here, of course.

Finally, this PR makes `TextEmitter` a very thin wrapper around a
`DisplayDiagnosticsConfig`. It's still used in a few places, though,
unlike the other emitters we've replaced, so I figured it was worth
keeping around. It's a pretty nice API for setting all of the options on
the config and then passing that along to a `DisplayDiagnostics`.

Test Plan
--

Existing snapshot tests with diffs
This commit is contained in:
Brent Westbrook 2025-08-21 09:47:00 -04:00 committed by GitHub
parent 14fe1228e7
commit 692be72f5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 266 additions and 230 deletions

View file

@ -622,6 +622,33 @@ impl OneIndexed {
pub fn checked_sub(self, rhs: Self) -> Option<Self> {
self.0.get().checked_sub(rhs.get()).and_then(Self::new)
}
/// Calculate the number of digits in `self`.
///
/// This is primarily intended for computing the length of the string representation for
/// formatted printing.
///
/// # Examples
///
/// ```
/// use ruff_source_file::OneIndexed;
///
/// let one = OneIndexed::new(1).unwrap();
/// assert_eq!(one.digits().get(), 1);
///
/// let hundred = OneIndexed::new(100).unwrap();
/// assert_eq!(hundred.digits().get(), 3);
///
/// let thousand = OneIndexed::new(1000).unwrap();
/// assert_eq!(thousand.digits().get(), 4);
/// ```
pub const fn digits(self) -> NonZeroUsize {
// Safety: the 1+ ensures this is always non-zero, and
// `usize::MAX.ilog10()` << `usize::MAX`, so the result is always safe
// to cast to a usize, even though it's returned as a u32
// (u64::MAX.ilog10() is 19).
NonZeroUsize::new(1 + self.0.get().ilog10() as usize).unwrap()
}
}
impl Default for OneIndexed {