Add debug methods for visually annotating ranges (#38097)

This allows you to write `buffer_snapshot.debug(ranges, value)` and it
will be displayed in the buffer (or multibuffer!) until that callsite
runs again. `ranges` can be any position (`usize`, `Anchor`, etc), any
range, or a slice or vec of those. `value` just needs a `Debug` impl.
These are stored in a mutable global for convenience, and this is only
available in debug builds.

For example, using this to visualize the captures of the brackets
Tree-sitter query:

<img width="1215" height="480" alt="image"
src="https://github.com/user-attachments/assets/c1878fc7-f6b3-4e27-949e-ecf67a7906b9"
/>

Release Notes:

- N/A
This commit is contained in:
Michael Sloan
2025-09-13 03:37:24 +00:00
committed by GitHub
parent ded6467604
commit e43ad858d8
3 changed files with 357 additions and 0 deletions
+111
View File
@@ -6402,6 +6402,45 @@ impl MultiBufferSnapshot {
pub fn diff_for_buffer_id(&self, buffer_id: BufferId) -> Option<&BufferDiffSnapshot> {
self.diffs.get(&buffer_id)
}
/// Visually annotates a position or range with the `Debug` representation of a value. The
/// callsite of this function is used as a key - previous annotations will be removed.
#[cfg(debug_assertions)]
#[track_caller]
pub fn debug<V, R>(&self, ranges: &R, value: V)
where
R: debug::ToMultiBufferDebugRanges,
V: std::fmt::Debug,
{
self.debug_with_key(std::panic::Location::caller(), ranges, value);
}
/// Visually annotates a position or range with the `Debug` representation of a value. Previous
/// debug annotations with the same key will be removed. The key is also used to determine the
/// annotation's color.
#[cfg(debug_assertions)]
#[track_caller]
pub fn debug_with_key<K, R, V>(&self, key: &K, ranges: &R, value: V)
where
K: std::hash::Hash + 'static,
R: debug::ToMultiBufferDebugRanges,
V: std::fmt::Debug,
{
let text_ranges = ranges
.to_multi_buffer_debug_ranges(self)
.into_iter()
.flat_map(|range| {
self.range_to_buffer_ranges(range).into_iter().map(
|(buffer, range, _excerpt_id)| {
buffer.anchor_after(range.start)..buffer.anchor_before(range.end)
},
)
})
.collect();
text::debug::GlobalDebugRanges::with_locked(|debug_ranges| {
debug_ranges.insert(key, text_ranges, format!("{value:?}").into())
});
}
}
#[cfg(any(test, feature = "test-support"))]
@@ -7983,3 +8022,75 @@ impl From<ExcerptId> for EntityId {
EntityId::from(id.0 as u64)
}
}
#[cfg(debug_assertions)]
pub mod debug {
use super::*;
pub trait ToMultiBufferDebugRanges {
fn to_multi_buffer_debug_ranges(&self, snapshot: &MultiBufferSnapshot)
-> Vec<Range<usize>>;
}
impl<T: ToOffset> ToMultiBufferDebugRanges for T {
fn to_multi_buffer_debug_ranges(
&self,
snapshot: &MultiBufferSnapshot,
) -> Vec<Range<usize>> {
[self.to_offset(snapshot)].to_multi_buffer_debug_ranges(snapshot)
}
}
impl<T: ToOffset> ToMultiBufferDebugRanges for Range<T> {
fn to_multi_buffer_debug_ranges(
&self,
snapshot: &MultiBufferSnapshot,
) -> Vec<Range<usize>> {
[self.start.to_offset(snapshot)..self.end.to_offset(snapshot)]
.to_multi_buffer_debug_ranges(snapshot)
}
}
impl<T: ToOffset> ToMultiBufferDebugRanges for Vec<T> {
fn to_multi_buffer_debug_ranges(
&self,
snapshot: &MultiBufferSnapshot,
) -> Vec<Range<usize>> {
self.as_slice().to_multi_buffer_debug_ranges(snapshot)
}
}
impl<T: ToOffset> ToMultiBufferDebugRanges for Vec<Range<T>> {
fn to_multi_buffer_debug_ranges(
&self,
snapshot: &MultiBufferSnapshot,
) -> Vec<Range<usize>> {
self.as_slice().to_multi_buffer_debug_ranges(snapshot)
}
}
impl<T: ToOffset> ToMultiBufferDebugRanges for [T] {
fn to_multi_buffer_debug_ranges(
&self,
snapshot: &MultiBufferSnapshot,
) -> Vec<Range<usize>> {
self.iter()
.map(|item| {
let offset = item.to_offset(snapshot);
offset..offset
})
.collect()
}
}
impl<T: ToOffset> ToMultiBufferDebugRanges for [Range<T>] {
fn to_multi_buffer_debug_ranges(
&self,
snapshot: &MultiBufferSnapshot,
) -> Vec<Range<usize>> {
self.iter()
.map(|range| range.start.to_offset(snapshot)..range.end.to_offset(snapshot))
.collect()
}
}
}