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, redo_stack: Vec, 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, first_edit_at: Instant, last_edit_at: Instant, suppress_grouping: bool, } impl History { fn start_transaction(&mut self, now: Instant) -> Option { 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, ) -> 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, ) where T: IntoIterator, &'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 { 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 { 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 { 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) -> Option { self.start_transaction_at(Instant::now(), cx) } pub fn start_transaction_at( &mut self, now: Instant, cx: &mut Context, ) -> Option { 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 { 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) -> Option { self.end_transaction_at(Instant::now(), cx) } pub fn end_transaction_at( &mut self, now: Instant, cx: &mut Context, ) -> Option { 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( &self, transaction_id: TransactionId, cx: &App, ) -> Vec> where D: TextDimension + Ord + Sub, { 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::(()); 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::(*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::(buffer); let excerpt_buffer_end = excerpt.range.context.end.summary::(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, ) { 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.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) where T: IntoIterator, &'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, ) { 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) -> Option { 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) -> Option { 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) { 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) { 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); }); } } } } }