Detailed changes
@@ -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<MultiBufferSnapshot>,
@@ -53,7 +58,7 @@ pub struct ExcerptProperties<'a, T> {
struct Excerpt {
id: ExcerptId,
buffer: buffer::BufferSnapshot,
- range: Range<Anchor>,
+ range: Range<text::Anchor>,
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<usize>,
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::<ExcerptId>();
+ 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<Anchor>,
+ range: Range<text::Anchor>,
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)
- );
- }
- }
}
@@ -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<T> {
+ entries: SmallVec<[(ExcerptId, text::AnchorRangeMap<T>); 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<Ordering> {
+ 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<T> AnchorRangeMap<T> {
+ 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<Item = (Range<D>, &'a T)> + 'a
+ where
+ D: TextDimension + Clone,
+ {
+ let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>();
+ 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::<D>(&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<Item = (Range<D>, &'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::<ExcerptSummary>();
+ 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<D>, &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<D>, &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<D>, &T)>
+ where
+ D: TextDimension,
+ F: FnMut(&T) -> K,
+ K: Ord,
+ {
+ let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>();
+ 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::<ExcerptSummary>();
+ 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
+ }
+}
@@ -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)
+ );
+ }
+ }
+}
@@ -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<AnchorRangeMap<SelectionState>>,
+}
+
+impl SelectionSet {
+ pub fn len(&self) -> usize {
+ self.selections.len()
+ }
+
+ pub fn selections<'a, D>(
+ &'a self,
+ content: &'a MultiBufferSnapshot,
+ ) -> impl 'a + Iterator<Item = Selection<D>>
+ 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<Item = Selection<D>>
+ 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<Selection<D>>
+ 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<Selection<D>>
+ 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,
+ })
+ }
+}
@@ -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 {
@@ -685,6 +685,15 @@ impl sum_tree::Summary for TextSummary {
}
}
+impl<'a> std::ops::Add<Self> 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;