@@ -1,7 +1,11 @@
mod anchor;
#[cfg(test)]
mod multi_buffer_tests;
+mod path_key;
mod position;
+mod transaction;
+
+use self::transaction::History;
pub use anchor::{Anchor, AnchorRangeExt, Offset};
pub use position::{TypedOffset, TypedPoint, TypedRow};
@@ -13,7 +17,7 @@ use buffer_diff::{
};
use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet};
-use gpui::{App, AppContext as _, Context, Entity, EntityId, EventEmitter, Task};
+use gpui::{App, Context, Entity, EntityId, EventEmitter};
use itertools::Itertools;
use language::{
AutoindentMode, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, CharClassifier,
@@ -24,6 +28,9 @@ use language::{
language_settings::{LanguageSettings, language_settings},
};
+#[cfg(any(test, feature = "test-support"))]
+use gpui::AppContext as _;
+
use rope::DimensionPair;
use smallvec::SmallVec;
use smol::future::yield_now;
@@ -40,7 +47,7 @@ use std::{
rc::Rc,
str,
sync::Arc,
- time::{Duration, Instant},
+ time::Duration,
};
use sum_tree::{Bias, Cursor, Dimension, Dimensions, SumTree, Summary, TreeMap};
use text::{
@@ -49,9 +56,9 @@ use text::{
subscription::{Subscription, Topic},
};
use theme::SyntaxTheme;
-use util::{post_inc, rel_path::RelPath};
+use util::post_inc;
-const NEWLINES: &[u8] = &[b'\n'; rope::Chunk::MASK_BITS];
+pub use self::path_key::PathKey;
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExcerptId(u32);
@@ -163,35 +170,6 @@ impl MultiBufferDiffHunk {
}
}
-#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)]
-pub struct PathKey {
- // Used by the derived PartialOrd & Ord
- pub sort_prefix: Option<u64>,
- pub path: Arc<RelPath>,
-}
-
-impl PathKey {
- pub fn with_sort_prefix(sort_prefix: u64, path: Arc<RelPath>) -> Self {
- Self {
- sort_prefix: Some(sort_prefix),
- path,
- }
- }
-
- pub fn for_buffer(buffer: &Entity<Buffer>, cx: &App) -> Self {
- if let Some(file) = buffer.read(cx).file() {
- Self::with_sort_prefix(file.worktree_id(cx).to_proto(), file.path().clone())
- } else {
- Self {
- sort_prefix: None,
- path: RelPath::unix(&buffer.entity_id().to_string())
- .unwrap()
- .into_arc(),
- }
- }
- }
-}
-
pub type MultiBufferPoint = Point;
type ExcerptOffset = TypedOffset<Excerpt>;
type ExcerptPoint = TypedPoint<Excerpt>;
@@ -213,37 +191,13 @@ impl std::ops::Add<usize> for MultiBufferRow {
}
}
-#[derive(Clone)]
-struct History {
- next_transaction_id: TransactionId,
- undo_stack: Vec<Transaction>,
- redo_stack: Vec<Transaction>,
- transaction_depth: usize,
- group_interval: Duration,
-}
-
-#[derive(Clone)]
-struct Transaction {
- id: TransactionId,
- buffer_transactions: HashMap<BufferId, text::TransactionId>,
- first_edit_at: Instant,
- last_edit_at: Instant,
- suppress_grouping: bool,
-}
-
pub trait ToOffset: 'static + fmt::Debug {
fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize;
-}
-
-pub trait ToOffsetUtf16: 'static + fmt::Debug {
fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16;
}
pub trait ToPoint: 'static + fmt::Debug {
fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point;
-}
-
-pub trait ToPointUtf16: 'static + fmt::Debug {
fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16;
}
@@ -281,24 +235,20 @@ impl DiffState {
/// The contents of a [`MultiBuffer`] at a single point in time.
#[derive(Clone, Default)]
pub struct MultiBufferSnapshot {
- singleton: bool,
- /* mut */
excerpts: SumTree<Excerpt>,
- /* mut */
- excerpt_ids: SumTree<ExcerptIdMapping>,
diffs: TreeMap<BufferId, BufferDiffSnapshot>,
diff_transforms: SumTree<DiffTransform>,
- /* mut */
- replaced_excerpts: TreeMap<ExcerptId, ExcerptId>,
- /* mut */
- trailing_excerpt_update_count: usize,
- all_diff_hunks_expanded: bool,
non_text_state_update_count: usize,
edit_count: usize,
- /* mut */
is_dirty: bool,
has_deleted_file: bool,
has_conflict: bool,
+ /// immutable fields
+ singleton: bool,
+ excerpt_ids: SumTree<ExcerptIdMapping>,
+ replaced_excerpts: TreeMap<ExcerptId, ExcerptId>,
+ trailing_excerpt_update_count: usize,
+ all_diff_hunks_expanded: bool,
show_headers: bool,
}
@@ -555,7 +505,7 @@ struct MultiBufferRegion<'a, D: TextDimension> {
struct ExcerptChunks<'a> {
excerpt_id: ExcerptId,
content_chunks: BufferChunks<'a>,
- footer_height: usize,
+ has_footer: bool,
}
#[derive(Debug)]
@@ -660,13 +610,7 @@ impl MultiBuffer {
excerpts_by_path: Default::default(),
paths_by_excerpt: Default::default(),
buffer_changed_since_sync: Default::default(),
- history: History {
- next_transaction_id: clock::Lamport::MIN,
- undo_stack: Vec::new(),
- redo_stack: Vec::new(),
- transaction_depth: 0,
- group_interval: Duration::from_millis(300),
- },
+ history: History::default(),
}
}
@@ -712,6 +656,10 @@ impl MultiBuffer {
}
}
+ pub fn set_group_interval(&mut self, group_interval: Duration) {
+ self.history.set_group_interval(group_interval);
+ }
+
pub fn with_title(mut self, title: String) -> Self {
self.title = Some(title);
self
@@ -770,17 +718,8 @@ impl MultiBuffer {
self.buffers.is_empty()
}
- pub fn symbols_containing<T: ToOffset>(
- &self,
- offset: T,
- theme: Option<&SyntaxTheme>,
- cx: &App,
- ) -> Option<(BufferId, Vec<OutlineItem<Anchor>>)> {
- self.read(cx).symbols_containing(offset, theme)
- }
-
pub fn edit<I, S, T>(
- &self,
+ &mut self,
edits: I,
autoindent_mode: Option<AutoindentMode>,
cx: &mut Context<Self>,
@@ -789,11 +728,15 @@ impl MultiBuffer {
S: ToOffset,
T: Into<Arc<str>>,
{
- let snapshot = self.read(cx);
+ if self.read_only() || self.buffers.is_empty() {
+ return;
+ }
+ self.sync_mut(cx);
let edits = edits
.into_iter()
.map(|(range, new_text)| {
- let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
+ let mut range = range.start.to_offset(self.snapshot.get_mut())
+ ..range.end.to_offset(self.snapshot.get_mut());
if range.start > range.end {
mem::swap(&mut range.start, &mut range.end);
}
@@ -801,20 +744,15 @@ impl MultiBuffer {
})
.collect::<Vec<_>>();
- return edit_internal(self, snapshot, edits, autoindent_mode, cx);
+ return edit_internal(self, edits, autoindent_mode, cx);
// Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
fn edit_internal(
- this: &MultiBuffer,
- snapshot: Ref<MultiBufferSnapshot>,
+ this: &mut MultiBuffer,
edits: Vec<(Range<usize>, Arc<str>)>,
mut autoindent_mode: Option<AutoindentMode>,
cx: &mut Context<MultiBuffer>,
) {
- if this.read_only() || this.buffers.is_empty() {
- return;
- }
-
let original_indent_columns = match &mut autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
@@ -822,9 +760,11 @@ impl MultiBuffer {
_ => Default::default(),
};
- let (buffer_edits, edited_excerpt_ids) =
- this.convert_edits_to_buffer_edits(edits, &snapshot, &original_indent_columns);
- drop(snapshot);
+ let (buffer_edits, edited_excerpt_ids) = MultiBuffer::convert_edits_to_buffer_edits(
+ edits,
+ this.snapshot.get_mut(),
+ &original_indent_columns,
+ );
let mut buffer_ids = Vec::with_capacity(buffer_edits.len());
for (buffer_id, mut edits) in buffer_edits {
@@ -908,7 +848,6 @@ impl MultiBuffer {
}
fn convert_edits_to_buffer_edits(
- &self,
edits: Vec<(Range<usize>, Arc<str>)>,
snapshot: &MultiBufferSnapshot,
original_indent_columns: &[Option<u32>],
@@ -1028,17 +967,21 @@ impl MultiBuffer {
(buffer_edits, edited_excerpt_ids)
}
- pub fn autoindent_ranges<I, S>(&self, ranges: I, cx: &mut Context<Self>)
+ pub fn autoindent_ranges<I, S>(&mut self, ranges: I, cx: &mut Context<Self>)
where
I: IntoIterator<Item = Range<S>>,
S: ToOffset,
{
- let snapshot = self.read(cx);
+ if self.read_only() || self.buffers.is_empty() {
+ return;
+ }
+ self.sync_mut(cx);
let empty = Arc::<str>::from("");
let edits = ranges
.into_iter()
.map(|range| {
- let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
+ let mut range = range.start.to_offset(self.snapshot.get_mut())
+ ..range.end.to_offset(&self.snapshot.get_mut());
if range.start > range.end {
mem::swap(&mut range.start, &mut range.end);
}
@@ -1046,21 +989,15 @@ impl MultiBuffer {
})
.collect::<Vec<_>>();
- return autoindent_ranges_internal(self, snapshot, edits, cx);
+ return autoindent_ranges_internal(self, edits, cx);
fn autoindent_ranges_internal(
- this: &MultiBuffer,
- snapshot: Ref<MultiBufferSnapshot>,
+ this: &mut MultiBuffer,
edits: Vec<(Range<usize>, Arc<str>)>,
cx: &mut Context<MultiBuffer>,
) {
- if this.read_only() || this.buffers.is_empty() {
- return;
- }
-
let (buffer_edits, edited_excerpt_ids) =
- this.convert_edits_to_buffer_edits(edits, &snapshot, &[]);
- drop(snapshot);
+ MultiBuffer::convert_edits_to_buffer_edits(edits, this.snapshot.get_mut(), &[]);
let mut buffer_ids = Vec::new();
for (buffer_id, mut edits) in buffer_edits {
@@ -1090,9 +1027,9 @@ impl MultiBuffer {
}
}
- // Inserts newlines at the given position to create an empty line, returning the start of the new line.
- // You can also request the insertion of empty lines above and below the line starting at the returned point.
- // Panics if the given position is invalid.
+ /// Inserts newlines at the given position to create an empty line, returning the start of the new line.
+ /// You can also request the insertion of empty lines above and below the line starting at the returned point.
+ /// Panics if the given position is invalid.
pub fn insert_empty_line(
&mut self,
position: impl ToPoint,
@@ -1110,186 +1047,6 @@ impl MultiBuffer {
multibuffer_point + (empty_line_start - buffer_point)
}
- pub fn start_transaction(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
- self.start_transaction_at(Instant::now(), cx)
- }
-
- pub fn start_transaction_at(
- &mut self,
- now: Instant,
- cx: &mut Context<Self>,
- ) -> Option<TransactionId> {
- if let Some(buffer) = self.as_singleton() {
- return buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
- }
-
- for BufferState { buffer, .. } in self.buffers.values() {
- buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
- }
- self.history.start_transaction(now)
- }
-
- pub fn last_transaction_id(&self, cx: &App) -> Option<TransactionId> {
- if let Some(buffer) = self.as_singleton() {
- buffer
- .read(cx)
- .peek_undo_stack()
- .map(|history_entry| history_entry.transaction_id())
- } else {
- let last_transaction = self.history.undo_stack.last()?;
- Some(last_transaction.id)
- }
- }
-
- pub fn end_transaction(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
- self.end_transaction_at(Instant::now(), cx)
- }
-
- pub fn end_transaction_at(
- &mut self,
- now: Instant,
- cx: &mut Context<Self>,
- ) -> Option<TransactionId> {
- if let Some(buffer) = self.as_singleton() {
- return buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx));
- }
-
- let mut buffer_transactions = HashMap::default();
- for BufferState { buffer, .. } in self.buffers.values() {
- if let Some(transaction_id) =
- buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
- {
- buffer_transactions.insert(buffer.read(cx).remote_id(), transaction_id);
- }
- }
-
- if self.history.end_transaction(now, buffer_transactions) {
- let transaction_id = self.history.group().unwrap();
- Some(transaction_id)
- } else {
- None
- }
- }
-
- pub fn edited_ranges_for_transaction<D>(
- &self,
- transaction_id: TransactionId,
- cx: &App,
- ) -> Vec<Range<D>>
- where
- D: TextDimension + Ord + Sub<D, Output = D>,
- {
- let Some(transaction) = self.history.transaction(transaction_id) else {
- return Vec::new();
- };
-
- let mut ranges = Vec::new();
- let snapshot = self.read(cx);
- let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
-
- for (buffer_id, buffer_transaction) in &transaction.buffer_transactions {
- let Some(buffer_state) = self.buffers.get(buffer_id) else {
- continue;
- };
-
- let buffer = buffer_state.buffer.read(cx);
- for range in buffer.edited_ranges_for_transaction_id::<D>(*buffer_transaction) {
- for excerpt_id in &buffer_state.excerpts {
- cursor.seek(excerpt_id, Bias::Left);
- if let Some(excerpt) = cursor.item()
- && excerpt.locator == *excerpt_id
- {
- let excerpt_buffer_start = excerpt.range.context.start.summary::<D>(buffer);
- let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(buffer);
- let excerpt_range = excerpt_buffer_start..excerpt_buffer_end;
- if excerpt_range.contains(&range.start)
- && excerpt_range.contains(&range.end)
- {
- let excerpt_start = D::from_text_summary(&cursor.start().text);
-
- let mut start = excerpt_start;
- start.add_assign(&(range.start - excerpt_buffer_start));
- let mut end = excerpt_start;
- end.add_assign(&(range.end - excerpt_buffer_start));
-
- ranges.push(start..end);
- break;
- }
- }
- }
- }
- }
-
- ranges.sort_by_key(|range| range.start);
- ranges
- }
-
- pub fn merge_transactions(
- &mut self,
- transaction: TransactionId,
- destination: TransactionId,
- cx: &mut Context<Self>,
- ) {
- if let Some(buffer) = self.as_singleton() {
- buffer.update(cx, |buffer, _| {
- buffer.merge_transactions(transaction, destination)
- });
- } else if let Some(transaction) = self.history.forget(transaction)
- && let Some(destination) = self.history.transaction_mut(destination)
- {
- for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions {
- if let Some(destination_buffer_transaction_id) =
- destination.buffer_transactions.get(&buffer_id)
- {
- if let Some(state) = self.buffers.get(&buffer_id) {
- state.buffer.update(cx, |buffer, _| {
- buffer.merge_transactions(
- buffer_transaction_id,
- *destination_buffer_transaction_id,
- )
- });
- }
- } else {
- destination
- .buffer_transactions
- .insert(buffer_id, buffer_transaction_id);
- }
- }
- }
- }
-
- pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
- self.history.finalize_last_transaction();
- for BufferState { buffer, .. } in self.buffers.values() {
- buffer.update(cx, |buffer, _| {
- buffer.finalize_last_transaction();
- });
- }
- }
-
- pub fn push_transaction<'a, T>(&mut self, buffer_transactions: T, cx: &Context<Self>)
- where
- T: IntoIterator<Item = (&'a Entity<Buffer>, &'a language::Transaction)>,
- {
- self.history
- .push_transaction(buffer_transactions, Instant::now(), cx);
- self.history.finalize_last_transaction();
- }
-
- pub fn group_until_transaction(
- &mut self,
- transaction_id: TransactionId,
- cx: &mut Context<Self>,
- ) {
- if let Some(buffer) = self.as_singleton() {
- buffer.update(cx, |buffer, _| {
- buffer.group_until_transaction(transaction_id)
- });
- } else {
- self.history.group_until(transaction_id);
- }
- }
-
pub fn set_active_selections(
&self,
selections: &[Selection<Anchor>],
@@ -1357,325 +1114,30 @@ impl MultiBuffer {
}
}
Some(selection)
- }));
- buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx);
- });
- }
- }
-
- pub fn remove_active_selections(&self, cx: &mut Context<Self>) {
- for buffer in self.buffers.values() {
- buffer
- .buffer
- .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
- }
- }
-
- pub fn undo(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
- let mut transaction_id = None;
- if let Some(buffer) = self.as_singleton() {
- transaction_id = buffer.update(cx, |buffer, cx| buffer.undo(cx));
- } else {
- while let Some(transaction) = self.history.pop_undo() {
- let mut undone = false;
- for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions {
- if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
- undone |= buffer.update(cx, |buffer, cx| {
- let undo_to = *buffer_transaction_id;
- if let Some(entry) = buffer.peek_undo_stack() {
- *buffer_transaction_id = entry.transaction_id();
- }
- buffer.undo_to_transaction(undo_to, cx)
- });
- }
- }
-
- if undone {
- transaction_id = Some(transaction.id);
- break;
- }
- }
- }
-
- if let Some(transaction_id) = transaction_id {
- cx.emit(Event::TransactionUndone { transaction_id });
- }
-
- transaction_id
- }
-
- pub fn redo(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
- if let Some(buffer) = self.as_singleton() {
- return buffer.update(cx, |buffer, cx| buffer.redo(cx));
- }
-
- while let Some(transaction) = self.history.pop_redo() {
- let mut redone = false;
- for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions {
- if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
- redone |= buffer.update(cx, |buffer, cx| {
- let redo_to = *buffer_transaction_id;
- if let Some(entry) = buffer.peek_redo_stack() {
- *buffer_transaction_id = entry.transaction_id();
- }
- buffer.redo_to_transaction(redo_to, cx)
- });
- }
- }
-
- if redone {
- return Some(transaction.id);
- }
- }
-
- None
- }
-
- pub fn undo_transaction(&mut self, transaction_id: TransactionId, cx: &mut Context<Self>) {
- if let Some(buffer) = self.as_singleton() {
- buffer.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
- } else if let Some(transaction) = self.history.remove_from_undo(transaction_id) {
- for (buffer_id, transaction_id) in &transaction.buffer_transactions {
- if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
- buffer.update(cx, |buffer, cx| {
- buffer.undo_transaction(*transaction_id, cx)
- });
- }
- }
- }
- }
-
- pub fn forget_transaction(&mut self, transaction_id: TransactionId, cx: &mut Context<Self>) {
- if let Some(buffer) = self.as_singleton() {
- buffer.update(cx, |buffer, _| {
- buffer.forget_transaction(transaction_id);
- });
- } else if let Some(transaction) = self.history.forget(transaction_id) {
- for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions {
- if let Some(state) = self.buffers.get_mut(&buffer_id) {
- state.buffer.update(cx, |buffer, _| {
- buffer.forget_transaction(buffer_transaction_id);
- });
- }
- }
- }
- }
-
- pub fn push_excerpts<O>(
- &mut self,
- buffer: Entity<Buffer>,
- ranges: impl IntoIterator<Item = ExcerptRange<O>>,
- cx: &mut Context<Self>,
- ) -> Vec<ExcerptId>
- where
- O: text::ToOffset,
- {
- self.insert_excerpts_after(ExcerptId::max(), buffer, ranges, cx)
- }
-
- pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
- let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
- let snapshot = self.snapshot(cx);
- let excerpt = snapshot.excerpt(*excerpt_id)?;
- Some(Anchor::in_buffer(
- *excerpt_id,
- excerpt.buffer_id,
- excerpt.range.context.start,
- ))
- }
-
- pub fn excerpt_paths(&self) -> impl Iterator<Item = &PathKey> {
- self.excerpts_by_path.keys()
- }
-
- fn expand_excerpts_with_paths(
- &mut self,
- ids: impl IntoIterator<Item = ExcerptId>,
- line_count: u32,
- direction: ExpandExcerptDirection,
- cx: &mut Context<Self>,
- ) {
- let grouped = ids
- .into_iter()
- .chunk_by(|id| self.paths_by_excerpt.get(id).cloned())
- .into_iter()
- .flat_map(|(k, v)| Some((k?, v.into_iter().collect::<Vec<_>>())))
- .collect::<Vec<_>>();
- let snapshot = self.snapshot(cx);
-
- for (path, ids) in grouped.into_iter() {
- let Some(excerpt_ids) = self.excerpts_by_path.get(&path) else {
- continue;
- };
-
- let ids_to_expand = HashSet::from_iter(ids);
- let expanded_ranges = excerpt_ids.iter().filter_map(|excerpt_id| {
- let excerpt = snapshot.excerpt(*excerpt_id)?;
-
- let mut context = excerpt.range.context.to_point(&excerpt.buffer);
- if ids_to_expand.contains(excerpt_id) {
- match direction {
- ExpandExcerptDirection::Up => {
- context.start.row = context.start.row.saturating_sub(line_count);
- context.start.column = 0;
- }
- ExpandExcerptDirection::Down => {
- context.end.row =
- (context.end.row + line_count).min(excerpt.buffer.max_point().row);
- context.end.column = excerpt.buffer.line_len(context.end.row);
- }
- ExpandExcerptDirection::UpAndDown => {
- context.start.row = context.start.row.saturating_sub(line_count);
- context.start.column = 0;
- context.end.row =
- (context.end.row + line_count).min(excerpt.buffer.max_point().row);
- context.end.column = excerpt.buffer.line_len(context.end.row);
- }
- }
- }
-
- Some(ExcerptRange {
- context,
- primary: excerpt.range.primary.to_point(&excerpt.buffer),
- })
- });
- let mut merged_ranges: Vec<ExcerptRange<Point>> = Vec::new();
- for range in expanded_ranges {
- if let Some(last_range) = merged_ranges.last_mut()
- && last_range.context.end >= range.context.start
- {
- last_range.context.end = range.context.end;
- continue;
- }
- merged_ranges.push(range)
- }
- let Some(excerpt_id) = excerpt_ids.first() else {
- continue;
- };
- let Some(buffer_id) = &snapshot.buffer_id_for_excerpt(*excerpt_id) else {
- continue;
- };
-
- let Some(buffer) = self.buffers.get(buffer_id).map(|b| b.buffer.clone()) else {
- continue;
- };
-
- let buffer_snapshot = buffer.read(cx).snapshot();
- self.update_path_excerpts(path.clone(), buffer, &buffer_snapshot, merged_ranges, cx);
- }
- }
-
- /// Sets excerpts, returns `true` if at least one new excerpt was added.
- pub fn set_excerpts_for_path(
- &mut self,
- path: PathKey,
- buffer: Entity<Buffer>,
- ranges: impl IntoIterator<Item = Range<Point>>,
- context_line_count: u32,
- cx: &mut Context<Self>,
- ) -> (Vec<Range<Anchor>>, bool) {
- let buffer_snapshot = buffer.read(cx).snapshot();
- let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
-
- let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
- self.set_merged_excerpt_ranges_for_path(
- path,
- buffer,
- excerpt_ranges,
- &buffer_snapshot,
- new,
- counts,
- cx,
- )
- }
-
- pub fn set_excerpt_ranges_for_path(
- &mut self,
- path: PathKey,
- buffer: Entity<Buffer>,
- buffer_snapshot: &BufferSnapshot,
- excerpt_ranges: Vec<ExcerptRange<Point>>,
- cx: &mut Context<Self>,
- ) -> (Vec<Range<Anchor>>, bool) {
- let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
- self.set_merged_excerpt_ranges_for_path(
- path,
- buffer,
- excerpt_ranges,
- buffer_snapshot,
- new,
- counts,
- cx,
- )
- }
-
- pub fn set_anchored_excerpts_for_path(
- &self,
- path_key: PathKey,
- buffer: Entity<Buffer>,
- ranges: Vec<Range<text::Anchor>>,
- context_line_count: u32,
- cx: &mut Context<Self>,
- ) -> Task<Vec<Range<Anchor>>> {
- let buffer_snapshot = buffer.read(cx).snapshot();
- cx.spawn(async move |multi_buffer, cx| {
- let snapshot = buffer_snapshot.clone();
- let (excerpt_ranges, new, counts) = cx
- .background_spawn(async move {
- let ranges = ranges.into_iter().map(|range| range.to_point(&snapshot));
- let excerpt_ranges =
- build_excerpt_ranges(ranges, context_line_count, &snapshot);
- let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
- (excerpt_ranges, new, counts)
- })
- .await;
-
- multi_buffer
- .update(cx, move |multi_buffer, cx| {
- let (ranges, _) = multi_buffer.set_merged_excerpt_ranges_for_path(
- path_key,
- buffer,
- excerpt_ranges,
- &buffer_snapshot,
- new,
- counts,
- cx,
- );
- ranges
- })
- .ok()
- .unwrap_or_default()
- })
+ }));
+ buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx);
+ });
+ }
+ }
+
+ pub fn remove_active_selections(&self, cx: &mut Context<Self>) {
+ for buffer in self.buffers.values() {
+ buffer
+ .buffer
+ .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
+ }
}
- /// Sets excerpts, returns `true` if at least one new excerpt was added.
- fn set_merged_excerpt_ranges_for_path(
+ pub fn push_excerpts<O>(
&mut self,
- path: PathKey,
buffer: Entity<Buffer>,
- ranges: Vec<ExcerptRange<Point>>,
- buffer_snapshot: &BufferSnapshot,
- new: Vec<ExcerptRange<Point>>,
- counts: Vec<usize>,
+ ranges: impl IntoIterator<Item = ExcerptRange<O>>,
cx: &mut Context<Self>,
- ) -> (Vec<Range<Anchor>>, bool) {
- let (excerpt_ids, added_a_new_excerpt) =
- self.update_path_excerpts(path, buffer, buffer_snapshot, new, cx);
-
- let mut result = Vec::new();
- let mut ranges = ranges.into_iter();
- for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(counts.into_iter()) {
- for range in ranges.by_ref().take(range_count) {
- let range = Anchor::range_in_buffer(
- excerpt_id,
- buffer_snapshot.remote_id(),
- buffer_snapshot.anchor_before(&range.primary.start)
- ..buffer_snapshot.anchor_after(&range.primary.end),
- );
- result.push(range)
- }
- }
- (result, added_a_new_excerpt)
+ ) -> Vec<ExcerptId>
+ where
+ O: text::ToOffset,
+ {
+ self.insert_excerpts_after(ExcerptId::max(), buffer, ranges, cx)
}
fn merge_excerpt_ranges<'a>(
@@ -1703,174 +1165,6 @@ impl MultiBuffer {
(merged_ranges, counts)
}
- fn update_path_excerpts(
- &mut self,
- path: PathKey,
- buffer: Entity<Buffer>,
- buffer_snapshot: &BufferSnapshot,
- new: Vec<ExcerptRange<Point>>,
- cx: &mut Context<Self>,
- ) -> (Vec<ExcerptId>, bool) {
- let mut insert_after = self
- .excerpts_by_path
- .range(..path.clone())
- .next_back()
- .map(|(_, value)| *value.last().unwrap())
- .unwrap_or(ExcerptId::min());
-
- let existing = self
- .excerpts_by_path
- .get(&path)
- .cloned()
- .unwrap_or_default();
-
- let mut new_iter = new.into_iter().peekable();
- let mut existing_iter = existing.into_iter().peekable();
-
- let mut excerpt_ids = Vec::new();
- let mut to_remove = Vec::new();
- let mut to_insert: Vec<(ExcerptId, ExcerptRange<Point>)> = Vec::new();
- let mut added_a_new_excerpt = false;
- let snapshot = self.snapshot(cx);
-
- let mut next_excerpt_id =
- if let Some(last_entry) = self.snapshot.borrow().excerpt_ids.last() {
- last_entry.id.0 + 1
- } else {
- 1
- };
-
- let mut next_excerpt_id = move || ExcerptId(post_inc(&mut next_excerpt_id));
-
- let mut excerpts_cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
- excerpts_cursor.next();
-
- loop {
- let new = new_iter.peek();
- let existing = if let Some(existing_id) = existing_iter.peek() {
- let locator = snapshot.excerpt_locator_for_id(*existing_id);
- excerpts_cursor.seek_forward(&Some(locator), Bias::Left);
- if let Some(excerpt) = excerpts_cursor.item() {
- if excerpt.buffer_id != buffer_snapshot.remote_id() {
- to_remove.push(*existing_id);
- existing_iter.next();
- continue;
- }
- Some((
- *existing_id,
- excerpt.range.context.to_point(buffer_snapshot),
- ))
- } else {
- None
- }
- } else {
- None
- };
-
- if let Some((last_id, last)) = to_insert.last_mut() {
- if let Some(new) = new
- && last.context.end >= new.context.start
- {
- last.context.end = last.context.end.max(new.context.end);
- excerpt_ids.push(*last_id);
- new_iter.next();
- continue;
- }
- if let Some((existing_id, existing_range)) = &existing
- && last.context.end >= existing_range.start
- {
- last.context.end = last.context.end.max(existing_range.end);
- to_remove.push(*existing_id);
- self.snapshot
- .get_mut()
- .replaced_excerpts
- .insert(*existing_id, *last_id);
- existing_iter.next();
- continue;
- }
- }
-
- match (new, existing) {
- (None, None) => break,
- (None, Some((existing_id, _))) => {
- existing_iter.next();
- to_remove.push(existing_id);
- continue;
- }
- (Some(_), None) => {
- added_a_new_excerpt = true;
- let new_id = next_excerpt_id();
- excerpt_ids.push(new_id);
- to_insert.push((new_id, new_iter.next().unwrap()));
- continue;
- }
- (Some(new), Some((_, existing_range))) => {
- if existing_range.end < new.context.start {
- let existing_id = existing_iter.next().unwrap();
- to_remove.push(existing_id);
- continue;
- } else if existing_range.start > new.context.end {
- let new_id = next_excerpt_id();
- excerpt_ids.push(new_id);
- to_insert.push((new_id, new_iter.next().unwrap()));
- continue;
- }
-
- if existing_range.start == new.context.start
- && existing_range.end == new.context.end
- {
- self.insert_excerpts_with_ids_after(
- insert_after,
- buffer.clone(),
- mem::take(&mut to_insert),
- cx,
- );
- insert_after = existing_iter.next().unwrap();
- excerpt_ids.push(insert_after);
- new_iter.next();
- } else {
- let existing_id = existing_iter.next().unwrap();
- let new_id = next_excerpt_id();
- self.snapshot
- .get_mut()
- .replaced_excerpts
- .insert(existing_id, new_id);
- to_remove.push(existing_id);
- let mut range = new_iter.next().unwrap();
- range.context.start = range.context.start.min(existing_range.start);
- range.context.end = range.context.end.max(existing_range.end);
- excerpt_ids.push(new_id);
- to_insert.push((new_id, range));
- }
- }
- };
- }
-
- self.insert_excerpts_with_ids_after(insert_after, buffer, to_insert, cx);
- self.remove_excerpts(to_remove, cx);
- if excerpt_ids.is_empty() {
- self.excerpts_by_path.remove(&path);
- } else {
- for excerpt_id in &excerpt_ids {
- self.paths_by_excerpt.insert(*excerpt_id, path.clone());
- }
- self.excerpts_by_path
- .insert(path, excerpt_ids.iter().dedup().cloned().collect());
- }
-
- (excerpt_ids, added_a_new_excerpt)
- }
-
- pub fn paths(&self) -> impl Iterator<Item = PathKey> + '_ {
- self.excerpts_by_path.keys().cloned()
- }
-
- pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
- if let Some(to_remove) = self.excerpts_by_path.remove(&path) {
- self.remove_excerpts(to_remove, cx)
- }
- }
-
pub fn insert_excerpts_after<O>(
&mut self,
prev_excerpt_id: ExcerptId,
@@ -0,0 +1,417 @@
+use std::{mem, ops::Range, sync::Arc};
+
+use collections::HashSet;
+use gpui::{App, AppContext, Context, Entity, Task};
+use itertools::Itertools;
+use language::{Buffer, BufferSnapshot};
+use rope::Point;
+use text::{Bias, OffsetRangeExt, locator::Locator};
+use util::{post_inc, rel_path::RelPath};
+
+use crate::{
+ Anchor, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, build_excerpt_ranges,
+};
+
+#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)]
+pub struct PathKey {
+ // Used by the derived PartialOrd & Ord
+ pub sort_prefix: Option<u64>,
+ pub path: Arc<RelPath>,
+}
+
+impl PathKey {
+ pub fn with_sort_prefix(sort_prefix: u64, path: Arc<RelPath>) -> Self {
+ Self {
+ sort_prefix: Some(sort_prefix),
+ path,
+ }
+ }
+
+ pub fn for_buffer(buffer: &Entity<Buffer>, cx: &App) -> Self {
+ if let Some(file) = buffer.read(cx).file() {
+ Self::with_sort_prefix(file.worktree_id(cx).to_proto(), file.path().clone())
+ } else {
+ Self {
+ sort_prefix: None,
+ path: RelPath::unix(&buffer.entity_id().to_string())
+ .unwrap()
+ .into_arc(),
+ }
+ }
+ }
+}
+
+impl MultiBuffer {
+ pub fn paths(&self) -> impl Iterator<Item = PathKey> + '_ {
+ self.excerpts_by_path.keys().cloned()
+ }
+
+ pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
+ if let Some(to_remove) = self.excerpts_by_path.remove(&path) {
+ self.remove_excerpts(to_remove, cx)
+ }
+ }
+
+ pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
+ let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
+ let snapshot = self.read(cx);
+ let excerpt = snapshot.excerpt(*excerpt_id)?;
+ Some(Anchor::in_buffer(
+ *excerpt_id,
+ excerpt.buffer_id,
+ excerpt.range.context.start,
+ ))
+ }
+
+ pub fn excerpt_paths(&self) -> impl Iterator<Item = &PathKey> {
+ self.excerpts_by_path.keys()
+ }
+
+ /// Sets excerpts, returns `true` if at least one new excerpt was added.
+ pub fn set_excerpts_for_path(
+ &mut self,
+ path: PathKey,
+ buffer: Entity<Buffer>,
+ ranges: impl IntoIterator<Item = Range<Point>>,
+ context_line_count: u32,
+ cx: &mut Context<Self>,
+ ) -> (Vec<Range<Anchor>>, bool) {
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
+
+ let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
+ self.set_merged_excerpt_ranges_for_path(
+ path,
+ buffer,
+ excerpt_ranges,
+ &buffer_snapshot,
+ new,
+ counts,
+ cx,
+ )
+ }
+
+ pub fn set_excerpt_ranges_for_path(
+ &mut self,
+ path: PathKey,
+ buffer: Entity<Buffer>,
+ buffer_snapshot: &BufferSnapshot,
+ excerpt_ranges: Vec<ExcerptRange<Point>>,
+ cx: &mut Context<Self>,
+ ) -> (Vec<Range<Anchor>>, bool) {
+ let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
+ self.set_merged_excerpt_ranges_for_path(
+ path,
+ buffer,
+ excerpt_ranges,
+ buffer_snapshot,
+ new,
+ counts,
+ cx,
+ )
+ }
+
+ pub fn set_anchored_excerpts_for_path(
+ &self,
+ path_key: PathKey,
+ buffer: Entity<Buffer>,
+ ranges: Vec<Range<text::Anchor>>,
+ context_line_count: u32,
+ cx: &mut Context<Self>,
+ ) -> Task<Vec<Range<Anchor>>> {
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ cx.spawn(async move |multi_buffer, cx| {
+ let snapshot = buffer_snapshot.clone();
+ let (excerpt_ranges, new, counts) = cx
+ .background_spawn(async move {
+ let ranges = ranges.into_iter().map(|range| range.to_point(&snapshot));
+ let excerpt_ranges =
+ build_excerpt_ranges(ranges, context_line_count, &snapshot);
+ let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
+ (excerpt_ranges, new, counts)
+ })
+ .await;
+
+ multi_buffer
+ .update(cx, move |multi_buffer, cx| {
+ let (ranges, _) = multi_buffer.set_merged_excerpt_ranges_for_path(
+ path_key,
+ buffer,
+ excerpt_ranges,
+ &buffer_snapshot,
+ new,
+ counts,
+ cx,
+ );
+ ranges
+ })
+ .ok()
+ .unwrap_or_default()
+ })
+ }
+
+ pub(super) fn expand_excerpts_with_paths(
+ &mut self,
+ ids: impl IntoIterator<Item = ExcerptId>,
+ line_count: u32,
+ direction: ExpandExcerptDirection,
+ cx: &mut Context<Self>,
+ ) {
+ let grouped = ids
+ .into_iter()
+ .chunk_by(|id| self.paths_by_excerpt.get(id).cloned())
+ .into_iter()
+ .flat_map(|(k, v)| Some((k?, v.into_iter().collect::<Vec<_>>())))
+ .collect::<Vec<_>>();
+ let snapshot = self.snapshot(cx);
+
+ for (path, ids) in grouped.into_iter() {
+ let Some(excerpt_ids) = self.excerpts_by_path.get(&path) else {
+ continue;
+ };
+
+ let ids_to_expand = HashSet::from_iter(ids);
+ let expanded_ranges = excerpt_ids.iter().filter_map(|excerpt_id| {
+ let excerpt = snapshot.excerpt(*excerpt_id)?;
+
+ let mut context = excerpt.range.context.to_point(&excerpt.buffer);
+ if ids_to_expand.contains(excerpt_id) {
+ match direction {
+ ExpandExcerptDirection::Up => {
+ context.start.row = context.start.row.saturating_sub(line_count);
+ context.start.column = 0;
+ }
+ ExpandExcerptDirection::Down => {
+ context.end.row =
+ (context.end.row + line_count).min(excerpt.buffer.max_point().row);
+ context.end.column = excerpt.buffer.line_len(context.end.row);
+ }
+ ExpandExcerptDirection::UpAndDown => {
+ context.start.row = context.start.row.saturating_sub(line_count);
+ context.start.column = 0;
+ context.end.row =
+ (context.end.row + line_count).min(excerpt.buffer.max_point().row);
+ context.end.column = excerpt.buffer.line_len(context.end.row);
+ }
+ }
+ }
+
+ Some(ExcerptRange {
+ context,
+ primary: excerpt.range.primary.to_point(&excerpt.buffer),
+ })
+ });
+ let mut merged_ranges: Vec<ExcerptRange<Point>> = Vec::new();
+ for range in expanded_ranges {
+ if let Some(last_range) = merged_ranges.last_mut()
+ && last_range.context.end >= range.context.start
+ {
+ last_range.context.end = range.context.end;
+ continue;
+ }
+ merged_ranges.push(range)
+ }
+ let Some(excerpt_id) = excerpt_ids.first() else {
+ continue;
+ };
+ let Some(buffer_id) = &snapshot.buffer_id_for_excerpt(*excerpt_id) else {
+ continue;
+ };
+
+ let Some(buffer) = self.buffers.get(buffer_id).map(|b| b.buffer.clone()) else {
+ continue;
+ };
+
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ self.update_path_excerpts(path.clone(), buffer, &buffer_snapshot, merged_ranges, cx);
+ }
+ }
+
+ /// Sets excerpts, returns `true` if at least one new excerpt was added.
+ fn set_merged_excerpt_ranges_for_path(
+ &mut self,
+ path: PathKey,
+ buffer: Entity<Buffer>,
+ ranges: Vec<ExcerptRange<Point>>,
+ buffer_snapshot: &BufferSnapshot,
+ new: Vec<ExcerptRange<Point>>,
+ counts: Vec<usize>,
+ cx: &mut Context<Self>,
+ ) -> (Vec<Range<Anchor>>, bool) {
+ let (excerpt_ids, added_a_new_excerpt) =
+ self.update_path_excerpts(path, buffer, buffer_snapshot, new, cx);
+
+ let mut result = Vec::new();
+ let mut ranges = ranges.into_iter();
+ for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(counts.into_iter()) {
+ for range in ranges.by_ref().take(range_count) {
+ let range = Anchor::range_in_buffer(
+ excerpt_id,
+ buffer_snapshot.remote_id(),
+ buffer_snapshot.anchor_before(&range.primary.start)
+ ..buffer_snapshot.anchor_after(&range.primary.end),
+ );
+ result.push(range)
+ }
+ }
+ (result, added_a_new_excerpt)
+ }
+
+ fn update_path_excerpts(
+ &mut self,
+ path: PathKey,
+ buffer: Entity<Buffer>,
+ buffer_snapshot: &BufferSnapshot,
+ new: Vec<ExcerptRange<Point>>,
+ cx: &mut Context<Self>,
+ ) -> (Vec<ExcerptId>, bool) {
+ let mut insert_after = self
+ .excerpts_by_path
+ .range(..path.clone())
+ .next_back()
+ .map(|(_, value)| *value.last().unwrap())
+ .unwrap_or(ExcerptId::min());
+
+ let existing = self
+ .excerpts_by_path
+ .get(&path)
+ .cloned()
+ .unwrap_or_default();
+
+ let mut new_iter = new.into_iter().peekable();
+ let mut existing_iter = existing.into_iter().peekable();
+
+ let mut excerpt_ids = Vec::new();
+ let mut to_remove = Vec::new();
+ let mut to_insert: Vec<(ExcerptId, ExcerptRange<Point>)> = Vec::new();
+ let mut added_a_new_excerpt = false;
+ let snapshot = self.snapshot(cx);
+
+ let mut next_excerpt_id =
+ if let Some(last_entry) = self.snapshot.borrow().excerpt_ids.last() {
+ last_entry.id.0 + 1
+ } else {
+ 1
+ };
+
+ let mut next_excerpt_id = move || ExcerptId(post_inc(&mut next_excerpt_id));
+
+ let mut excerpts_cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
+ excerpts_cursor.next();
+
+ loop {
+ let new = new_iter.peek();
+ let existing = if let Some(existing_id) = existing_iter.peek() {
+ let locator = snapshot.excerpt_locator_for_id(*existing_id);
+ excerpts_cursor.seek_forward(&Some(locator), Bias::Left);
+ if let Some(excerpt) = excerpts_cursor.item() {
+ if excerpt.buffer_id != buffer_snapshot.remote_id() {
+ to_remove.push(*existing_id);
+ existing_iter.next();
+ continue;
+ }
+ Some((
+ *existing_id,
+ excerpt.range.context.to_point(buffer_snapshot),
+ ))
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ if let Some((last_id, last)) = to_insert.last_mut() {
+ if let Some(new) = new
+ && last.context.end >= new.context.start
+ {
+ last.context.end = last.context.end.max(new.context.end);
+ excerpt_ids.push(*last_id);
+ new_iter.next();
+ continue;
+ }
+ if let Some((existing_id, existing_range)) = &existing
+ && last.context.end >= existing_range.start
+ {
+ last.context.end = last.context.end.max(existing_range.end);
+ to_remove.push(*existing_id);
+ self.snapshot
+ .get_mut()
+ .replaced_excerpts
+ .insert(*existing_id, *last_id);
+ existing_iter.next();
+ continue;
+ }
+ }
+
+ match (new, existing) {
+ (None, None) => break,
+ (None, Some((existing_id, _))) => {
+ existing_iter.next();
+ to_remove.push(existing_id);
+ continue;
+ }
+ (Some(_), None) => {
+ added_a_new_excerpt = true;
+ let new_id = next_excerpt_id();
+ excerpt_ids.push(new_id);
+ to_insert.push((new_id, new_iter.next().unwrap()));
+ continue;
+ }
+ (Some(new), Some((_, existing_range))) => {
+ if existing_range.end < new.context.start {
+ let existing_id = existing_iter.next().unwrap();
+ to_remove.push(existing_id);
+ continue;
+ } else if existing_range.start > new.context.end {
+ let new_id = next_excerpt_id();
+ excerpt_ids.push(new_id);
+ to_insert.push((new_id, new_iter.next().unwrap()));
+ continue;
+ }
+
+ if existing_range.start == new.context.start
+ && existing_range.end == new.context.end
+ {
+ self.insert_excerpts_with_ids_after(
+ insert_after,
+ buffer.clone(),
+ mem::take(&mut to_insert),
+ cx,
+ );
+ insert_after = existing_iter.next().unwrap();
+ excerpt_ids.push(insert_after);
+ new_iter.next();
+ } else {
+ let existing_id = existing_iter.next().unwrap();
+ let new_id = next_excerpt_id();
+ self.snapshot
+ .get_mut()
+ .replaced_excerpts
+ .insert(existing_id, new_id);
+ to_remove.push(existing_id);
+ let mut range = new_iter.next().unwrap();
+ range.context.start = range.context.start.min(existing_range.start);
+ range.context.end = range.context.end.max(existing_range.end);
+ excerpt_ids.push(new_id);
+ to_insert.push((new_id, range));
+ }
+ }
+ };
+ }
+
+ self.insert_excerpts_with_ids_after(insert_after, buffer, to_insert, cx);
+ self.remove_excerpts(to_remove, cx);
+ if excerpt_ids.is_empty() {
+ self.excerpts_by_path.remove(&path);
+ } else {
+ for excerpt_id in &excerpt_ids {
+ self.paths_by_excerpt.insert(*excerpt_id, path.clone());
+ }
+ self.excerpts_by_path
+ .insert(path, excerpt_ids.iter().dedup().cloned().collect());
+ }
+
+ (excerpt_ids, added_a_new_excerpt)
+ }
+}
@@ -0,0 +1,524 @@
+use gpui::{App, Context, Entity};
+use language::{self, Buffer, TextDimension, TransactionId};
+use std::{
+ collections::HashMap,
+ ops::{Range, Sub},
+ time::{Duration, Instant},
+};
+use sum_tree::Bias;
+use text::BufferId;
+
+use crate::BufferState;
+
+use super::{Event, ExcerptSummary, MultiBuffer};
+
+#[derive(Clone)]
+pub(super) struct History {
+ next_transaction_id: TransactionId,
+ undo_stack: Vec<Transaction>,
+ redo_stack: Vec<Transaction>,
+ transaction_depth: usize,
+ group_interval: Duration,
+}
+
+impl Default for History {
+ fn default() -> Self {
+ History {
+ next_transaction_id: clock::Lamport::MIN,
+ undo_stack: Vec::new(),
+ redo_stack: Vec::new(),
+ transaction_depth: 0,
+ group_interval: Duration::from_millis(300),
+ }
+ }
+}
+
+#[derive(Clone)]
+struct Transaction {
+ id: TransactionId,
+ buffer_transactions: HashMap<BufferId, text::TransactionId>,
+ first_edit_at: Instant,
+ last_edit_at: Instant,
+ suppress_grouping: bool,
+}
+
+impl History {
+ fn start_transaction(&mut self, now: Instant) -> Option<TransactionId> {
+ self.transaction_depth += 1;
+ if self.transaction_depth == 1 {
+ let id = self.next_transaction_id.tick();
+ self.undo_stack.push(Transaction {
+ id,
+ buffer_transactions: Default::default(),
+ first_edit_at: now,
+ last_edit_at: now,
+ suppress_grouping: false,
+ });
+ Some(id)
+ } else {
+ None
+ }
+ }
+
+ fn end_transaction(
+ &mut self,
+ now: Instant,
+ buffer_transactions: HashMap<BufferId, text::TransactionId>,
+ ) -> bool {
+ assert_ne!(self.transaction_depth, 0);
+ self.transaction_depth -= 1;
+ if self.transaction_depth == 0 {
+ if buffer_transactions.is_empty() {
+ self.undo_stack.pop();
+ false
+ } else {
+ self.redo_stack.clear();
+ let transaction = self.undo_stack.last_mut().unwrap();
+ transaction.last_edit_at = now;
+ for (buffer_id, transaction_id) in buffer_transactions {
+ transaction
+ .buffer_transactions
+ .entry(buffer_id)
+ .or_insert(transaction_id);
+ }
+ true
+ }
+ } else {
+ false
+ }
+ }
+
+ fn push_transaction<'a, T>(
+ &mut self,
+ buffer_transactions: T,
+ now: Instant,
+ cx: &Context<MultiBuffer>,
+ ) where
+ T: IntoIterator<Item = (&'a Entity<Buffer>, &'a language::Transaction)>,
+ {
+ assert_eq!(self.transaction_depth, 0);
+ let transaction = Transaction {
+ id: self.next_transaction_id.tick(),
+ buffer_transactions: buffer_transactions
+ .into_iter()
+ .map(|(buffer, transaction)| (buffer.read(cx).remote_id(), transaction.id))
+ .collect(),
+ first_edit_at: now,
+ last_edit_at: now,
+ suppress_grouping: false,
+ };
+ if !transaction.buffer_transactions.is_empty() {
+ self.undo_stack.push(transaction);
+ self.redo_stack.clear();
+ }
+ }
+
+ fn finalize_last_transaction(&mut self) {
+ if let Some(transaction) = self.undo_stack.last_mut() {
+ transaction.suppress_grouping = true;
+ }
+ }
+
+ fn forget(&mut self, transaction_id: TransactionId) -> Option<Transaction> {
+ if let Some(ix) = self
+ .undo_stack
+ .iter()
+ .rposition(|transaction| transaction.id == transaction_id)
+ {
+ Some(self.undo_stack.remove(ix))
+ } else if let Some(ix) = self
+ .redo_stack
+ .iter()
+ .rposition(|transaction| transaction.id == transaction_id)
+ {
+ Some(self.redo_stack.remove(ix))
+ } else {
+ None
+ }
+ }
+
+ fn transaction(&self, transaction_id: TransactionId) -> Option<&Transaction> {
+ self.undo_stack
+ .iter()
+ .find(|transaction| transaction.id == transaction_id)
+ .or_else(|| {
+ self.redo_stack
+ .iter()
+ .find(|transaction| transaction.id == transaction_id)
+ })
+ }
+
+ fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> {
+ self.undo_stack
+ .iter_mut()
+ .find(|transaction| transaction.id == transaction_id)
+ .or_else(|| {
+ self.redo_stack
+ .iter_mut()
+ .find(|transaction| transaction.id == transaction_id)
+ })
+ }
+
+ fn pop_undo(&mut self) -> Option<&mut Transaction> {
+ assert_eq!(self.transaction_depth, 0);
+ if let Some(transaction) = self.undo_stack.pop() {
+ self.redo_stack.push(transaction);
+ self.redo_stack.last_mut()
+ } else {
+ None
+ }
+ }
+
+ fn pop_redo(&mut self) -> Option<&mut Transaction> {
+ assert_eq!(self.transaction_depth, 0);
+ if let Some(transaction) = self.redo_stack.pop() {
+ self.undo_stack.push(transaction);
+ self.undo_stack.last_mut()
+ } else {
+ None
+ }
+ }
+
+ fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&Transaction> {
+ let ix = self
+ .undo_stack
+ .iter()
+ .rposition(|transaction| transaction.id == transaction_id)?;
+ let transaction = self.undo_stack.remove(ix);
+ self.redo_stack.push(transaction);
+ self.redo_stack.last()
+ }
+
+ fn group(&mut self) -> Option<TransactionId> {
+ let mut count = 0;
+ let mut transactions = self.undo_stack.iter();
+ if let Some(mut transaction) = transactions.next_back() {
+ while let Some(prev_transaction) = transactions.next_back() {
+ if !prev_transaction.suppress_grouping
+ && transaction.first_edit_at - prev_transaction.last_edit_at
+ <= self.group_interval
+ {
+ transaction = prev_transaction;
+ count += 1;
+ } else {
+ break;
+ }
+ }
+ }
+ self.group_trailing(count)
+ }
+
+ fn group_until(&mut self, transaction_id: TransactionId) {
+ let mut count = 0;
+ for transaction in self.undo_stack.iter().rev() {
+ if transaction.id == transaction_id {
+ self.group_trailing(count);
+ break;
+ } else if transaction.suppress_grouping {
+ break;
+ } else {
+ count += 1;
+ }
+ }
+ }
+
+ fn group_trailing(&mut self, n: usize) -> Option<TransactionId> {
+ let new_len = self.undo_stack.len() - n;
+ let (transactions_to_keep, transactions_to_merge) = self.undo_stack.split_at_mut(new_len);
+ if let Some(last_transaction) = transactions_to_keep.last_mut() {
+ if let Some(transaction) = transactions_to_merge.last() {
+ last_transaction.last_edit_at = transaction.last_edit_at;
+ }
+ for to_merge in transactions_to_merge {
+ for (buffer_id, transaction_id) in &to_merge.buffer_transactions {
+ last_transaction
+ .buffer_transactions
+ .entry(*buffer_id)
+ .or_insert(*transaction_id);
+ }
+ }
+ }
+
+ self.undo_stack.truncate(new_len);
+ self.undo_stack.last().map(|t| t.id)
+ }
+
+ pub(super) fn transaction_depth(&self) -> usize {
+ self.transaction_depth
+ }
+
+ pub fn set_group_interval(&mut self, group_interval: Duration) {
+ self.group_interval = group_interval;
+ }
+}
+
+impl MultiBuffer {
+ pub fn start_transaction(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
+ self.start_transaction_at(Instant::now(), cx)
+ }
+
+ pub fn start_transaction_at(
+ &mut self,
+ now: Instant,
+ cx: &mut Context<Self>,
+ ) -> Option<TransactionId> {
+ if let Some(buffer) = self.as_singleton() {
+ return buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
+ }
+
+ for BufferState { buffer, .. } in self.buffers.values() {
+ buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
+ }
+ self.history.start_transaction(now)
+ }
+
+ pub fn last_transaction_id(&self, cx: &App) -> Option<TransactionId> {
+ if let Some(buffer) = self.as_singleton() {
+ buffer
+ .read(cx)
+ .peek_undo_stack()
+ .map(|history_entry| history_entry.transaction_id())
+ } else {
+ let last_transaction = self.history.undo_stack.last()?;
+ Some(last_transaction.id)
+ }
+ }
+
+ pub fn end_transaction(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
+ self.end_transaction_at(Instant::now(), cx)
+ }
+
+ pub fn end_transaction_at(
+ &mut self,
+ now: Instant,
+ cx: &mut Context<Self>,
+ ) -> Option<TransactionId> {
+ if let Some(buffer) = self.as_singleton() {
+ return buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx));
+ }
+
+ let mut buffer_transactions = HashMap::default();
+ for BufferState { buffer, .. } in self.buffers.values() {
+ if let Some(transaction_id) =
+ buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
+ {
+ buffer_transactions.insert(buffer.read(cx).remote_id(), transaction_id);
+ }
+ }
+
+ if self.history.end_transaction(now, buffer_transactions) {
+ let transaction_id = self.history.group().unwrap();
+ Some(transaction_id)
+ } else {
+ None
+ }
+ }
+
+ pub fn edited_ranges_for_transaction<D>(
+ &self,
+ transaction_id: TransactionId,
+ cx: &App,
+ ) -> Vec<Range<D>>
+ where
+ D: TextDimension + Ord + Sub<D, Output = D>,
+ {
+ let Some(transaction) = self.history.transaction(transaction_id) else {
+ return Vec::new();
+ };
+
+ let mut ranges = Vec::new();
+ let snapshot = self.read(cx);
+ let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
+
+ for (buffer_id, buffer_transaction) in &transaction.buffer_transactions {
+ let Some(buffer_state) = self.buffers.get(buffer_id) else {
+ continue;
+ };
+
+ let buffer = buffer_state.buffer.read(cx);
+ for range in buffer.edited_ranges_for_transaction_id::<D>(*buffer_transaction) {
+ for excerpt_id in &buffer_state.excerpts {
+ cursor.seek(excerpt_id, Bias::Left);
+ if let Some(excerpt) = cursor.item()
+ && excerpt.locator == *excerpt_id
+ {
+ let excerpt_buffer_start = excerpt.range.context.start.summary::<D>(buffer);
+ let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(buffer);
+ let excerpt_range = excerpt_buffer_start..excerpt_buffer_end;
+ if excerpt_range.contains(&range.start)
+ && excerpt_range.contains(&range.end)
+ {
+ let excerpt_start = D::from_text_summary(&cursor.start().text);
+
+ let mut start = excerpt_start;
+ start.add_assign(&(range.start - excerpt_buffer_start));
+ let mut end = excerpt_start;
+ end.add_assign(&(range.end - excerpt_buffer_start));
+
+ ranges.push(start..end);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ ranges.sort_by_key(|range| range.start);
+ ranges
+ }
+
+ pub fn merge_transactions(
+ &mut self,
+ transaction: TransactionId,
+ destination: TransactionId,
+ cx: &mut Context<Self>,
+ ) {
+ if let Some(buffer) = self.as_singleton() {
+ buffer.update(cx, |buffer, _| {
+ buffer.merge_transactions(transaction, destination)
+ });
+ } else if let Some(transaction) = self.history.forget(transaction)
+ && let Some(destination) = self.history.transaction_mut(destination)
+ {
+ for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions {
+ if let Some(destination_buffer_transaction_id) =
+ destination.buffer_transactions.get(&buffer_id)
+ {
+ if let Some(state) = self.buffers.get(&buffer_id) {
+ state.buffer.update(cx, |buffer, _| {
+ buffer.merge_transactions(
+ buffer_transaction_id,
+ *destination_buffer_transaction_id,
+ )
+ });
+ }
+ } else {
+ destination
+ .buffer_transactions
+ .insert(buffer_id, buffer_transaction_id);
+ }
+ }
+ }
+ }
+
+ pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
+ self.history.finalize_last_transaction();
+ for BufferState { buffer, .. } in self.buffers.values() {
+ buffer.update(cx, |buffer, _| {
+ buffer.finalize_last_transaction();
+ });
+ }
+ }
+
+ pub fn push_transaction<'a, T>(&mut self, buffer_transactions: T, cx: &Context<Self>)
+ where
+ T: IntoIterator<Item = (&'a Entity<Buffer>, &'a language::Transaction)>,
+ {
+ self.history
+ .push_transaction(buffer_transactions, Instant::now(), cx);
+ self.history.finalize_last_transaction();
+ }
+
+ pub fn group_until_transaction(
+ &mut self,
+ transaction_id: TransactionId,
+ cx: &mut Context<Self>,
+ ) {
+ if let Some(buffer) = self.as_singleton() {
+ buffer.update(cx, |buffer, _| {
+ buffer.group_until_transaction(transaction_id)
+ });
+ } else {
+ self.history.group_until(transaction_id);
+ }
+ }
+ pub fn undo(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
+ let mut transaction_id = None;
+ if let Some(buffer) = self.as_singleton() {
+ transaction_id = buffer.update(cx, |buffer, cx| buffer.undo(cx));
+ } else {
+ while let Some(transaction) = self.history.pop_undo() {
+ let mut undone = false;
+ for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions {
+ if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
+ undone |= buffer.update(cx, |buffer, cx| {
+ let undo_to = *buffer_transaction_id;
+ if let Some(entry) = buffer.peek_undo_stack() {
+ *buffer_transaction_id = entry.transaction_id();
+ }
+ buffer.undo_to_transaction(undo_to, cx)
+ });
+ }
+ }
+
+ if undone {
+ transaction_id = Some(transaction.id);
+ break;
+ }
+ }
+ }
+
+ if let Some(transaction_id) = transaction_id {
+ cx.emit(Event::TransactionUndone { transaction_id });
+ }
+
+ transaction_id
+ }
+
+ pub fn redo(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
+ if let Some(buffer) = self.as_singleton() {
+ return buffer.update(cx, |buffer, cx| buffer.redo(cx));
+ }
+
+ while let Some(transaction) = self.history.pop_redo() {
+ let mut redone = false;
+ for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions.iter_mut() {
+ if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
+ redone |= buffer.update(cx, |buffer, cx| {
+ let redo_to = *buffer_transaction_id;
+ if let Some(entry) = buffer.peek_redo_stack() {
+ *buffer_transaction_id = entry.transaction_id();
+ }
+ buffer.redo_to_transaction(redo_to, cx)
+ });
+ }
+ }
+
+ if redone {
+ return Some(transaction.id);
+ }
+ }
+
+ None
+ }
+
+ pub fn undo_transaction(&mut self, transaction_id: TransactionId, cx: &mut Context<Self>) {
+ if let Some(buffer) = self.as_singleton() {
+ buffer.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
+ } else if let Some(transaction) = self.history.remove_from_undo(transaction_id) {
+ for (buffer_id, transaction_id) in &transaction.buffer_transactions {
+ if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
+ buffer.update(cx, |buffer, cx| {
+ buffer.undo_transaction(*transaction_id, cx)
+ });
+ }
+ }
+ }
+ }
+
+ pub fn forget_transaction(&mut self, transaction_id: TransactionId, cx: &mut Context<Self>) {
+ if let Some(buffer) = self.as_singleton() {
+ buffer.update(cx, |buffer, _| {
+ buffer.forget_transaction(transaction_id);
+ });
+ } else if let Some(transaction) = self.history.forget(transaction_id) {
+ for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions {
+ if let Some(state) = self.buffers.get_mut(&buffer_id) {
+ state.buffer.update(cx, |buffer, _| {
+ buffer.forget_transaction(buffer_transaction_id);
+ });
+ }
+ }
+ }
+ }
+}