mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
Implement Anchor, AnchorRangeMap, SelectionSet in multi_buffer
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
a7634ccd5f
commit
daedf179b2
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
280
crates/language/src/multi_buffer/anchor.rs
Normal file
280
crates/language/src/multi_buffer/anchor.rs
Normal 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
|
||||
}
|
||||
}
|
76
crates/language/src/multi_buffer/location.rs
Normal file
76
crates/language/src/multi_buffer/location.rs
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
91
crates/language/src/multi_buffer/selection.rs
Normal file
91
crates/language/src/multi_buffer/selection.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user