Detailed changes
@@ -1505,6 +1505,15 @@ impl EditorElement {
selections.push((player, layouts));
}
});
+
+ #[cfg(debug_assertions)]
+ Self::layout_debug_ranges(
+ &mut selections,
+ start_anchor..end_anchor,
+ &snapshot.display_snapshot,
+ cx,
+ );
+
(selections, active_rows, newest_selection_head)
}
@@ -7294,6 +7303,54 @@ impl EditorElement {
unstaged == unstaged_hollow
}
+
+ #[cfg(debug_assertions)]
+ fn layout_debug_ranges(
+ selections: &mut Vec<(PlayerColor, Vec<SelectionLayout>)>,
+ anchor_range: Range<Anchor>,
+ display_snapshot: &DisplaySnapshot,
+ cx: &App,
+ ) {
+ let theme = cx.theme();
+ text::debug::GlobalDebugRanges::with_locked(|debug_ranges| {
+ if debug_ranges.ranges.is_empty() {
+ return;
+ }
+ let buffer_snapshot = &display_snapshot.buffer_snapshot;
+ for (buffer, buffer_range, excerpt_id) in
+ buffer_snapshot.range_to_buffer_ranges(anchor_range)
+ {
+ let buffer_range =
+ buffer.anchor_after(buffer_range.start)..buffer.anchor_before(buffer_range.end);
+ selections.extend(debug_ranges.ranges.iter().flat_map(|debug_range| {
+ let player_color = theme
+ .players()
+ .color_for_participant(debug_range.occurrence_index as u32 + 1);
+ debug_range.ranges.iter().filter_map(move |range| {
+ if range.start.buffer_id != Some(buffer.remote_id()) {
+ return None;
+ }
+ let clipped_start = range.start.max(&buffer_range.start, buffer);
+ let clipped_end = range.end.min(&buffer_range.end, buffer);
+ let range = buffer_snapshot.anchor_in_excerpt(excerpt_id, clipped_start)?
+ ..buffer_snapshot.anchor_in_excerpt(excerpt_id, clipped_end)?;
+ let start = range.start.to_display_point(display_snapshot);
+ let end = range.end.to_display_point(display_snapshot);
+ let selection_layout = SelectionLayout {
+ head: start,
+ range: start..end,
+ cursor_shape: CursorShape::Bar,
+ is_newest: false,
+ is_local: false,
+ active_rows: start.row()..end.row(),
+ user_name: Some(SharedString::new(debug_range.value.clone())),
+ };
+ Some((player_color, vec![selection_layout]))
+ })
+ }));
+ }
+ });
+ }
}
fn header_jump_data(
@@ -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()
+ }
+ }
+}
@@ -2593,6 +2593,38 @@ impl BufferSnapshot {
last_old_end + new_offset.saturating_sub(last_new_end)
})
}
+
+ /// 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<R, V>(&self, ranges: &R, value: V)
+ where
+ R: debug::ToDebugRanges,
+ 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)]
+ pub fn debug_with_key<K, R, V>(&self, key: &K, ranges: &R, value: V)
+ where
+ K: std::hash::Hash + 'static,
+ R: debug::ToDebugRanges,
+ V: std::fmt::Debug,
+ {
+ let ranges = ranges
+ .to_debug_ranges(self)
+ .into_iter()
+ .map(|range| self.anchor_after(range.start)..self.anchor_before(range.end))
+ .collect();
+ debug::GlobalDebugRanges::with_locked(|debug_ranges| {
+ debug_ranges.insert(key, ranges, format!("{value:?}").into());
+ });
+ }
}
struct RopeBuilder<'a> {
@@ -3247,3 +3279,160 @@ impl LineEnding {
}
}
}
+
+#[cfg(debug_assertions)]
+pub mod debug {
+ use super::*;
+ use parking_lot::Mutex;
+ use std::any::TypeId;
+ use std::hash::{Hash, Hasher};
+
+ static GLOBAL_DEBUG_RANGES: Mutex<Option<GlobalDebugRanges>> = Mutex::new(None);
+
+ pub struct GlobalDebugRanges {
+ pub ranges: Vec<DebugRange>,
+ key_to_occurrence_index: HashMap<Key, usize>,
+ next_occurrence_index: usize,
+ }
+
+ pub struct DebugRange {
+ key: Key,
+ pub ranges: Vec<Range<Anchor>>,
+ pub value: Arc<str>,
+ pub occurrence_index: usize,
+ }
+
+ #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+ struct Key {
+ type_id: TypeId,
+ hash: u64,
+ }
+
+ impl GlobalDebugRanges {
+ pub fn with_locked<R>(f: impl FnOnce(&mut Self) -> R) -> R {
+ let mut state = GLOBAL_DEBUG_RANGES.lock();
+ if state.is_none() {
+ *state = Some(GlobalDebugRanges {
+ ranges: Vec::new(),
+ key_to_occurrence_index: HashMap::default(),
+ next_occurrence_index: 0,
+ });
+ }
+ if let Some(global_debug_ranges) = state.as_mut() {
+ f(global_debug_ranges)
+ } else {
+ unreachable!()
+ }
+ }
+
+ pub fn insert<K: Hash + 'static>(
+ &mut self,
+ key: &K,
+ ranges: Vec<Range<Anchor>>,
+ value: Arc<str>,
+ ) {
+ let occurrence_index = *self
+ .key_to_occurrence_index
+ .entry(Key::new(key))
+ .or_insert_with(|| {
+ let occurrence_index = self.next_occurrence_index;
+ self.next_occurrence_index += 1;
+ occurrence_index
+ });
+ let key = Key::new(key);
+ let existing = self
+ .ranges
+ .iter()
+ .enumerate()
+ .rfind(|(_, existing)| existing.key == key);
+ if let Some((existing_ix, _)) = existing {
+ self.ranges.remove(existing_ix);
+ }
+ self.ranges.push(DebugRange {
+ ranges,
+ key,
+ value,
+ occurrence_index,
+ });
+ }
+
+ pub fn remove<K: Hash + 'static>(&mut self, key: &K) {
+ self.remove_impl(&Key::new(key));
+ }
+
+ fn remove_impl(&mut self, key: &Key) {
+ let existing = self
+ .ranges
+ .iter()
+ .enumerate()
+ .rfind(|(_, existing)| &existing.key == key);
+ if let Some((existing_ix, _)) = existing {
+ self.ranges.remove(existing_ix);
+ }
+ }
+
+ pub fn remove_all_with_key_type<K: 'static>(&mut self) {
+ self.ranges
+ .retain(|item| item.key.type_id != TypeId::of::<K>());
+ }
+ }
+
+ impl Key {
+ fn new<K: Hash + 'static>(key: &K) -> Self {
+ let type_id = TypeId::of::<K>();
+ let mut hasher = collections::FxHasher::default();
+ key.hash(&mut hasher);
+ Key {
+ type_id,
+ hash: hasher.finish(),
+ }
+ }
+ }
+
+ pub trait ToDebugRanges {
+ fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec<Range<usize>>;
+ }
+
+ impl<T: ToOffset> ToDebugRanges for T {
+ fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec<Range<usize>> {
+ [self.to_offset(snapshot)].to_debug_ranges(snapshot)
+ }
+ }
+
+ impl<T: ToOffset + Clone> ToDebugRanges for Range<T> {
+ fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec<Range<usize>> {
+ [self.clone()].to_debug_ranges(snapshot)
+ }
+ }
+
+ impl<T: ToOffset> ToDebugRanges for Vec<T> {
+ fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec<Range<usize>> {
+ self.as_slice().to_debug_ranges(snapshot)
+ }
+ }
+
+ impl<T: ToOffset> ToDebugRanges for Vec<Range<T>> {
+ fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec<Range<usize>> {
+ self.as_slice().to_debug_ranges(snapshot)
+ }
+ }
+
+ impl<T: ToOffset> ToDebugRanges for [T] {
+ fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec<Range<usize>> {
+ self.iter()
+ .map(|item| {
+ let offset = item.to_offset(snapshot);
+ offset..offset
+ })
+ .collect()
+ }
+ }
+
+ impl<T: ToOffset> ToDebugRanges for [Range<T>] {
+ fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec<Range<usize>> {
+ self.iter()
+ .map(|range| range.start.to_offset(snapshot)..range.end.to_offset(snapshot))
+ .collect()
+ }
+ }
+}