diff --git a/crates/language/src/multi_buffer.rs b/crates/language/src/multi_buffer.rs index 7f6b599715f99c9a4ce12417cdff568816384d8f..6e545e3fd25f84c3a72f4008e388231f403721cd 100644 --- a/crates/language/src/multi_buffer.rs +++ b/crates/language/src/multi_buffer.rs @@ -1,21 +1,26 @@ -use crate::buffer::{self, Buffer, Chunk, ToOffset as _, ToPoint as _}; +mod anchor; +mod location; +mod selection; + +use self::location::*; +use crate::{ + buffer::{self, Buffer, Chunk, ToOffset as _, ToPoint as _}, + BufferSnapshot, +}; use collections::HashMap; use gpui::{AppContext, Entity, ModelContext, ModelHandle}; use parking_lot::Mutex; -use smallvec::{smallvec, SmallVec}; -use std::{cmp, iter, ops::Range}; +use std::{cmp, ops::Range}; use sum_tree::{Bias, Cursor, SumTree}; use text::{ rope::TextDimension, subscription::{Subscription, Topic}, - Anchor, AnchorRangeExt, Edit, Point, PointUtf16, TextSummary, + AnchorRangeExt, Edit, Point, PointUtf16, TextSummary, }; use theme::SyntaxTheme; const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize]; -pub type ExcerptId = Location; - #[derive(Default)] pub struct MultiBuffer { snapshot: Mutex, @@ -53,7 +58,7 @@ pub struct ExcerptProperties<'a, T> { struct Excerpt { id: ExcerptId, buffer: buffer::BufferSnapshot, - range: Range, + range: Range, text_summary: TextSummary, header_height: u8, } @@ -64,9 +69,6 @@ struct ExcerptSummary { text: TextSummary, } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Location(SmallVec<[u8; 4]>); - pub struct Chunks<'a> { range: Range, cursor: Cursor<'a, Excerpt, usize>, @@ -531,13 +533,41 @@ impl MultiBufferSnapshot { summary } + + fn resolve_excerpt<'a, D: TextDimension>( + &'a self, + excerpt_id: &ExcerptId, + ) -> Option<(D, &'a BufferSnapshot)> { + let mut cursor = self.excerpts.cursor::<(ExcerptId, TextSummary)>(); + cursor.seek(excerpt_id, Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if cursor.start().0 == *excerpt_id { + return Some((D::from_text_summary(&cursor.start().1), &excerpt.buffer)); + } + } + None + } + + fn buffer_snapshot_for_excerpt<'a>( + &'a self, + excerpt_id: &ExcerptId, + ) -> Option<&'a BufferSnapshot> { + let mut cursor = self.excerpts.cursor::(); + cursor.seek(excerpt_id, Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if cursor.start() == excerpt_id { + return Some(&excerpt.buffer); + } + } + None + } } impl Excerpt { fn new( id: ExcerptId, buffer: buffer::BufferSnapshot, - range: Range, + range: Range, header_height: u8, ) -> Self { let mut text_summary = @@ -564,6 +594,18 @@ impl Excerpt { header_height, } } + + fn header_summary(&self) -> TextSummary { + TextSummary { + bytes: self.header_height as usize, + lines: Point::new(self.header_height as u32, 0), + lines_utf16: PointUtf16::new(self.header_height as u32, 0), + first_line_chars: 0, + last_line_chars: 0, + longest_row: 0, + longest_row_chars: 0, + } + } } impl sum_tree::Item for Excerpt { @@ -599,6 +641,18 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize { } } +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize { + fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.text.bytes) + } +} + +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Location { + fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.excerpt_id) + } +} + impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point { fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { *self += summary.text.lines; @@ -703,43 +757,13 @@ impl ToPoint for Point { } } -impl Default for Location { - fn default() -> Self { - Self::min() - } -} - -impl Location { - pub fn min() -> Self { - Self(smallvec![u8::MIN]) - } - - pub fn max() -> Self { - Self(smallvec![u8::MAX]) - } - - pub fn between(lhs: &Self, rhs: &Self) -> Self { - let lhs = lhs.0.iter().copied().chain(iter::repeat(u8::MIN)); - let rhs = rhs.0.iter().copied().chain(iter::repeat(u8::MAX)); - let mut location = SmallVec::new(); - for (lhs, rhs) in lhs.zip(rhs) { - let mid = lhs + (rhs.saturating_sub(lhs)) / 2; - location.push(mid); - if mid > lhs { - break; - } - } - Self(location) - } -} - #[cfg(test)] mod tests { use super::*; use crate::buffer::Buffer; use gpui::MutableAppContext; use rand::prelude::*; - use std::{env, mem}; + use std::env; use text::{Point, RandomCharIter}; use util::test::sample_text; @@ -1094,36 +1118,4 @@ mod tests { assert_eq!(text.to_string(), snapshot.text()); } } - - #[gpui::test(iterations = 100)] - fn test_location(mut rng: StdRng) { - let mut lhs = Default::default(); - let mut rhs = Default::default(); - while lhs == rhs { - lhs = Location( - (0..rng.gen_range(1..=5)) - .map(|_| rng.gen_range(0..=100)) - .collect(), - ); - rhs = Location( - (0..rng.gen_range(1..=5)) - .map(|_| rng.gen_range(0..=100)) - .collect(), - ); - } - - if lhs > rhs { - mem::swap(&mut lhs, &mut rhs); - } - - let middle = Location::between(&lhs, &rhs); - assert!(middle > lhs); - assert!(middle < rhs); - for ix in 0..middle.0.len() - 1 { - assert!( - middle.0[ix] == *lhs.0.get(ix).unwrap_or(&0) - || middle.0[ix] == *rhs.0.get(ix).unwrap_or(&0) - ); - } - } } diff --git a/crates/language/src/multi_buffer/anchor.rs b/crates/language/src/multi_buffer/anchor.rs new file mode 100644 index 0000000000000000000000000000000000000000..e6b78eeefebd868d8774d6e144b3b7f8ce9fa0c5 --- /dev/null +++ b/crates/language/src/multi_buffer/anchor.rs @@ -0,0 +1,280 @@ +use super::{location::*, ExcerptSummary, MultiBufferSnapshot, ToOffset}; +use anyhow::{anyhow, Result}; +use smallvec::SmallVec; +use std::{cmp::Ordering, ops::Range}; +use sum_tree::Bias; +use text::{rope::TextDimension, AnchorRangeExt, ToOffset as _}; + +#[derive(Clone, Eq, PartialEq, Debug, Hash)] +pub struct Anchor { + excerpt_id: ExcerptId, + text_anchor: text::Anchor, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AnchorRangeMap { + entries: SmallVec<[(ExcerptId, text::AnchorRangeMap); 1]>, +} + +impl Anchor { + pub fn min() -> Self { + Self { + excerpt_id: ExcerptId::min(), + text_anchor: text::Anchor::min(), + } + } + + pub fn max() -> Self { + Self { + excerpt_id: ExcerptId::max(), + text_anchor: text::Anchor::max(), + } + } + + pub fn cmp<'a>(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Result { + let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id); + if excerpt_id_cmp.is_eq() { + self.text_anchor.cmp( + &other.text_anchor, + snapshot + .buffer_snapshot_for_excerpt(&self.excerpt_id) + .ok_or_else(|| anyhow!("excerpt {:?} not found", self.excerpt_id))?, + ) + } else { + return Ok(excerpt_id_cmp); + } + } + + pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor { + if self.text_anchor.bias != Bias::Left { + if let Some(buffer_snapshot) = snapshot.buffer_snapshot_for_excerpt(&self.excerpt_id) { + return Self { + excerpt_id: self.excerpt_id.clone(), + text_anchor: self.text_anchor.bias_left(buffer_snapshot), + }; + } + } + self.clone() + } + + pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor { + if self.text_anchor.bias != Bias::Right { + if let Some(buffer_snapshot) = snapshot.buffer_snapshot_for_excerpt(&self.excerpt_id) { + return Self { + excerpt_id: self.excerpt_id.clone(), + text_anchor: self.text_anchor.bias_right(buffer_snapshot), + }; + } + } + self.clone() + } +} + +impl AnchorRangeMap { + pub fn len(&self) -> usize { + self.entries + .iter() + .map(|(_, text_map)| text_map.len()) + .sum() + } + + pub fn ranges<'a, D>( + &'a self, + snapshot: &'a MultiBufferSnapshot, + ) -> impl Iterator, &'a T)> + 'a + where + D: TextDimension + Clone, + { + let mut cursor = snapshot.excerpts.cursor::(); + self.entries + .iter() + .filter_map(move |(excerpt_id, text_map)| { + cursor.seek_forward(excerpt_id, Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.id == *excerpt_id { + let mut excerpt_start = D::from_text_summary(&cursor.start().text); + excerpt_start.add_summary(&excerpt.header_summary(), &()); + return Some(text_map.ranges::(&excerpt.buffer).map( + move |(range, value)| { + let mut full_range = excerpt_start.clone()..excerpt_start.clone(); + full_range.start.add_assign(&range.start); + full_range.end.add_assign(&range.end); + (full_range, value) + }, + )); + } + } + None + }) + .flatten() + } + + pub fn intersecting_ranges<'a, D, I>( + &'a self, + range: Range<(I, Bias)>, + snapshot: &'a MultiBufferSnapshot, + ) -> impl Iterator, &'a T)> + 'a + where + D: TextDimension, + I: ToOffset, + { + let start_bias = range.start.1; + let end_bias = range.end.1; + let start_offset = range.start.0.to_offset(snapshot); + let end_offset = range.end.0.to_offset(snapshot); + + let mut cursor = snapshot.excerpts.cursor::(); + cursor.seek(&start_offset, start_bias, &()); + let start_excerpt_id = &cursor.start().excerpt_id; + let start_ix = match self + .entries + .binary_search_by_key(&start_excerpt_id, |e| &e.0) + { + Ok(ix) | Err(ix) => ix, + }; + + let mut entry_ranges = None; + let mut entries = self.entries[start_ix..].iter(); + std::iter::from_fn(move || loop { + match &mut entry_ranges { + None => { + let (excerpt_id, text_map) = entries.next()?; + cursor.seek(excerpt_id, Bias::Left, &()); + if cursor.start().text.bytes >= end_offset { + return None; + } + + if let Some(excerpt) = cursor.item() { + if excerpt.id == *excerpt_id { + let mut excerpt_start = D::from_text_summary(&cursor.start().text); + excerpt_start.add_summary(&excerpt.header_summary(), &()); + + let excerpt_start_offset = cursor.start().text.bytes; + let excerpt_end_offset = cursor.end(&()).text.bytes; + let excerpt_buffer_range = excerpt.range.to_offset(&excerpt.buffer); + + let start; + if start_offset >= excerpt_start_offset { + start = ( + excerpt_buffer_range.start + start_offset + - excerpt_start_offset, + start_bias, + ); + } else { + start = (excerpt_buffer_range.start, Bias::Left); + } + + let end; + if end_offset <= excerpt_end_offset { + end = ( + excerpt_buffer_range.start + end_offset - excerpt_start_offset, + end_bias, + ); + } else { + end = (excerpt_buffer_range.end, Bias::Right); + } + + entry_ranges = Some( + text_map + .intersecting_ranges(start..end, &excerpt.buffer) + .map(move |(range, value)| { + let mut full_range = + excerpt_start.clone()..excerpt_start.clone(); + full_range.start.add_assign(&range.start); + full_range.end.add_assign(&range.end); + (full_range, value) + }), + ); + } + } + } + Some(ranges) => { + if let Some(item) = ranges.next() { + return Some(item); + } else { + entry_ranges.take(); + } + } + } + }) + } + + pub fn min_by_key<'a, D, F, K>( + &self, + snapshot: &'a MultiBufferSnapshot, + extract_key: F, + ) -> Option<(Range, &T)> + where + D: TextDimension, + F: FnMut(&T) -> K, + K: Ord, + { + self.min_or_max_by_key(snapshot, Ordering::Less, extract_key) + } + + pub fn max_by_key<'a, D, F, K>( + &self, + snapshot: &'a MultiBufferSnapshot, + extract_key: F, + ) -> Option<(Range, &T)> + where + D: TextDimension, + F: FnMut(&T) -> K, + K: Ord, + { + self.min_or_max_by_key(snapshot, Ordering::Greater, extract_key) + } + + fn min_or_max_by_key<'a, D, F, K>( + &self, + snapshot: &'a MultiBufferSnapshot, + target_ordering: Ordering, + mut extract_key: F, + ) -> Option<(Range, &T)> + where + D: TextDimension, + F: FnMut(&T) -> K, + K: Ord, + { + let mut cursor = snapshot.excerpts.cursor::(); + let mut max = None; + for (excerpt_id, text_map) in &self.entries { + cursor.seek(excerpt_id, Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.id == *excerpt_id { + if let Some((range, value)) = + text_map.max_by_key(&excerpt.buffer, &mut extract_key) + { + if max.as_ref().map_or(true, |(_, max_value)| { + extract_key(value).cmp(&extract_key(*max_value)) == target_ordering + }) { + let mut excerpt_start = D::from_text_summary(&cursor.start().text); + excerpt_start.add_summary(&excerpt.header_summary(), &()); + let mut full_range = excerpt_start.clone()..excerpt_start.clone(); + full_range.start.add_assign(&range.start); + full_range.end.add_assign(&range.end); + max = Some((full_range, value)); + } + } + } + } + } + max + } +} + +impl ToOffset for Anchor { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + let mut cursor = snapshot.excerpts.cursor::(); + cursor.seek(&self.excerpt_id, Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.id == self.excerpt_id { + let buffer_offset = self.text_anchor.to_offset(&excerpt.buffer); + return cursor.start().text.bytes + + excerpt.header_height as usize + + buffer_offset.saturating_sub(excerpt.range.start.to_offset(&excerpt.buffer)); + } + } + cursor.start().text.bytes + } +} diff --git a/crates/language/src/multi_buffer/location.rs b/crates/language/src/multi_buffer/location.rs new file mode 100644 index 0000000000000000000000000000000000000000..a61b2a76301dec9321eb771b3c427a1bc2a1db43 --- /dev/null +++ b/crates/language/src/multi_buffer/location.rs @@ -0,0 +1,76 @@ +use smallvec::{smallvec, SmallVec}; +use std::iter; + +pub type ExcerptId = Location; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Location(SmallVec<[u8; 4]>); + +impl Location { + pub fn min() -> Self { + Self(smallvec![u8::MIN]) + } + + pub fn max() -> Self { + Self(smallvec![u8::MAX]) + } + + pub fn between(lhs: &Self, rhs: &Self) -> Self { + let lhs = lhs.0.iter().copied().chain(iter::repeat(u8::MIN)); + let rhs = rhs.0.iter().copied().chain(iter::repeat(u8::MAX)); + let mut location = SmallVec::new(); + for (lhs, rhs) in lhs.zip(rhs) { + let mid = lhs + (rhs.saturating_sub(lhs)) / 2; + location.push(mid); + if mid > lhs { + break; + } + } + Self(location) + } +} + +impl Default for Location { + fn default() -> Self { + Self::min() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::prelude::*; + use std::mem; + + #[gpui::test(iterations = 100)] + fn test_location(mut rng: StdRng) { + let mut lhs = Default::default(); + let mut rhs = Default::default(); + while lhs == rhs { + lhs = Location( + (0..rng.gen_range(1..=5)) + .map(|_| rng.gen_range(0..=100)) + .collect(), + ); + rhs = Location( + (0..rng.gen_range(1..=5)) + .map(|_| rng.gen_range(0..=100)) + .collect(), + ); + } + + if lhs > rhs { + mem::swap(&mut lhs, &mut rhs); + } + + let middle = Location::between(&lhs, &rhs); + assert!(middle > lhs); + assert!(middle < rhs); + for ix in 0..middle.0.len() - 1 { + assert!( + middle.0[ix] == *lhs.0.get(ix).unwrap_or(&0) + || middle.0[ix] == *rhs.0.get(ix).unwrap_or(&0) + ); + } + } +} diff --git a/crates/language/src/multi_buffer/selection.rs b/crates/language/src/multi_buffer/selection.rs new file mode 100644 index 0000000000000000000000000000000000000000..825b6a27b84629baf9af021829e4c45ea0107e40 --- /dev/null +++ b/crates/language/src/multi_buffer/selection.rs @@ -0,0 +1,91 @@ +use super::{anchor::AnchorRangeMap, MultiBufferSnapshot, ToOffset}; +use std::{ops::Range, sync::Arc}; +use sum_tree::Bias; +use text::{rope::TextDimension, Selection, SelectionSetId, SelectionState}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SelectionSet { + pub id: SelectionSetId, + pub active: bool, + pub selections: Arc>, +} + +impl SelectionSet { + pub fn len(&self) -> usize { + self.selections.len() + } + + pub fn selections<'a, D>( + &'a self, + content: &'a MultiBufferSnapshot, + ) -> impl 'a + Iterator> + where + D: TextDimension, + { + self.selections + .ranges(content) + .map(|(range, state)| Selection { + id: state.id, + start: range.start, + end: range.end, + reversed: state.reversed, + goal: state.goal, + }) + } + + pub fn intersecting_selections<'a, D, I>( + &'a self, + range: Range<(I, Bias)>, + content: &'a MultiBufferSnapshot, + ) -> impl 'a + Iterator> + where + D: TextDimension, + I: 'a + ToOffset, + { + self.selections + .intersecting_ranges(range, content) + .map(|(range, state)| Selection { + id: state.id, + start: range.start, + end: range.end, + reversed: state.reversed, + goal: state.goal, + }) + } + + pub fn oldest_selection<'a, D>( + &'a self, + content: &'a MultiBufferSnapshot, + ) -> Option> + where + D: TextDimension, + { + self.selections + .min_by_key(content, |selection| selection.id) + .map(|(range, state)| Selection { + id: state.id, + start: range.start, + end: range.end, + reversed: state.reversed, + goal: state.goal, + }) + } + + pub fn newest_selection<'a, D>( + &'a self, + content: &'a MultiBufferSnapshot, + ) -> Option> + where + D: TextDimension, + { + self.selections + .max_by_key(content, |selection| selection.id) + .map(|(range, state)| Selection { + id: state.id, + start: range.start, + end: range.end, + reversed: state.reversed, + goal: state.goal, + }) + } +} diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index 5653124c68f4ec6f7207c87398b765d3dbeb51ca..6f38593a78f20c07d25c3c3f33ef051c8cd5b686 100644 --- a/crates/text/src/anchor.rs +++ b/crates/text/src/anchor.rs @@ -1,6 +1,5 @@ +use super::{FromAnchor, FullOffset, Point, ToOffset}; use crate::{rope::TextDimension, BufferSnapshot}; - -use super::{Buffer, FromAnchor, FullOffset, Point, ToOffset}; use anyhow::Result; use std::{ cmp::Ordering, @@ -99,7 +98,7 @@ impl Anchor { Ok(offset_comparison.then_with(|| self.bias.cmp(&other.bias))) } - pub fn bias_left(&self, buffer: &Buffer) -> Anchor { + pub fn bias_left(&self, buffer: &BufferSnapshot) -> Anchor { if self.bias == Bias::Left { self.clone() } else { @@ -107,7 +106,7 @@ impl Anchor { } } - pub fn bias_right(&self, buffer: &Buffer) -> Anchor { + pub fn bias_right(&self, buffer: &BufferSnapshot) -> Anchor { if self.bias == Bias::Right { self.clone() } else { diff --git a/crates/text/src/rope.rs b/crates/text/src/rope.rs index ffc1b74c55de7f6c5bd631d4c4258382fa6bc262..8b0965847542f1d30359872728d9e2779f0cb4a0 100644 --- a/crates/text/src/rope.rs +++ b/crates/text/src/rope.rs @@ -685,6 +685,15 @@ impl sum_tree::Summary for TextSummary { } } +impl<'a> std::ops::Add for TextSummary { + type Output = Self; + + fn add(mut self, rhs: Self) -> Self::Output { + self.add_assign(&rhs); + self + } +} + impl<'a> std::ops::AddAssign<&'a Self> for TextSummary { fn add_assign(&mut self, other: &'a Self) { let joined_chars = self.last_line_chars + other.first_line_chars;