diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a9c24a4b3e67de21a72fbe53b4e19fa5c8974d17..603dcdece22053d131441929fe4270371e803463 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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)>, + anchor_range: Range, + 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( diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 5637f9af1a8ec8d3a064b29c4f8a18a6aff074a4..f9d99473fbd196152418376d1bfbf63e21ad8b00 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -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(&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(&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 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>; + } + + impl ToMultiBufferDebugRanges for T { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + [self.to_offset(snapshot)].to_multi_buffer_debug_ranges(snapshot) + } + } + + impl ToMultiBufferDebugRanges for Range { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + [self.start.to_offset(snapshot)..self.end.to_offset(snapshot)] + .to_multi_buffer_debug_ranges(snapshot) + } + } + + impl ToMultiBufferDebugRanges for Vec { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + self.as_slice().to_multi_buffer_debug_ranges(snapshot) + } + } + + impl ToMultiBufferDebugRanges for Vec> { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + self.as_slice().to_multi_buffer_debug_ranges(snapshot) + } + } + + impl ToMultiBufferDebugRanges for [T] { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + self.iter() + .map(|item| { + let offset = item.to_offset(snapshot); + offset..offset + }) + .collect() + } + } + + impl ToMultiBufferDebugRanges for [Range] { + fn to_multi_buffer_debug_ranges( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Vec> { + self.iter() + .map(|range| range.start.to_offset(snapshot)..range.end.to_offset(snapshot)) + .collect() + } + } +} diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 8fb6f56222b503360a3d2dd6f4a6b27d1ac728e3..db282e5c30de562441f5076157a8db4a269aea9d 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -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(&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(&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> = Mutex::new(None); + + pub struct GlobalDebugRanges { + pub ranges: Vec, + key_to_occurrence_index: HashMap, + next_occurrence_index: usize, + } + + pub struct DebugRange { + key: Key, + pub ranges: Vec>, + pub value: Arc, + pub occurrence_index: usize, + } + + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + struct Key { + type_id: TypeId, + hash: u64, + } + + impl GlobalDebugRanges { + pub fn with_locked(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( + &mut self, + key: &K, + ranges: Vec>, + value: Arc, + ) { + 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(&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(&mut self) { + self.ranges + .retain(|item| item.key.type_id != TypeId::of::()); + } + } + + impl Key { + fn new(key: &K) -> Self { + let type_id = TypeId::of::(); + 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>; + } + + impl ToDebugRanges for T { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + [self.to_offset(snapshot)].to_debug_ranges(snapshot) + } + } + + impl ToDebugRanges for Range { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + [self.clone()].to_debug_ranges(snapshot) + } + } + + impl ToDebugRanges for Vec { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + self.as_slice().to_debug_ranges(snapshot) + } + } + + impl ToDebugRanges for Vec> { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + self.as_slice().to_debug_ranges(snapshot) + } + } + + impl ToDebugRanges for [T] { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + self.iter() + .map(|item| { + let offset = item.to_offset(snapshot); + offset..offset + }) + .collect() + } + } + + impl ToDebugRanges for [Range] { + fn to_debug_ranges(&self, snapshot: &BufferSnapshot) -> Vec> { + self.iter() + .map(|range| range.start.to_offset(snapshot)..range.end.to_offset(snapshot)) + .collect() + } + } +}