Implement Anchor, AnchorRangeMap, SelectionSet in multi_buffer

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2021-12-08 12:56:09 -08:00
parent a7634ccd5f
commit daedf179b2
6 changed files with 525 additions and 78 deletions

View File

@ -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)
);
}
}
}

View File

@ -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
}
}

View File

@ -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)
);
}
}
}

View File

@ -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,
})
}
}

View File

@ -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 {

View File

@ -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;