Add a layer of indirection between excerpt ids and locators

This commit is contained in:
Max Brunsfeld 2022-11-23 16:13:10 -08:00
parent 0b0fe91545
commit f71145bb32
8 changed files with 297 additions and 166 deletions

View file

@ -322,7 +322,7 @@ impl ProjectDiagnosticsEditor {
); );
let excerpt_id = excerpts let excerpt_id = excerpts
.insert_excerpts_after( .insert_excerpts_after(
&prev_excerpt_id, prev_excerpt_id,
buffer.clone(), buffer.clone(),
[ExcerptRange { [ExcerptRange {
context: excerpt_start..excerpt_end, context: excerpt_start..excerpt_end,
@ -384,7 +384,7 @@ impl ProjectDiagnosticsEditor {
groups_to_add.push(group_state); groups_to_add.push(group_state);
} else if let Some((group_ix, group_state)) = to_remove { } else if let Some((group_ix, group_state)) = to_remove {
excerpts.remove_excerpts(group_state.excerpts.iter(), excerpts_cx); excerpts.remove_excerpts(group_state.excerpts.iter().copied(), excerpts_cx);
group_ixs_to_remove.push(group_ix); group_ixs_to_remove.push(group_ix);
blocks_to_remove.extend(group_state.blocks.iter().copied()); blocks_to_remove.extend(group_state.blocks.iter().copied());
} else if let Some((_, group)) = to_keep { } else if let Some((_, group)) = to_keep {
@ -457,10 +457,15 @@ impl ProjectDiagnosticsEditor {
} }
// If any selection has lost its position, move it to start of the next primary diagnostic. // If any selection has lost its position, move it to start of the next primary diagnostic.
let snapshot = editor.snapshot(cx);
for selection in &mut selections { for selection in &mut selections {
if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) { if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) {
let group_ix = match groups.binary_search_by(|probe| { let group_ix = match groups.binary_search_by(|probe| {
probe.excerpts.last().unwrap().cmp(new_excerpt_id) probe
.excerpts
.last()
.unwrap()
.cmp(new_excerpt_id, &snapshot.buffer_snapshot)
}) { }) {
Ok(ix) | Err(ix) => ix, Ok(ix) | Err(ix) => ix,
}; };

View file

@ -2,7 +2,7 @@ use super::{
wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot}, wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
TextHighlights, TextHighlights,
}; };
use crate::{Anchor, ExcerptRange, ToPoint as _}; use crate::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
use collections::{Bound, HashMap, HashSet}; use collections::{Bound, HashMap, HashSet};
use gpui::{ElementBox, RenderContext}; use gpui::{ElementBox, RenderContext};
use language::{BufferSnapshot, Chunk, Patch, Point}; use language::{BufferSnapshot, Chunk, Patch, Point};
@ -107,7 +107,7 @@ struct Transform {
pub enum TransformBlock { pub enum TransformBlock {
Custom(Arc<Block>), Custom(Arc<Block>),
ExcerptHeader { ExcerptHeader {
key: usize, id: ExcerptId,
buffer: BufferSnapshot, buffer: BufferSnapshot,
range: ExcerptRange<text::Anchor>, range: ExcerptRange<text::Anchor>,
height: u8, height: u8,
@ -371,7 +371,7 @@ impl BlockMap {
.make_wrap_point(Point::new(excerpt_boundary.row, 0), Bias::Left) .make_wrap_point(Point::new(excerpt_boundary.row, 0), Bias::Left)
.row(), .row(),
TransformBlock::ExcerptHeader { TransformBlock::ExcerptHeader {
key: excerpt_boundary.key, id: excerpt_boundary.id,
buffer: excerpt_boundary.buffer, buffer: excerpt_boundary.buffer,
range: excerpt_boundary.range, range: excerpt_boundary.range,
height: if excerpt_boundary.starts_new_buffer { height: if excerpt_boundary.starts_new_buffer {

View file

@ -4718,9 +4718,7 @@ fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
// Refreshing selections is a no-op when excerpts haven't changed. // Refreshing selections is a no-op when excerpts haven't changed.
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| { editor.change_selections(None, cx, |s| s.refresh());
s.refresh();
});
assert_eq!( assert_eq!(
editor.selections.ranges(cx), editor.selections.ranges(cx),
[ [
@ -4731,7 +4729,7 @@ fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
}); });
multibuffer.update(cx, |multibuffer, cx| { multibuffer.update(cx, |multibuffer, cx| {
multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx); multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
}); });
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
// Removing an excerpt causes the first selection to become degenerate. // Removing an excerpt causes the first selection to become degenerate.
@ -4745,9 +4743,7 @@ fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
// Refreshing selections will relocate the first selection to the original buffer // Refreshing selections will relocate the first selection to the original buffer
// location. // location.
editor.change_selections(None, cx, |s| { editor.change_selections(None, cx, |s| s.refresh());
s.refresh();
});
assert_eq!( assert_eq!(
editor.selections.ranges(cx), editor.selections.ranges(cx),
[ [
@ -4801,7 +4797,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppC
}); });
multibuffer.update(cx, |multibuffer, cx| { multibuffer.update(cx, |multibuffer, cx| {
multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx); multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
}); });
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
assert_eq!( assert_eq!(
@ -4810,9 +4806,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppC
); );
// Ensure we don't panic when selections are refreshed and that the pending selection is finalized. // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
editor.change_selections(None, cx, |s| { editor.change_selections(None, cx, |s| s.refresh());
s.refresh();
});
assert_eq!( assert_eq!(
editor.selections.ranges(cx), editor.selections.ranges(cx),
[Point::new(0, 3)..Point::new(0, 3)] [Point::new(0, 3)..Point::new(0, 3)]

View file

@ -1334,12 +1334,13 @@ impl EditorElement {
}) })
} }
TransformBlock::ExcerptHeader { TransformBlock::ExcerptHeader {
key, id,
buffer, buffer,
range, range,
starts_new_buffer, starts_new_buffer,
.. ..
} => { } => {
let id = *id;
let jump_icon = project::File::from_dyn(buffer.file()).map(|file| { let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
let jump_position = range let jump_position = range
.primary .primary
@ -1356,7 +1357,7 @@ impl EditorElement {
enum JumpIcon {} enum JumpIcon {}
cx.render(&editor, |_, cx| { cx.render(&editor, |_, cx| {
MouseEventHandler::<JumpIcon>::new(*key, cx, |state, _| { MouseEventHandler::<JumpIcon>::new(id.into(), cx, |state, _| {
let style = style.jump_icon.style_for(state, false); let style = style.jump_icon.style_for(state, false);
Svg::new("icons/arrow_up_right_8.svg") Svg::new("icons/arrow_up_right_8.svg")
.with_color(style.color) .with_color(style.color)
@ -1375,7 +1376,7 @@ impl EditorElement {
cx.dispatch_action(jump_action.clone()) cx.dispatch_action(jump_action.clone())
}) })
.with_tooltip::<JumpIcon, _>( .with_tooltip::<JumpIcon, _>(
*key, id.into(),
"Jump to Buffer".to_string(), "Jump to Buffer".to_string(),
Some(Box::new(crate::OpenExcerpts)), Some(Box::new(crate::OpenExcerpts)),
tooltip_style.clone(), tooltip_style.clone(),

View file

@ -36,13 +36,13 @@ use util::post_inc;
const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
pub type ExcerptId = Locator; #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExcerptId(usize);
pub struct MultiBuffer { pub struct MultiBuffer {
snapshot: RefCell<MultiBufferSnapshot>, snapshot: RefCell<MultiBufferSnapshot>,
buffers: RefCell<HashMap<usize, BufferState>>, buffers: RefCell<HashMap<usize, BufferState>>,
used_excerpt_ids: SumTree<ExcerptId>, next_excerpt_id: usize,
next_excerpt_key: usize,
subscriptions: Topic, subscriptions: Topic,
singleton: bool, singleton: bool,
replica_id: ReplicaId, replica_id: ReplicaId,
@ -92,7 +92,7 @@ struct BufferState {
last_diagnostics_update_count: usize, last_diagnostics_update_count: usize,
last_file_update_count: usize, last_file_update_count: usize,
last_git_diff_update_count: usize, last_git_diff_update_count: usize,
excerpts: Vec<ExcerptId>, excerpts: Vec<Locator>,
_subscriptions: [gpui::Subscription; 2], _subscriptions: [gpui::Subscription; 2],
} }
@ -100,6 +100,7 @@ struct BufferState {
pub struct MultiBufferSnapshot { pub struct MultiBufferSnapshot {
singleton: bool, singleton: bool,
excerpts: SumTree<Excerpt>, excerpts: SumTree<Excerpt>,
excerpt_ids: SumTree<ExcerptIdMapping>,
parse_count: usize, parse_count: usize,
diagnostics_update_count: usize, diagnostics_update_count: usize,
trailing_excerpt_update_count: usize, trailing_excerpt_update_count: usize,
@ -111,7 +112,6 @@ pub struct MultiBufferSnapshot {
pub struct ExcerptBoundary { pub struct ExcerptBoundary {
pub id: ExcerptId, pub id: ExcerptId,
pub key: usize,
pub row: u32, pub row: u32,
pub buffer: BufferSnapshot, pub buffer: BufferSnapshot,
pub range: ExcerptRange<text::Anchor>, pub range: ExcerptRange<text::Anchor>,
@ -121,7 +121,7 @@ pub struct ExcerptBoundary {
#[derive(Clone)] #[derive(Clone)]
struct Excerpt { struct Excerpt {
id: ExcerptId, id: ExcerptId,
key: usize, locator: Locator,
buffer_id: usize, buffer_id: usize,
buffer: BufferSnapshot, buffer: BufferSnapshot,
range: ExcerptRange<text::Anchor>, range: ExcerptRange<text::Anchor>,
@ -130,6 +130,12 @@ struct Excerpt {
has_trailing_newline: bool, has_trailing_newline: bool,
} }
#[derive(Clone, Debug)]
struct ExcerptIdMapping {
id: ExcerptId,
locator: Locator,
}
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExcerptRange<T> { pub struct ExcerptRange<T> {
pub context: Range<T>, pub context: Range<T>,
@ -139,6 +145,7 @@ pub struct ExcerptRange<T> {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct ExcerptSummary { struct ExcerptSummary {
excerpt_id: ExcerptId, excerpt_id: ExcerptId,
excerpt_locator: Locator,
max_buffer_row: u32, max_buffer_row: u32,
text: TextSummary, text: TextSummary,
} }
@ -178,8 +185,7 @@ impl MultiBuffer {
Self { Self {
snapshot: Default::default(), snapshot: Default::default(),
buffers: Default::default(), buffers: Default::default(),
used_excerpt_ids: Default::default(), next_excerpt_id: 1,
next_excerpt_key: Default::default(),
subscriptions: Default::default(), subscriptions: Default::default(),
singleton: false, singleton: false,
replica_id, replica_id,
@ -218,8 +224,7 @@ impl MultiBuffer {
Self { Self {
snapshot: RefCell::new(self.snapshot.borrow().clone()), snapshot: RefCell::new(self.snapshot.borrow().clone()),
buffers: RefCell::new(buffers), buffers: RefCell::new(buffers),
used_excerpt_ids: self.used_excerpt_ids.clone(), next_excerpt_id: 1,
next_excerpt_key: self.next_excerpt_key,
subscriptions: Default::default(), subscriptions: Default::default(),
singleton: self.singleton, singleton: self.singleton,
replica_id: self.replica_id, replica_id: self.replica_id,
@ -610,11 +615,14 @@ impl MultiBuffer {
let mut selections_by_buffer: HashMap<usize, Vec<Selection<text::Anchor>>> = let mut selections_by_buffer: HashMap<usize, Vec<Selection<text::Anchor>>> =
Default::default(); Default::default();
let snapshot = self.read(cx); let snapshot = self.read(cx);
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>(); let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
for selection in selections { for selection in selections {
cursor.seek(&Some(&selection.start.excerpt_id), Bias::Left, &()); let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id);
let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id);
cursor.seek(&Some(start_locator), Bias::Left, &());
while let Some(excerpt) = cursor.item() { while let Some(excerpt) = cursor.item() {
if excerpt.id > selection.end.excerpt_id { if excerpt.locator > *end_locator {
break; break;
} }
@ -745,7 +753,7 @@ impl MultiBuffer {
where where
O: text::ToOffset, O: text::ToOffset,
{ {
self.insert_excerpts_after(&ExcerptId::max(), buffer, ranges, cx) self.insert_excerpts_after(ExcerptId::max(), buffer, ranges, cx)
} }
pub fn push_excerpts_with_context_lines<O>( pub fn push_excerpts_with_context_lines<O>(
@ -818,7 +826,7 @@ impl MultiBuffer {
pub fn insert_excerpts_after<O>( pub fn insert_excerpts_after<O>(
&mut self, &mut self,
prev_excerpt_id: &ExcerptId, prev_excerpt_id: ExcerptId,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
ranges: impl IntoIterator<Item = ExcerptRange<O>>, ranges: impl IntoIterator<Item = ExcerptRange<O>>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
@ -854,8 +862,12 @@ impl MultiBuffer {
}); });
let mut snapshot = self.snapshot.borrow_mut(); let mut snapshot = self.snapshot.borrow_mut();
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
let mut new_excerpts = cursor.slice(&Some(prev_excerpt_id), Bias::Right, &()); let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone();
let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids);
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right, &());
prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone();
let edit_start = new_excerpts.summary().text.len; let edit_start = new_excerpts.summary().text.len;
new_excerpts.update_last( new_excerpts.update_last(
@ -865,25 +877,17 @@ impl MultiBuffer {
&(), &(),
); );
let mut used_cursor = self.used_excerpt_ids.cursor::<Locator>(); let next_locator = if let Some(excerpt) = cursor.item() {
used_cursor.seek(prev_excerpt_id, Bias::Right, &()); excerpt.locator.clone()
let mut prev_id = if let Some(excerpt_id) = used_cursor.prev_item() {
excerpt_id.clone()
} else { } else {
ExcerptId::min() Locator::max()
}; };
let next_id = if let Some(excerpt_id) = used_cursor.item() {
excerpt_id.clone()
} else {
ExcerptId::max()
};
drop(used_cursor);
let mut ids = Vec::new(); let mut ids = Vec::new();
while let Some(range) = ranges.next() { while let Some(range) = ranges.next() {
let id = ExcerptId::between(&prev_id, &next_id); let locator = Locator::between(&prev_locator, &next_locator);
if let Err(ix) = buffer_state.excerpts.binary_search(&id) { if let Err(ix) = buffer_state.excerpts.binary_search(&locator) {
buffer_state.excerpts.insert(ix, id.clone()); buffer_state.excerpts.insert(ix, locator.clone());
} }
let range = ExcerptRange { let range = ExcerptRange {
context: buffer_snapshot.anchor_before(&range.context.start) context: buffer_snapshot.anchor_before(&range.context.start)
@ -893,22 +897,20 @@ impl MultiBuffer {
..buffer_snapshot.anchor_after(&primary.end) ..buffer_snapshot.anchor_after(&primary.end)
}), }),
}; };
let id = ExcerptId(post_inc(&mut self.next_excerpt_id));
let excerpt = Excerpt::new( let excerpt = Excerpt::new(
id.clone(), id,
post_inc(&mut self.next_excerpt_key), locator.clone(),
buffer_id, buffer_id,
buffer_snapshot.clone(), buffer_snapshot.clone(),
range, range,
ranges.peek().is_some() || cursor.item().is_some(), ranges.peek().is_some() || cursor.item().is_some(),
); );
new_excerpts.push(excerpt, &()); new_excerpts.push(excerpt, &());
prev_id = id.clone(); prev_locator = locator.clone();
new_excerpt_ids.push(ExcerptIdMapping { id, locator }, &());
ids.push(id); ids.push(id);
} }
self.used_excerpt_ids.edit(
ids.iter().cloned().map(sum_tree::Edit::Insert).collect(),
&(),
);
let edit_end = new_excerpts.summary().text.len; let edit_end = new_excerpts.summary().text.len;
@ -917,6 +919,7 @@ impl MultiBuffer {
new_excerpts.push_tree(suffix, &()); new_excerpts.push_tree(suffix, &());
drop(cursor); drop(cursor);
snapshot.excerpts = new_excerpts; snapshot.excerpts = new_excerpts;
snapshot.excerpt_ids = new_excerpt_ids;
if changed_trailing_excerpt { if changed_trailing_excerpt {
snapshot.trailing_excerpt_update_count += 1; snapshot.trailing_excerpt_update_count += 1;
} }
@ -956,16 +959,16 @@ impl MultiBuffer {
let mut excerpts = Vec::new(); let mut excerpts = Vec::new();
let snapshot = self.read(cx); let snapshot = self.read(cx);
let buffers = self.buffers.borrow(); let buffers = self.buffers.borrow();
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>(); let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
for excerpt_id in buffers for locator in buffers
.get(&buffer.id()) .get(&buffer.id())
.map(|state| &state.excerpts) .map(|state| &state.excerpts)
.into_iter() .into_iter()
.flatten() .flatten()
{ {
cursor.seek_forward(&Some(excerpt_id), Bias::Left, &()); cursor.seek_forward(&Some(locator), Bias::Left, &());
if let Some(excerpt) = cursor.item() { if let Some(excerpt) = cursor.item() {
if excerpt.id == *excerpt_id { if excerpt.locator == *locator {
excerpts.push((excerpt.id.clone(), excerpt.range.clone())); excerpts.push((excerpt.id.clone(), excerpt.range.clone()));
} }
} }
@ -975,10 +978,11 @@ impl MultiBuffer {
} }
pub fn excerpt_ids(&self) -> Vec<ExcerptId> { pub fn excerpt_ids(&self) -> Vec<ExcerptId> {
self.buffers self.snapshot
.borrow() .borrow()
.values() .excerpts
.flat_map(|state| state.excerpts.iter().cloned()) .iter()
.map(|entry| entry.id)
.collect() .collect()
} }
@ -1061,32 +1065,34 @@ impl MultiBuffer {
result result
} }
pub fn remove_excerpts<'a>( pub fn remove_excerpts(
&mut self, &mut self,
excerpt_ids: impl IntoIterator<Item = &'a ExcerptId>, excerpt_ids: impl IntoIterator<Item = ExcerptId>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
self.sync(cx); self.sync(cx);
let mut buffers = self.buffers.borrow_mut(); let mut buffers = self.buffers.borrow_mut();
let mut snapshot = self.snapshot.borrow_mut(); let mut snapshot = self.snapshot.borrow_mut();
let mut new_excerpts = SumTree::new(); let mut new_excerpts = SumTree::new();
let mut cursor = snapshot.excerpts.cursor::<(Option<&ExcerptId>, usize)>(); let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
let mut edits = Vec::new(); let mut edits = Vec::new();
let mut excerpt_ids = excerpt_ids.into_iter().peekable(); let mut excerpt_ids = excerpt_ids.into_iter().peekable();
while let Some(mut excerpt_id) = excerpt_ids.next() { while let Some(excerpt_id) = excerpt_ids.next() {
// Seek to the next excerpt to remove, preserving any preceding excerpts. // Seek to the next excerpt to remove, preserving any preceding excerpts.
new_excerpts.push_tree(cursor.slice(&Some(excerpt_id), Bias::Left, &()), &()); let locator = snapshot.excerpt_locator_for_id(excerpt_id);
new_excerpts.push_tree(cursor.slice(&Some(locator), Bias::Left, &()), &());
if let Some(mut excerpt) = cursor.item() { if let Some(mut excerpt) = cursor.item() {
if excerpt.id != *excerpt_id { if excerpt.id != excerpt_id {
continue; continue;
} }
let mut old_start = cursor.start().1; let mut old_start = cursor.start().1;
// Skip over the removed excerpt. // Skip over the removed excerpt.
loop { 'remove_excerpts: loop {
if let Some(buffer_state) = buffers.get_mut(&excerpt.buffer_id) { if let Some(buffer_state) = buffers.get_mut(&excerpt.buffer_id) {
buffer_state.excerpts.retain(|id| id != excerpt_id); buffer_state.excerpts.retain(|l| l != &excerpt.locator);
if buffer_state.excerpts.is_empty() { if buffer_state.excerpts.is_empty() {
buffers.remove(&excerpt.buffer_id); buffers.remove(&excerpt.buffer_id);
} }
@ -1094,14 +1100,16 @@ impl MultiBuffer {
cursor.next(&()); cursor.next(&());
// Skip over any subsequent excerpts that are also removed. // Skip over any subsequent excerpts that are also removed.
if let Some(&next_excerpt_id) = excerpt_ids.peek() { while let Some(&next_excerpt_id) = excerpt_ids.peek() {
let next_locator = snapshot.excerpt_locator_for_id(next_excerpt_id);
if let Some(next_excerpt) = cursor.item() { if let Some(next_excerpt) = cursor.item() {
if next_excerpt.id == *next_excerpt_id { if next_excerpt.locator == *next_locator {
excerpt_ids.next();
excerpt = next_excerpt; excerpt = next_excerpt;
excerpt_id = excerpt_ids.next().unwrap(); continue 'remove_excerpts;
continue;
} }
} }
break;
} }
break; break;
@ -1128,6 +1136,7 @@ impl MultiBuffer {
new_excerpts.push_tree(suffix, &()); new_excerpts.push_tree(suffix, &());
drop(cursor); drop(cursor);
snapshot.excerpts = new_excerpts; snapshot.excerpts = new_excerpts;
if changed_trailing_excerpt { if changed_trailing_excerpt {
snapshot.trailing_excerpt_update_count += 1; snapshot.trailing_excerpt_update_count += 1;
} }
@ -1307,7 +1316,7 @@ impl MultiBuffer {
buffer_state buffer_state
.excerpts .excerpts
.iter() .iter()
.map(|excerpt_id| (excerpt_id, buffer_state.buffer.clone(), buffer_edited)), .map(|locator| (locator, buffer_state.buffer.clone(), buffer_edited)),
); );
} }
@ -1333,14 +1342,14 @@ impl MultiBuffer {
snapshot.is_dirty = is_dirty; snapshot.is_dirty = is_dirty;
snapshot.has_conflict = has_conflict; snapshot.has_conflict = has_conflict;
excerpts_to_edit.sort_unstable_by_key(|(excerpt_id, _, _)| *excerpt_id); excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator);
let mut edits = Vec::new(); let mut edits = Vec::new();
let mut new_excerpts = SumTree::new(); let mut new_excerpts = SumTree::new();
let mut cursor = snapshot.excerpts.cursor::<(Option<&ExcerptId>, usize)>(); let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
for (id, buffer, buffer_edited) in excerpts_to_edit { for (locator, buffer, buffer_edited) in excerpts_to_edit {
new_excerpts.push_tree(cursor.slice(&Some(id), Bias::Left, &()), &()); new_excerpts.push_tree(cursor.slice(&Some(locator), Bias::Left, &()), &());
let old_excerpt = cursor.item().unwrap(); let old_excerpt = cursor.item().unwrap();
let buffer_id = buffer.id(); let buffer_id = buffer.id();
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
@ -1365,8 +1374,8 @@ impl MultiBuffer {
); );
new_excerpt = Excerpt::new( new_excerpt = Excerpt::new(
id.clone(), old_excerpt.id,
old_excerpt.key, locator.clone(),
buffer_id, buffer_id,
buffer.snapshot(), buffer.snapshot(),
old_excerpt.range.clone(), old_excerpt.range.clone(),
@ -1467,13 +1476,7 @@ impl MultiBuffer {
continue; continue;
} }
let excerpt_ids = self let excerpt_ids = self.excerpt_ids();
.buffers
.borrow()
.values()
.flat_map(|b| &b.excerpts)
.cloned()
.collect::<Vec<_>>();
if excerpt_ids.is_empty() || (rng.gen() && excerpt_ids.len() < max_excerpts) { if excerpt_ids.is_empty() || (rng.gen() && excerpt_ids.len() < max_excerpts) {
let buffer_handle = if rng.gen() || self.buffers.borrow().is_empty() { let buffer_handle = if rng.gen() || self.buffers.borrow().is_empty() {
let text = RandomCharIter::new(&mut *rng).take(10).collect::<String>(); let text = RandomCharIter::new(&mut *rng).take(10).collect::<String>();
@ -1511,24 +1514,26 @@ impl MultiBuffer {
log::info!( log::info!(
"Inserting excerpts from buffer {} and ranges {:?}: {:?}", "Inserting excerpts from buffer {} and ranges {:?}: {:?}",
buffer_handle.id(), buffer_handle.id(),
ranges, ranges.iter().map(|r| &r.context).collect::<Vec<_>>(),
ranges ranges
.iter() .iter()
.map(|range| &buffer_text[range.context.clone()]) .map(|r| &buffer_text[r.context.clone()])
.collect::<Vec<_>>() .collect::<Vec<_>>()
); );
let excerpt_id = self.push_excerpts(buffer_handle.clone(), ranges, cx); let excerpt_id = self.push_excerpts(buffer_handle.clone(), ranges, cx);
log::info!("Inserted with id: {:?}", excerpt_id); log::info!("Inserted with ids: {:?}", excerpt_id);
} else { } else {
let remove_count = rng.gen_range(1..=excerpt_ids.len()); let remove_count = rng.gen_range(1..=excerpt_ids.len());
let mut excerpts_to_remove = excerpt_ids let mut excerpts_to_remove = excerpt_ids
.choose_multiple(rng, remove_count) .choose_multiple(rng, remove_count)
.cloned() .cloned()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
excerpts_to_remove.sort(); let snapshot = self.snapshot.borrow();
excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &*snapshot));
drop(snapshot);
log::info!("Removing excerpts {:?}", excerpts_to_remove); log::info!("Removing excerpts {:?}", excerpts_to_remove);
self.remove_excerpts(&excerpts_to_remove, cx); self.remove_excerpts(excerpts_to_remove, cx);
} }
} }
} }
@ -1563,6 +1568,38 @@ impl MultiBuffer {
} else { } else {
self.randomly_edit_excerpts(rng, mutation_count, cx); self.randomly_edit_excerpts(rng, mutation_count, cx);
} }
self.check_invariants(cx);
}
fn check_invariants(&self, cx: &mut ModelContext<Self>) {
let snapshot = self.read(cx);
let excerpts = snapshot.excerpts.items(&());
let excerpt_ids = snapshot.excerpt_ids.items(&());
for (ix, excerpt) in excerpts.iter().enumerate() {
if ix == 0 {
if excerpt.locator <= Locator::min() {
panic!("invalid first excerpt locator {:?}", excerpt.locator);
}
} else {
if excerpt.locator <= excerpts[ix - 1].locator {
panic!("excerpts are out-of-order: {:?}", excerpts);
}
}
}
for (ix, entry) in excerpt_ids.iter().enumerate() {
if ix == 0 {
if entry.id.cmp(&ExcerptId::min(), &*snapshot).is_le() {
panic!("invalid first excerpt id {:?}", entry.id);
}
} else {
if entry.id <= excerpt_ids[ix - 1].id {
panic!("excerpt ids are out-of-order: {:?}", excerpt_ids);
}
}
}
} }
} }
@ -2151,7 +2188,9 @@ impl MultiBufferSnapshot {
D: TextDimension + Ord + Sub<D, Output = D>, D: TextDimension + Ord + Sub<D, Output = D>,
{ {
let mut cursor = self.excerpts.cursor::<ExcerptSummary>(); let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
cursor.seek(&Some(&anchor.excerpt_id), Bias::Left, &()); let locator = self.excerpt_locator_for_id(anchor.excerpt_id);
cursor.seek(locator, Bias::Left, &());
if cursor.item().is_none() { if cursor.item().is_none() {
cursor.next(&()); cursor.next(&());
} }
@ -2189,24 +2228,25 @@ impl MultiBufferSnapshot {
let mut cursor = self.excerpts.cursor::<ExcerptSummary>(); let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
let mut summaries = Vec::new(); let mut summaries = Vec::new();
while let Some(anchor) = anchors.peek() { while let Some(anchor) = anchors.peek() {
let excerpt_id = &anchor.excerpt_id; let excerpt_id = anchor.excerpt_id;
let excerpt_anchors = iter::from_fn(|| { let excerpt_anchors = iter::from_fn(|| {
let anchor = anchors.peek()?; let anchor = anchors.peek()?;
if anchor.excerpt_id == *excerpt_id { if anchor.excerpt_id == excerpt_id {
Some(&anchors.next().unwrap().text_anchor) Some(&anchors.next().unwrap().text_anchor)
} else { } else {
None None
} }
}); });
cursor.seek_forward(&Some(excerpt_id), Bias::Left, &()); let locator = self.excerpt_locator_for_id(excerpt_id);
cursor.seek_forward(locator, Bias::Left, &());
if cursor.item().is_none() { if cursor.item().is_none() {
cursor.next(&()); cursor.next(&());
} }
let position = D::from_text_summary(&cursor.start().text); let position = D::from_text_summary(&cursor.start().text);
if let Some(excerpt) = cursor.item() { if let Some(excerpt) = cursor.item() {
if excerpt.id == *excerpt_id { if excerpt.id == excerpt_id {
let excerpt_buffer_start = let excerpt_buffer_start =
excerpt.range.context.start.summary::<D>(&excerpt.buffer); excerpt.range.context.start.summary::<D>(&excerpt.buffer);
let excerpt_buffer_end = let excerpt_buffer_end =
@ -2240,13 +2280,18 @@ impl MultiBufferSnapshot {
I: 'a + IntoIterator<Item = &'a Anchor>, I: 'a + IntoIterator<Item = &'a Anchor>,
{ {
let mut anchors = anchors.into_iter().enumerate().peekable(); let mut anchors = anchors.into_iter().enumerate().peekable();
let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>(); let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
cursor.next(&());
let mut result = Vec::new(); let mut result = Vec::new();
while let Some((_, anchor)) = anchors.peek() { while let Some((_, anchor)) = anchors.peek() {
let old_excerpt_id = &anchor.excerpt_id; let old_excerpt_id = anchor.excerpt_id;
// Find the location where this anchor's excerpt should be. // Find the location where this anchor's excerpt should be.
cursor.seek_forward(&Some(old_excerpt_id), Bias::Left, &()); let old_locator = self.excerpt_locator_for_id(old_excerpt_id);
cursor.seek_forward(&Some(old_locator), Bias::Left, &());
if cursor.item().is_none() { if cursor.item().is_none() {
cursor.next(&()); cursor.next(&());
} }
@ -2256,27 +2301,22 @@ impl MultiBufferSnapshot {
// Process all of the anchors for this excerpt. // Process all of the anchors for this excerpt.
while let Some((_, anchor)) = anchors.peek() { while let Some((_, anchor)) = anchors.peek() {
if anchor.excerpt_id != *old_excerpt_id { if anchor.excerpt_id != old_excerpt_id {
break; break;
} }
let mut kept_position = false;
let (anchor_ix, anchor) = anchors.next().unwrap(); let (anchor_ix, anchor) = anchors.next().unwrap();
let mut anchor = anchor.clone(); let mut anchor = anchor.clone();
let id_invalid =
*old_excerpt_id == ExcerptId::max() || *old_excerpt_id == ExcerptId::min();
let still_exists = next_excerpt.map_or(false, |excerpt| {
excerpt.id == *old_excerpt_id && excerpt.contains(&anchor)
});
// Leave min and max anchors unchanged if invalid or // Leave min and max anchors unchanged if invalid or
// if the old excerpt still exists at this location // if the old excerpt still exists at this location
if id_invalid || still_exists { let mut kept_position = next_excerpt
kept_position = true; .map_or(false, |e| e.id == old_excerpt_id && e.contains(&anchor))
} || old_excerpt_id == ExcerptId::max()
|| old_excerpt_id == ExcerptId::min();
// If the old excerpt no longer exists at this location, then attempt to // If the old excerpt no longer exists at this location, then attempt to
// find an equivalent position for this anchor in an adjacent excerpt. // find an equivalent position for this anchor in an adjacent excerpt.
else { if !kept_position {
for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) { for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) {
if excerpt.contains(&anchor) { if excerpt.contains(&anchor) {
anchor.excerpt_id = excerpt.id.clone(); anchor.excerpt_id = excerpt.id.clone();
@ -2285,6 +2325,7 @@ impl MultiBufferSnapshot {
} }
} }
} }
// If there's no adjacent excerpt that contains the anchor's position, // If there's no adjacent excerpt that contains the anchor's position,
// then report that the anchor has lost its position. // then report that the anchor has lost its position.
if !kept_position { if !kept_position {
@ -2354,7 +2395,7 @@ impl MultiBufferSnapshot {
}; };
} }
let mut cursor = self.excerpts.cursor::<(usize, Option<&ExcerptId>)>(); let mut cursor = self.excerpts.cursor::<(usize, Option<ExcerptId>)>();
cursor.seek(&offset, Bias::Right, &()); cursor.seek(&offset, Bias::Right, &());
if cursor.item().is_none() && offset == cursor.start().0 && bias == Bias::Left { if cursor.item().is_none() && offset == cursor.start().0 && bias == Bias::Left {
cursor.prev(&()); cursor.prev(&());
@ -2382,8 +2423,9 @@ impl MultiBufferSnapshot {
} }
pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor { pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor {
let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>(); let locator = self.excerpt_locator_for_id(excerpt_id);
cursor.seek(&Some(&excerpt_id), Bias::Left, &()); let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
cursor.seek(locator, Bias::Left, &());
if let Some(excerpt) = cursor.item() { if let Some(excerpt) = cursor.item() {
if excerpt.id == excerpt_id { if excerpt.id == excerpt_id {
let text_anchor = excerpt.clip_anchor(text_anchor); let text_anchor = excerpt.clip_anchor(text_anchor);
@ -2401,7 +2443,7 @@ impl MultiBufferSnapshot {
pub fn can_resolve(&self, anchor: &Anchor) -> bool { pub fn can_resolve(&self, anchor: &Anchor) -> bool {
if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() { if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() {
true true
} else if let Some(excerpt) = self.excerpt(&anchor.excerpt_id) { } else if let Some(excerpt) = self.excerpt(anchor.excerpt_id) {
excerpt.buffer.can_resolve(&anchor.text_anchor) excerpt.buffer.can_resolve(&anchor.text_anchor)
} else { } else {
false false
@ -2456,7 +2498,6 @@ impl MultiBufferSnapshot {
let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id; let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id;
let boundary = ExcerptBoundary { let boundary = ExcerptBoundary {
id: excerpt.id.clone(), id: excerpt.id.clone(),
key: excerpt.key,
row: cursor.start().1.row, row: cursor.start().1.row,
buffer: excerpt.buffer.clone(), buffer: excerpt.buffer.clone(),
range: excerpt.range.clone(), range: excerpt.range.clone(),
@ -2678,8 +2719,8 @@ impl MultiBufferSnapshot {
.flatten() .flatten()
.map(|item| OutlineItem { .map(|item| OutlineItem {
depth: item.depth, depth: item.depth,
range: self.anchor_in_excerpt(excerpt_id.clone(), item.range.start) range: self.anchor_in_excerpt(excerpt_id, item.range.start)
..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end), ..self.anchor_in_excerpt(excerpt_id, item.range.end),
text: item.text, text: item.text,
highlight_ranges: item.highlight_ranges, highlight_ranges: item.highlight_ranges,
name_ranges: item.name_ranges, name_ranges: item.name_ranges,
@ -2688,11 +2729,29 @@ impl MultiBufferSnapshot {
)) ))
} }
fn excerpt<'a>(&'a self, excerpt_id: &'a ExcerptId) -> Option<&'a Excerpt> { fn excerpt_locator_for_id<'a>(&'a self, id: ExcerptId) -> &'a Locator {
let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>(); if id == ExcerptId::min() {
cursor.seek(&Some(excerpt_id), Bias::Left, &()); Locator::min_ref()
} else if id == ExcerptId::max() {
Locator::max_ref()
} else {
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>();
cursor.seek(&id, Bias::Left, &());
if let Some(entry) = cursor.item() {
if entry.id == id {
return &entry.locator;
}
}
panic!("invalid excerpt id {:?}", id)
}
}
fn excerpt<'a>(&'a self, excerpt_id: ExcerptId) -> Option<&'a Excerpt> {
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
let locator = self.excerpt_locator_for_id(excerpt_id);
cursor.seek(&Some(locator), Bias::Left, &());
if let Some(excerpt) = cursor.item() { if let Some(excerpt) = cursor.item() {
if excerpt.id == *excerpt_id { if excerpt.id == excerpt_id {
return Some(excerpt); return Some(excerpt);
} }
} }
@ -2703,10 +2762,12 @@ impl MultiBufferSnapshot {
&'a self, &'a self,
range: &'a Range<Anchor>, range: &'a Range<Anchor>,
) -> impl 'a + Iterator<Item = (ReplicaId, bool, CursorShape, Selection<Anchor>)> { ) -> impl 'a + Iterator<Item = (ReplicaId, bool, CursorShape, Selection<Anchor>)> {
let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>(); let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
cursor.seek(&Some(&range.start.excerpt_id), Bias::Left, &()); let start_locator = self.excerpt_locator_for_id(range.start.excerpt_id);
let end_locator = self.excerpt_locator_for_id(range.end.excerpt_id);
cursor.seek(start_locator, Bias::Left, &());
cursor cursor
.take_while(move |excerpt| excerpt.id <= range.end.excerpt_id) .take_while(move |excerpt| excerpt.locator <= *end_locator)
.flat_map(move |excerpt| { .flat_map(move |excerpt| {
let mut query_range = excerpt.range.context.start..excerpt.range.context.end; let mut query_range = excerpt.range.context.start..excerpt.range.context.end;
if excerpt.id == range.start.excerpt_id { if excerpt.id == range.start.excerpt_id {
@ -2916,7 +2977,7 @@ impl History {
impl Excerpt { impl Excerpt {
fn new( fn new(
id: ExcerptId, id: ExcerptId,
key: usize, locator: Locator,
buffer_id: usize, buffer_id: usize,
buffer: BufferSnapshot, buffer: BufferSnapshot,
range: ExcerptRange<text::Anchor>, range: ExcerptRange<text::Anchor>,
@ -2924,7 +2985,7 @@ impl Excerpt {
) -> Self { ) -> Self {
Excerpt { Excerpt {
id, id,
key, locator,
max_buffer_row: range.context.end.to_point(&buffer).row, max_buffer_row: range.context.end.to_point(&buffer).row,
text_summary: buffer text_summary: buffer
.text_summary_for_range::<TextSummary, _>(range.context.to_offset(&buffer)), .text_summary_for_range::<TextSummary, _>(range.context.to_offset(&buffer)),
@ -3010,10 +3071,33 @@ impl Excerpt {
} }
} }
impl ExcerptId {
pub fn min() -> Self {
Self(0)
}
pub fn max() -> Self {
Self(usize::MAX)
}
pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering {
let a = snapshot.excerpt_locator_for_id(*self);
let b = snapshot.excerpt_locator_for_id(*other);
a.cmp(&b).then_with(|| self.0.cmp(&other.0))
}
}
impl Into<usize> for ExcerptId {
fn into(self) -> usize {
self.0
}
}
impl fmt::Debug for Excerpt { impl fmt::Debug for Excerpt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Excerpt") f.debug_struct("Excerpt")
.field("id", &self.id) .field("id", &self.id)
.field("locator", &self.locator)
.field("buffer_id", &self.buffer_id) .field("buffer_id", &self.buffer_id)
.field("range", &self.range) .field("range", &self.range)
.field("text_summary", &self.text_summary) .field("text_summary", &self.text_summary)
@ -3031,19 +3115,44 @@ impl sum_tree::Item for Excerpt {
text += TextSummary::from("\n"); text += TextSummary::from("\n");
} }
ExcerptSummary { ExcerptSummary {
excerpt_id: self.id.clone(), excerpt_id: self.id,
excerpt_locator: self.locator.clone(),
max_buffer_row: self.max_buffer_row, max_buffer_row: self.max_buffer_row,
text, text,
} }
} }
} }
impl sum_tree::Item for ExcerptIdMapping {
type Summary = ExcerptId;
fn summary(&self) -> Self::Summary {
self.id
}
}
impl sum_tree::KeyedItem for ExcerptIdMapping {
type Key = ExcerptId;
fn key(&self) -> Self::Key {
self.id
}
}
impl sum_tree::Summary for ExcerptId {
type Context = ();
fn add_summary(&mut self, other: &Self, _: &()) {
*self = *other;
}
}
impl sum_tree::Summary for ExcerptSummary { impl sum_tree::Summary for ExcerptSummary {
type Context = (); type Context = ();
fn add_summary(&mut self, summary: &Self, _: &()) { fn add_summary(&mut self, summary: &Self, _: &()) {
debug_assert!(summary.excerpt_id > self.excerpt_id); debug_assert!(summary.excerpt_locator > self.excerpt_locator);
self.excerpt_id = summary.excerpt_id.clone(); self.excerpt_locator = summary.excerpt_locator.clone();
self.text.add_summary(&summary.text, &()); self.text.add_summary(&summary.text, &());
self.max_buffer_row = cmp::max(self.max_buffer_row, summary.max_buffer_row); self.max_buffer_row = cmp::max(self.max_buffer_row, summary.max_buffer_row);
} }
@ -3067,9 +3176,15 @@ impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize {
} }
} }
impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Option<&'a ExcerptId> { impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator {
fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering {
Ord::cmp(&Some(self), cursor_location)
}
}
impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Locator {
fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering { fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
Ord::cmp(self, &Some(&cursor_location.excerpt_id)) Ord::cmp(self, &cursor_location.excerpt_locator)
} }
} }
@ -3091,9 +3206,15 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
} }
} }
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a ExcerptId> { impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a Locator> {
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
*self = Some(&summary.excerpt_id); *self = Some(&summary.excerpt_locator);
}
}
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<ExcerptId> {
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
*self = Some(summary.excerpt_id);
} }
} }
@ -3591,7 +3712,7 @@ mod tests {
let snapshot = multibuffer.update(cx, |multibuffer, cx| { let snapshot = multibuffer.update(cx, |multibuffer, cx| {
let (buffer_2_excerpt_id, _) = let (buffer_2_excerpt_id, _) =
multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone(); multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone();
multibuffer.remove_excerpts(&[buffer_2_excerpt_id], cx); multibuffer.remove_excerpts([buffer_2_excerpt_id], cx);
multibuffer.snapshot(cx) multibuffer.snapshot(cx)
}); });
@ -3780,7 +3901,7 @@ mod tests {
// Replace the buffer 1 excerpt with new excerpts from buffer 2. // Replace the buffer 1 excerpt with new excerpts from buffer 2.
let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| { let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
multibuffer.remove_excerpts([&excerpt_id_1], cx); multibuffer.remove_excerpts([excerpt_id_1], cx);
let mut ids = multibuffer let mut ids = multibuffer
.push_excerpts( .push_excerpts(
buffer_2.clone(), buffer_2.clone(),
@ -3810,9 +3931,8 @@ mod tests {
assert_ne!(excerpt_id_2, excerpt_id_1); assert_ne!(excerpt_id_2, excerpt_id_1);
// Resolve some anchors from the previous snapshot in the new snapshot. // Resolve some anchors from the previous snapshot in the new snapshot.
// Although there is still an excerpt with the same id, it is for // The current excerpts are from a different buffer, so we don't attempt to
// a different buffer, so we don't attempt to resolve the old text // resolve the old text anchor in the new buffer.
// anchor in the new buffer.
assert_eq!( assert_eq!(
snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)), snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
0 0
@ -3824,6 +3944,9 @@ mod tests {
]), ]),
vec![0, 0] vec![0, 0]
); );
// Refresh anchors from the old snapshot. The return value indicates that both
// anchors lost their original excerpt.
let refresh = let refresh =
snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]); snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
assert_eq!( assert_eq!(
@ -3837,10 +3960,10 @@ mod tests {
// Replace the middle excerpt with a smaller excerpt in buffer 2, // Replace the middle excerpt with a smaller excerpt in buffer 2,
// that intersects the old excerpt. // that intersects the old excerpt.
let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| { let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| {
multibuffer.remove_excerpts([&excerpt_id_3], cx); multibuffer.remove_excerpts([excerpt_id_3], cx);
multibuffer multibuffer
.insert_excerpts_after( .insert_excerpts_after(
&excerpt_id_3, excerpt_id_2,
buffer_2.clone(), buffer_2.clone(),
[ExcerptRange { [ExcerptRange {
context: 5..8, context: 5..8,
@ -3857,8 +3980,8 @@ mod tests {
assert_ne!(excerpt_id_5, excerpt_id_3); assert_ne!(excerpt_id_5, excerpt_id_3);
// Resolve some anchors from the previous snapshot in the new snapshot. // Resolve some anchors from the previous snapshot in the new snapshot.
// The anchor in the middle excerpt snaps to the beginning of the // The third anchor can't be resolved, since its excerpt has been removed,
// excerpt, since it is not // so it resolves to the same position as its predecessor.
let anchors = [ let anchors = [
snapshot_2.anchor_before(0), snapshot_2.anchor_before(0),
snapshot_2.anchor_after(2), snapshot_2.anchor_after(2),
@ -3867,7 +3990,7 @@ mod tests {
]; ];
assert_eq!( assert_eq!(
snapshot_3.summaries_for_anchors::<usize, _>(&anchors), snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
&[0, 2, 5, 13] &[0, 2, 9, 13]
); );
let new_anchors = snapshot_3.refresh_anchors(&anchors); let new_anchors = snapshot_3.refresh_anchors(&anchors);
@ -3889,7 +4012,7 @@ mod tests {
let mut buffers: Vec<ModelHandle<Buffer>> = Vec::new(); let mut buffers: Vec<ModelHandle<Buffer>> = Vec::new();
let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
let mut excerpt_ids = Vec::new(); let mut excerpt_ids = Vec::<ExcerptId>::new();
let mut expected_excerpts = Vec::<(ModelHandle<Buffer>, Range<text::Anchor>)>::new(); let mut expected_excerpts = Vec::<(ModelHandle<Buffer>, Range<text::Anchor>)>::new();
let mut anchors = Vec::new(); let mut anchors = Vec::new();
let mut old_versions = Vec::new(); let mut old_versions = Vec::new();
@ -3919,9 +4042,11 @@ mod tests {
.collect::<String>(), .collect::<String>(),
); );
} }
ids_to_remove.sort_unstable(); let snapshot = multibuffer.read(cx).read(cx);
ids_to_remove.sort_unstable_by(|a, b| a.cmp(&b, &snapshot));
drop(snapshot);
multibuffer.update(cx, |multibuffer, cx| { multibuffer.update(cx, |multibuffer, cx| {
multibuffer.remove_excerpts(&ids_to_remove, cx) multibuffer.remove_excerpts(ids_to_remove, cx)
}); });
} }
30..=39 if !expected_excerpts.is_empty() => { 30..=39 if !expected_excerpts.is_empty() => {
@ -3945,7 +4070,6 @@ mod tests {
// Ensure the newly-refreshed anchors point to a valid excerpt and don't // Ensure the newly-refreshed anchors point to a valid excerpt and don't
// overshoot its boundaries. // overshoot its boundaries.
assert_eq!(anchors.len(), prev_len); assert_eq!(anchors.len(), prev_len);
let mut cursor = multibuffer.excerpts.cursor::<Option<&ExcerptId>>();
for anchor in &anchors { for anchor in &anchors {
if anchor.excerpt_id == ExcerptId::min() if anchor.excerpt_id == ExcerptId::min()
|| anchor.excerpt_id == ExcerptId::max() || anchor.excerpt_id == ExcerptId::max()
@ -3953,8 +4077,7 @@ mod tests {
continue; continue;
} }
cursor.seek_forward(&Some(&anchor.excerpt_id), Bias::Left, &()); let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
let excerpt = cursor.item().unwrap();
assert_eq!(excerpt.id, anchor.excerpt_id); assert_eq!(excerpt.id, anchor.excerpt_id);
assert!(excerpt.contains(anchor)); assert!(excerpt.contains(anchor));
} }
@ -3994,7 +4117,7 @@ mod tests {
let excerpt_id = multibuffer.update(cx, |multibuffer, cx| { let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
multibuffer multibuffer
.insert_excerpts_after( .insert_excerpts_after(
&prev_excerpt_id, prev_excerpt_id,
buffer_handle.clone(), buffer_handle.clone(),
[ExcerptRange { [ExcerptRange {
context: start_ix..end_ix, context: start_ix..end_ix,

View file

@ -30,16 +30,16 @@ impl Anchor {
} }
} }
pub fn excerpt_id(&self) -> &ExcerptId { pub fn excerpt_id(&self) -> ExcerptId {
&self.excerpt_id self.excerpt_id
} }
pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering { pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id); let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot);
if excerpt_id_cmp.is_eq() { if excerpt_id_cmp.is_eq() {
if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() { if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() {
Ordering::Equal Ordering::Equal
} else if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer) self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer)
} else { } else {
Ordering::Equal Ordering::Equal
@ -51,7 +51,7 @@ impl Anchor {
pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor { pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
if self.text_anchor.bias != Bias::Left { if self.text_anchor.bias != Bias::Left {
if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
return Self { return Self {
buffer_id: self.buffer_id, buffer_id: self.buffer_id,
excerpt_id: self.excerpt_id.clone(), excerpt_id: self.excerpt_id.clone(),
@ -64,7 +64,7 @@ impl Anchor {
pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor { pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
if self.text_anchor.bias != Bias::Right { if self.text_anchor.bias != Bias::Right {
if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
return Self { return Self {
buffer_id: self.buffer_id, buffer_id: self.buffer_id,
excerpt_id: self.excerpt_id.clone(), excerpt_id: self.excerpt_id.clone(),

View file

@ -3,8 +3,8 @@ use smallvec::{smallvec, SmallVec};
use std::iter; use std::iter;
lazy_static! { lazy_static! {
pub static ref MIN: Locator = Locator::min(); static ref MIN: Locator = Locator::min();
pub static ref MAX: Locator = Locator::max(); static ref MAX: Locator = Locator::max();
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -19,6 +19,14 @@ impl Locator {
Self(smallvec![u64::MAX]) Self(smallvec![u64::MAX])
} }
pub fn min_ref() -> &'static Self {
&*MIN
}
pub fn max_ref() -> &'static Self {
&*MAX
}
pub fn assign(&mut self, other: &Self) { pub fn assign(&mut self, other: &Self) {
self.0.resize(other.0.len(), 0); self.0.resize(other.0.len(), 0);
self.0.copy_from_slice(&other.0); self.0.copy_from_slice(&other.0);

View file

@ -1770,9 +1770,9 @@ impl BufferSnapshot {
fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator { fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator {
if *anchor == Anchor::MIN { if *anchor == Anchor::MIN {
&locator::MIN Locator::min_ref()
} else if *anchor == Anchor::MAX { } else if *anchor == Anchor::MAX {
&locator::MAX Locator::max_ref()
} else { } else {
let anchor_key = InsertionFragmentKey { let anchor_key = InsertionFragmentKey {
timestamp: anchor.timestamp, timestamp: anchor.timestamp,