There are a of separate APIs in this, partially interleaved making it difficult to grasp. Release Notes: - N/A *or* Added/Fixed/Improved ...
525 lines
18 KiB
Rust
525 lines
18 KiB
Rust
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);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|