mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-05 02:20:10 +00:00
Make transactions serializable to enable edits on behalf of other users
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
6768288da8
commit
7d8641afb6
9 changed files with 325 additions and 291 deletions
|
@ -2081,10 +2081,10 @@ impl Editor {
|
||||||
project.apply_code_action(buffer, action, true, cx)
|
project.apply_code_action(buffer, action, true, cx)
|
||||||
});
|
});
|
||||||
Some(cx.spawn(|workspace, mut cx| async move {
|
Some(cx.spawn(|workspace, mut cx| async move {
|
||||||
let buffers = apply_code_actions.await?;
|
let project_transaction = apply_code_actions.await?;
|
||||||
// TODO: replace this with opening a single tab that is a multibuffer
|
// TODO: replace this with opening a single tab that is a multibuffer
|
||||||
workspace.update(&mut cx, |workspace, cx| {
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
for (buffer, _) in buffers {
|
for (buffer, _) in project_transaction.0 {
|
||||||
workspace.open_item(BufferItemHandle(buffer), cx);
|
workspace.open_item(BufferItemHandle(buffer), cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -4825,7 +4825,7 @@ impl View for Editor {
|
||||||
self.focused = true;
|
self.focused = true;
|
||||||
self.blink_cursors(self.blink_epoch, cx);
|
self.blink_cursors(self.blink_epoch, cx);
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
buffer.avoid_grouping_next_transaction(cx);
|
buffer.finalize_last_transaction(cx);
|
||||||
buffer.set_active_selections(&self.selections, cx)
|
buffer.set_active_selections(&self.selections, cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ use text::{
|
||||||
AnchorRangeExt as _, Edit, Point, PointUtf16, TextSummary,
|
AnchorRangeExt as _, Edit, Point, PointUtf16, TextSummary,
|
||||||
};
|
};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use util::post_inc;
|
|
||||||
|
|
||||||
const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
|
const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ pub struct MultiBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct History {
|
struct History {
|
||||||
next_transaction_id: usize,
|
next_transaction_id: TransactionId,
|
||||||
undo_stack: Vec<Transaction>,
|
undo_stack: Vec<Transaction>,
|
||||||
redo_stack: Vec<Transaction>,
|
redo_stack: Vec<Transaction>,
|
||||||
transaction_depth: usize,
|
transaction_depth: usize,
|
||||||
|
@ -59,7 +58,7 @@ pub enum CharKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Transaction {
|
struct Transaction {
|
||||||
id: usize,
|
id: TransactionId,
|
||||||
buffer_transactions: HashSet<(usize, text::TransactionId)>,
|
buffer_transactions: HashSet<(usize, text::TransactionId)>,
|
||||||
first_edit_at: Instant,
|
first_edit_at: Instant,
|
||||||
last_edit_at: Instant,
|
last_edit_at: Instant,
|
||||||
|
@ -472,9 +471,11 @@ impl MultiBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn avoid_grouping_next_transaction(&mut self, cx: &mut ModelContext<Self>) {
|
pub fn finalize_last_transaction(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
for BufferState { buffer, .. } in self.buffers.borrow().values() {
|
for BufferState { buffer, .. } in self.buffers.borrow().values() {
|
||||||
buffer.update(cx, |buffer, _| buffer.avoid_grouping_next_transaction());
|
buffer.update(cx, |buffer, _| {
|
||||||
|
buffer.finalize_last_transaction();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2092,7 +2093,7 @@ impl History {
|
||||||
fn start_transaction(&mut self, now: Instant) -> Option<TransactionId> {
|
fn start_transaction(&mut self, now: Instant) -> Option<TransactionId> {
|
||||||
self.transaction_depth += 1;
|
self.transaction_depth += 1;
|
||||||
if self.transaction_depth == 1 {
|
if self.transaction_depth == 1 {
|
||||||
let id = post_inc(&mut self.next_transaction_id);
|
let id = self.next_transaction_id.tick();
|
||||||
self.undo_stack.push(Transaction {
|
self.undo_stack.push(Transaction {
|
||||||
id,
|
id,
|
||||||
buffer_transactions: Default::default(),
|
buffer_transactions: Default::default(),
|
||||||
|
|
|
@ -38,7 +38,7 @@ use text::{operation_queue::OperationQueue, rope::TextDimension};
|
||||||
pub use text::{Buffer as TextBuffer, Operation as _, *};
|
pub use text::{Buffer as TextBuffer, Operation as _, *};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use tree_sitter::{InputEdit, QueryCursor, Tree};
|
use tree_sitter::{InputEdit, QueryCursor, Tree};
|
||||||
use util::{post_inc, TryFutureExt as _};
|
use util::{post_inc, ResultExt, TryFutureExt as _};
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub use tree_sitter_rust;
|
pub use tree_sitter_rust;
|
||||||
|
@ -214,7 +214,7 @@ pub trait File {
|
||||||
buffer_id: u64,
|
buffer_id: u64,
|
||||||
completion: Completion<Anchor>,
|
completion: Completion<Anchor>,
|
||||||
cx: &mut MutableAppContext,
|
cx: &mut MutableAppContext,
|
||||||
) -> Task<Result<Vec<clock::Local>>>;
|
) -> Task<Result<Option<Transaction>>>;
|
||||||
|
|
||||||
fn code_actions(
|
fn code_actions(
|
||||||
&self,
|
&self,
|
||||||
|
@ -307,7 +307,7 @@ impl File for FakeFile {
|
||||||
_: u64,
|
_: u64,
|
||||||
_: Completion<Anchor>,
|
_: Completion<Anchor>,
|
||||||
_: &mut MutableAppContext,
|
_: &mut MutableAppContext,
|
||||||
) -> Task<Result<Vec<clock::Local>>> {
|
) -> Task<Result<Option<Transaction>>> {
|
||||||
Task::ready(Ok(Default::default()))
|
Task::ready(Ok(Default::default()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1339,16 +1339,12 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_transaction(
|
pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) {
|
||||||
&mut self,
|
self.text.push_transaction(transaction, now);
|
||||||
edit_ids: impl IntoIterator<Item = clock::Local>,
|
|
||||||
now: Instant,
|
|
||||||
) {
|
|
||||||
self.text.push_transaction(edit_ids, now);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn avoid_grouping_next_transaction(&mut self) {
|
pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> {
|
||||||
self.text.avoid_grouping_next_transaction();
|
self.text.finalize_last_transaction()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn forget_transaction(&mut self, transaction_id: TransactionId) {
|
pub fn forget_transaction(&mut self, transaction_id: TransactionId) {
|
||||||
|
@ -1536,7 +1532,7 @@ impl Buffer {
|
||||||
edits: impl IntoIterator<Item = lsp::TextEdit>,
|
edits: impl IntoIterator<Item = lsp::TextEdit>,
|
||||||
version: Option<i32>,
|
version: Option<i32>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Result<Vec<(Range<Anchor>, clock::Local)>> {
|
) -> Result<()> {
|
||||||
let mut anchored_edits = Vec::new();
|
let mut anchored_edits = Vec::new();
|
||||||
let snapshot =
|
let snapshot =
|
||||||
if let Some((version, language_server)) = version.zip(self.language_server.as_mut()) {
|
if let Some((version, language_server)) = version.zip(self.language_server.as_mut()) {
|
||||||
|
@ -1560,14 +1556,11 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.start_transaction();
|
self.start_transaction();
|
||||||
let edit_ids = anchored_edits
|
for (range, new_text) in anchored_edits {
|
||||||
.into_iter()
|
self.edit([range], new_text, cx);
|
||||||
.filter_map(|(range, new_text)| {
|
}
|
||||||
Some((range.clone(), self.edit([range], new_text, cx)?))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
self.end_transaction(cx);
|
self.end_transaction(cx);
|
||||||
Ok(edit_ids)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn did_edit(
|
fn did_edit(
|
||||||
|
@ -1941,7 +1934,7 @@ impl Buffer {
|
||||||
completion: Completion<Anchor>,
|
completion: Completion<Anchor>,
|
||||||
push_to_history: bool,
|
push_to_history: bool,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Vec<clock::Local>>> {
|
) -> Task<Result<Option<Transaction>>> {
|
||||||
let file = if let Some(file) = self.file.as_ref() {
|
let file = if let Some(file) = self.file.as_ref() {
|
||||||
file
|
file
|
||||||
} else {
|
} else {
|
||||||
|
@ -1961,20 +1954,22 @@ impl Buffer {
|
||||||
.await?;
|
.await?;
|
||||||
if let Some(additional_edits) = resolved_completion.additional_text_edits {
|
if let Some(additional_edits) = resolved_completion.additional_text_edits {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if !push_to_history {
|
this.finalize_last_transaction();
|
||||||
this.avoid_grouping_next_transaction();
|
|
||||||
}
|
|
||||||
this.start_transaction();
|
this.start_transaction();
|
||||||
let edits = this.apply_lsp_edits(additional_edits, None, cx);
|
this.apply_lsp_edits(additional_edits, None, cx).log_err();
|
||||||
if let Some(transaction_id) = this.end_transaction(cx) {
|
let transaction = if this.end_transaction(cx).is_some() {
|
||||||
|
let transaction = this.finalize_last_transaction().unwrap().clone();
|
||||||
if !push_to_history {
|
if !push_to_history {
|
||||||
this.forget_transaction(transaction_id);
|
this.forget_transaction(transaction.id);
|
||||||
}
|
}
|
||||||
}
|
Some(transaction)
|
||||||
Ok(edits?.into_iter().map(|(_, edit_id)| edit_id).collect())
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(transaction)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(Default::default())
|
Ok(None)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -1984,17 +1979,20 @@ impl Buffer {
|
||||||
cx.as_mut(),
|
cx.as_mut(),
|
||||||
);
|
);
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let edit_ids = apply_edits.await?;
|
if let Some(transaction) = apply_edits.await? {
|
||||||
this.update(&mut cx, |this, _| {
|
|
||||||
this.wait_for_edits(edit_ids.iter().copied())
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
if push_to_history {
|
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
this.push_transaction(edit_ids.iter().copied(), Instant::now());
|
this.wait_for_edits(transaction.edit_ids.iter().copied())
|
||||||
});
|
})
|
||||||
|
.await;
|
||||||
|
if push_to_history {
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
this.push_transaction(transaction.clone(), Instant::now());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Some(transaction))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
Ok(edit_ids)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,7 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation {
|
||||||
replica_id: undo.id.replica_id as u32,
|
replica_id: undo.id.replica_id as u32,
|
||||||
local_timestamp: undo.id.value,
|
local_timestamp: undo.id.value,
|
||||||
lamport_timestamp: lamport_timestamp.value,
|
lamport_timestamp: lamport_timestamp.value,
|
||||||
ranges: undo
|
ranges: undo.ranges.iter().map(serialize_range).collect(),
|
||||||
.ranges
|
|
||||||
.iter()
|
|
||||||
.map(|r| proto::Range {
|
|
||||||
start: r.start.0 as u64,
|
|
||||||
end: r.end.0 as u64,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
counts: undo
|
counts: undo
|
||||||
.counts
|
.counts
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -75,20 +68,12 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::Edit {
|
pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::Edit {
|
||||||
let ranges = operation
|
|
||||||
.ranges
|
|
||||||
.iter()
|
|
||||||
.map(|range| proto::Range {
|
|
||||||
start: range.start.0 as u64,
|
|
||||||
end: range.end.0 as u64,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
proto::operation::Edit {
|
proto::operation::Edit {
|
||||||
replica_id: operation.timestamp.replica_id as u32,
|
replica_id: operation.timestamp.replica_id as u32,
|
||||||
local_timestamp: operation.timestamp.local,
|
local_timestamp: operation.timestamp.local,
|
||||||
lamport_timestamp: operation.timestamp.lamport,
|
lamport_timestamp: operation.timestamp.lamport,
|
||||||
version: From::from(&operation.version),
|
version: From::from(&operation.version),
|
||||||
ranges,
|
ranges: operation.ranges.iter().map(serialize_range).collect(),
|
||||||
new_text: operation.new_text.clone(),
|
new_text: operation.new_text.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,11 +196,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
ranges: undo
|
ranges: undo.ranges.into_iter().map(deserialize_range).collect(),
|
||||||
.ranges
|
|
||||||
.into_iter()
|
|
||||||
.map(|r| FullOffset(r.start as usize)..FullOffset(r.end as usize))
|
|
||||||
.collect(),
|
|
||||||
version: undo.version.into(),
|
version: undo.version.into(),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -263,11 +244,6 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation {
|
pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation {
|
||||||
let ranges = edit
|
|
||||||
.ranges
|
|
||||||
.into_iter()
|
|
||||||
.map(|range| FullOffset(range.start as usize)..FullOffset(range.end as usize))
|
|
||||||
.collect();
|
|
||||||
EditOperation {
|
EditOperation {
|
||||||
timestamp: InsertionTimestamp {
|
timestamp: InsertionTimestamp {
|
||||||
replica_id: edit.replica_id as ReplicaId,
|
replica_id: edit.replica_id as ReplicaId,
|
||||||
|
@ -275,7 +251,7 @@ pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation
|
||||||
lamport: edit.lamport_timestamp,
|
lamport: edit.lamport_timestamp,
|
||||||
},
|
},
|
||||||
version: edit.version.into(),
|
version: edit.version.into(),
|
||||||
ranges,
|
ranges: edit.ranges.into_iter().map(deserialize_range).collect(),
|
||||||
new_text: edit.new_text,
|
new_text: edit.new_text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -469,42 +445,64 @@ pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction<A
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_code_action_edit(
|
pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction {
|
||||||
edit_id: clock::Local,
|
proto::Transaction {
|
||||||
old_range: &Range<Anchor>,
|
id: Some(serialize_local_timestamp(transaction.id)),
|
||||||
) -> proto::CodeActionEdit {
|
edit_ids: transaction
|
||||||
proto::CodeActionEdit {
|
.edit_ids
|
||||||
id: Some(serialize_edit_id(edit_id)),
|
.iter()
|
||||||
old_start: Some(serialize_anchor(&old_range.start)),
|
.copied()
|
||||||
old_end: Some(serialize_anchor(&old_range.end)),
|
.map(serialize_local_timestamp)
|
||||||
|
.collect(),
|
||||||
|
start: (&transaction.start).into(),
|
||||||
|
end: (&transaction.end).into(),
|
||||||
|
ranges: transaction.ranges.iter().map(serialize_range).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize_code_action_edit(
|
pub fn deserialize_transaction(transaction: proto::Transaction) -> Result<Transaction> {
|
||||||
edit: proto::CodeActionEdit,
|
Ok(Transaction {
|
||||||
) -> Result<(Range<Anchor>, clock::Local)> {
|
id: deserialize_local_timestamp(
|
||||||
let old_start = edit
|
transaction
|
||||||
.old_start
|
.id
|
||||||
.and_then(deserialize_anchor)
|
.ok_or_else(|| anyhow!("missing transaction id"))?,
|
||||||
.ok_or_else(|| anyhow!("invalid old_start"))?;
|
),
|
||||||
let old_end = edit
|
edit_ids: transaction
|
||||||
.old_end
|
.edit_ids
|
||||||
.and_then(deserialize_anchor)
|
.into_iter()
|
||||||
.ok_or_else(|| anyhow!("invalid old_end"))?;
|
.map(deserialize_local_timestamp)
|
||||||
let edit_id = deserialize_edit_id(edit.id.ok_or_else(|| anyhow!("invalid edit_id"))?);
|
.collect(),
|
||||||
Ok((old_start..old_end, edit_id))
|
start: transaction.start.into(),
|
||||||
|
end: transaction.end.into(),
|
||||||
|
ranges: transaction
|
||||||
|
.ranges
|
||||||
|
.into_iter()
|
||||||
|
.map(deserialize_range)
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_edit_id(edit_id: clock::Local) -> proto::EditId {
|
pub fn serialize_local_timestamp(timestamp: clock::Local) -> proto::LocalTimestamp {
|
||||||
proto::EditId {
|
proto::LocalTimestamp {
|
||||||
replica_id: edit_id.replica_id as u32,
|
replica_id: timestamp.replica_id as u32,
|
||||||
local_timestamp: edit_id.value,
|
value: timestamp.value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize_edit_id(edit_id: proto::EditId) -> clock::Local {
|
pub fn deserialize_local_timestamp(timestamp: proto::LocalTimestamp) -> clock::Local {
|
||||||
clock::Local {
|
clock::Local {
|
||||||
replica_id: edit_id.replica_id as ReplicaId,
|
replica_id: timestamp.replica_id as ReplicaId,
|
||||||
value: edit_id.local_timestamp,
|
value: timestamp.value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn serialize_range(range: &Range<FullOffset>) -> proto::Range {
|
||||||
|
proto::Range {
|
||||||
|
start: range.start.0 as u64,
|
||||||
|
end: range.end.0 as u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_range(range: proto::Range) -> Range<FullOffset> {
|
||||||
|
FullOffset(range.start as usize)..FullOffset(range.end as usize)
|
||||||
|
}
|
||||||
|
|
|
@ -109,6 +109,9 @@ pub struct Definition {
|
||||||
pub target_range: Range<language::Anchor>,
|
pub target_range: Range<language::Anchor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ProjectTransaction(pub HashMap<ModelHandle<Buffer>, language::Transaction>);
|
||||||
|
|
||||||
impl DiagnosticSummary {
|
impl DiagnosticSummary {
|
||||||
fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
|
fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
|
@ -1174,8 +1177,7 @@ impl Project {
|
||||||
mut action: CodeAction<language::Anchor>,
|
mut action: CodeAction<language::Anchor>,
|
||||||
push_to_history: bool,
|
push_to_history: bool,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<HashMap<ModelHandle<Buffer>, Vec<(Range<language::Anchor>, clock::Local)>>>>
|
) -> Task<Result<ProjectTransaction>> {
|
||||||
{
|
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
let buffer = buffer_handle.read(cx);
|
let buffer = buffer_handle.read(cx);
|
||||||
let lang_name = if let Some(lang) = buffer.language() {
|
let lang_name = if let Some(lang) = buffer.language() {
|
||||||
|
@ -1237,7 +1239,7 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut edited_buffers = HashMap::default();
|
let mut project_transaction = ProjectTransaction::default();
|
||||||
for operation in operations {
|
for operation in operations {
|
||||||
match operation {
|
match operation {
|
||||||
lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Create(op)) => {
|
lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Create(op)) => {
|
||||||
|
@ -1298,34 +1300,38 @@ impl Project {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let edits = buffer_to_edit.update(&mut cx, |buffer, cx| {
|
let transaction = buffer_to_edit.update(&mut cx, |buffer, cx| {
|
||||||
let edits = op.edits.into_iter().map(|edit| match edit {
|
let edits = op.edits.into_iter().map(|edit| match edit {
|
||||||
lsp::OneOf::Left(edit) => edit,
|
lsp::OneOf::Left(edit) => edit,
|
||||||
lsp::OneOf::Right(edit) => edit.text_edit,
|
lsp::OneOf::Right(edit) => edit.text_edit,
|
||||||
});
|
});
|
||||||
if !push_to_history {
|
|
||||||
buffer.avoid_grouping_next_transaction();
|
|
||||||
}
|
|
||||||
buffer.start_transaction();
|
|
||||||
let edits =
|
|
||||||
buffer.apply_lsp_edits(edits, op.text_document.version, cx);
|
|
||||||
if let Some(transaction_id) = buffer.end_transaction(cx) {
|
|
||||||
if !push_to_history {
|
|
||||||
buffer.forget_transaction(transaction_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
edits
|
buffer.finalize_last_transaction();
|
||||||
})?;
|
buffer.start_transaction();
|
||||||
edited_buffers
|
buffer
|
||||||
.entry(buffer_to_edit)
|
.apply_lsp_edits(edits, op.text_document.version, cx)
|
||||||
.or_insert(Vec::new())
|
.log_err();
|
||||||
.extend(edits);
|
let transaction = if buffer.end_transaction(cx).is_some() {
|
||||||
|
let transaction =
|
||||||
|
buffer.finalize_last_transaction().unwrap().clone();
|
||||||
|
if !push_to_history {
|
||||||
|
buffer.forget_transaction(transaction.id);
|
||||||
|
}
|
||||||
|
Some(transaction)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
transaction
|
||||||
|
});
|
||||||
|
if let Some(transaction) = transaction {
|
||||||
|
project_transaction.0.insert(buffer_to_edit, transaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(edited_buffers)
|
Ok(project_transaction)
|
||||||
})
|
})
|
||||||
} else if let Some(project_id) = self.remote_id() {
|
} else if let Some(project_id) = self.remote_id() {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
|
@ -1335,35 +1341,34 @@ impl Project {
|
||||||
action: Some(language::proto::serialize_code_action(&action)),
|
action: Some(language::proto::serialize_code_action(&action)),
|
||||||
};
|
};
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let response = client.request(request).await?;
|
let response = client
|
||||||
let mut edited_buffers = HashMap::default();
|
.request(request)
|
||||||
for buffer_edit in response.buffer_edits {
|
.await?
|
||||||
let buffer = buffer_edit
|
.transaction
|
||||||
.buffer
|
.ok_or_else(|| anyhow!("missing transaction"))?;
|
||||||
.ok_or_else(|| anyhow!("invalid buffer"))?;
|
let mut project_transaction = ProjectTransaction::default();
|
||||||
|
for (buffer, transaction) in response.buffers.into_iter().zip(response.transactions)
|
||||||
|
{
|
||||||
let buffer = this.update(&mut cx, |this, cx| {
|
let buffer = this.update(&mut cx, |this, cx| {
|
||||||
this.deserialize_remote_buffer(buffer, cx)
|
this.deserialize_remote_buffer(buffer, cx)
|
||||||
})?;
|
})?;
|
||||||
|
let transaction = language::proto::deserialize_transaction(transaction)?;
|
||||||
let buffer_edits = edited_buffers.entry(buffer.clone()).or_insert(Vec::new());
|
|
||||||
for edit in buffer_edit.edits {
|
|
||||||
buffer_edits.push(language::proto::deserialize_code_action_edit(edit)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
.update(&mut cx, |buffer, _| {
|
.update(&mut cx, |buffer, _| {
|
||||||
buffer.wait_for_edits(buffer_edits.iter().map(|e| e.1))
|
buffer.wait_for_edits(transaction.edit_ids.iter().copied())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if push_to_history {
|
if push_to_history {
|
||||||
buffer.update(&mut cx, |buffer, _| {
|
buffer.update(&mut cx, |buffer, _| {
|
||||||
buffer
|
buffer.push_transaction(transaction.clone(), Instant::now());
|
||||||
.push_transaction(buffer_edits.iter().map(|e| e.1), Instant::now());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project_transaction.0.insert(buffer, transaction);
|
||||||
}
|
}
|
||||||
Ok(edited_buffers)
|
Ok(project_transaction)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Task::ready(Err(anyhow!("project does not have a remote id")))
|
Task::ready(Err(anyhow!("project does not have a remote id")))
|
||||||
|
@ -1975,13 +1980,12 @@ impl Project {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(edit_ids) => rpc.respond(
|
Ok(transaction) => rpc.respond(
|
||||||
receipt,
|
receipt,
|
||||||
proto::ApplyCompletionAdditionalEditsResponse {
|
proto::ApplyCompletionAdditionalEditsResponse {
|
||||||
additional_edits: edit_ids
|
transaction: transaction
|
||||||
.into_iter()
|
.as_ref()
|
||||||
.map(language::proto::serialize_edit_id)
|
.map(language::proto::serialize_transaction),
|
||||||
.collect(),
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Err(error) => rpc.respond_with_error(
|
Err(error) => rpc.respond_with_error(
|
||||||
|
@ -2062,20 +2066,25 @@ impl Project {
|
||||||
let apply_code_action = self.apply_code_action(buffer, action, false, cx);
|
let apply_code_action = self.apply_code_action(buffer, action, false, cx);
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
match apply_code_action.await {
|
match apply_code_action.await {
|
||||||
Ok(edited_buffers) => this.update(&mut cx, |this, cx| {
|
Ok(project_transaction) => this.update(&mut cx, |this, cx| {
|
||||||
let buffer_edits = edited_buffers
|
let mut serialized_transaction = proto::ProjectTransaction {
|
||||||
.into_iter()
|
buffers: Default::default(),
|
||||||
.map(|(buffer, edits)| proto::CodeActionBufferEdits {
|
transactions: Default::default(),
|
||||||
buffer: Some(this.serialize_buffer_for_peer(&buffer, sender_id, cx)),
|
};
|
||||||
edits: edits
|
for (buffer, transaction) in project_transaction.0 {
|
||||||
.into_iter()
|
serialized_transaction
|
||||||
.map(|(range, edit_id)| {
|
.buffers
|
||||||
language::proto::serialize_code_action_edit(edit_id, &range)
|
.push(this.serialize_buffer_for_peer(&buffer, sender_id, cx));
|
||||||
})
|
serialized_transaction
|
||||||
.collect(),
|
.transactions
|
||||||
})
|
.push(language::proto::serialize_transaction(&transaction));
|
||||||
.collect();
|
}
|
||||||
rpc.respond(receipt, proto::ApplyCodeActionResponse { buffer_edits })
|
rpc.respond(
|
||||||
|
receipt,
|
||||||
|
proto::ApplyCodeActionResponse {
|
||||||
|
transaction: Some(serialized_transaction),
|
||||||
|
},
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
Err(error) => rpc.respond_with_error(
|
Err(error) => rpc.respond_with_error(
|
||||||
receipt,
|
receipt,
|
||||||
|
|
|
@ -15,7 +15,7 @@ use gpui::{
|
||||||
Task,
|
Task,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
Anchor, Buffer, Completion, DiagnosticEntry, Language, Operation, PointUtf16, Rope,
|
Anchor, Buffer, Completion, DiagnosticEntry, Language, Operation, PointUtf16, Rope, Transaction,
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -1446,7 +1446,7 @@ impl language::File for File {
|
||||||
buffer_id: u64,
|
buffer_id: u64,
|
||||||
completion: Completion<Anchor>,
|
completion: Completion<Anchor>,
|
||||||
cx: &mut MutableAppContext,
|
cx: &mut MutableAppContext,
|
||||||
) -> Task<Result<Vec<clock::Local>>> {
|
) -> Task<Result<Option<Transaction>>> {
|
||||||
let worktree = self.worktree.read(cx);
|
let worktree = self.worktree.read(cx);
|
||||||
let worktree = if let Some(worktree) = worktree.as_remote() {
|
let worktree = if let Some(worktree) = worktree.as_remote() {
|
||||||
worktree
|
worktree
|
||||||
|
@ -1466,11 +1466,11 @@ impl language::File for File {
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(response
|
if let Some(transaction) = response.transaction {
|
||||||
.additional_edits
|
Ok(Some(language::proto::deserialize_transaction(transaction)?))
|
||||||
.into_iter()
|
} else {
|
||||||
.map(language::proto::deserialize_edit_id)
|
Ok(None)
|
||||||
.collect())
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -228,7 +228,7 @@ message ApplyCompletionAdditionalEdits {
|
||||||
}
|
}
|
||||||
|
|
||||||
message ApplyCompletionAdditionalEditsResponse {
|
message ApplyCompletionAdditionalEditsResponse {
|
||||||
repeated EditId additional_edits = 1;
|
Transaction transaction = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Completion {
|
message Completion {
|
||||||
|
@ -255,7 +255,7 @@ message ApplyCodeAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
message ApplyCodeActionResponse {
|
message ApplyCodeActionResponse {
|
||||||
repeated CodeActionBufferEdits buffer_edits = 1;
|
ProjectTransaction transaction = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CodeAction {
|
message CodeAction {
|
||||||
|
@ -263,20 +263,22 @@ message CodeAction {
|
||||||
bytes lsp_action = 2;
|
bytes lsp_action = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CodeActionBufferEdits {
|
message ProjectTransaction {
|
||||||
Buffer buffer = 1;
|
repeated Buffer buffers = 1;
|
||||||
repeated CodeActionEdit edits = 2;
|
repeated Transaction transactions = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CodeActionEdit {
|
message Transaction {
|
||||||
EditId id = 1;
|
LocalTimestamp id = 1;
|
||||||
Anchor old_start = 2;
|
repeated LocalTimestamp edit_ids = 2;
|
||||||
Anchor old_end = 3;
|
repeated VectorClockEntry start = 3;
|
||||||
|
repeated VectorClockEntry end = 4;
|
||||||
|
repeated Range ranges = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EditId {
|
message LocalTimestamp {
|
||||||
uint32 replica_id = 1;
|
uint32 replica_id = 1;
|
||||||
uint32 local_timestamp = 2;
|
uint32 value = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateDiagnosticSummary {
|
message UpdateDiagnosticSummary {
|
||||||
|
|
|
@ -431,28 +431,28 @@ fn test_undo_redo() {
|
||||||
buffer.edit(vec![3..5], "cd");
|
buffer.edit(vec![3..5], "cd");
|
||||||
assert_eq!(buffer.text(), "1abcdef234");
|
assert_eq!(buffer.text(), "1abcdef234");
|
||||||
|
|
||||||
let transactions = buffer.history.undo_stack.clone();
|
let entries = buffer.history.undo_stack.clone();
|
||||||
assert_eq!(transactions.len(), 3);
|
assert_eq!(entries.len(), 3);
|
||||||
|
|
||||||
buffer.undo_or_redo(transactions[0].clone()).unwrap();
|
buffer.undo_or_redo(entries[0].transaction.clone()).unwrap();
|
||||||
assert_eq!(buffer.text(), "1cdef234");
|
assert_eq!(buffer.text(), "1cdef234");
|
||||||
buffer.undo_or_redo(transactions[0].clone()).unwrap();
|
buffer.undo_or_redo(entries[0].transaction.clone()).unwrap();
|
||||||
assert_eq!(buffer.text(), "1abcdef234");
|
assert_eq!(buffer.text(), "1abcdef234");
|
||||||
|
|
||||||
buffer.undo_or_redo(transactions[1].clone()).unwrap();
|
buffer.undo_or_redo(entries[1].transaction.clone()).unwrap();
|
||||||
assert_eq!(buffer.text(), "1abcdx234");
|
assert_eq!(buffer.text(), "1abcdx234");
|
||||||
buffer.undo_or_redo(transactions[2].clone()).unwrap();
|
buffer.undo_or_redo(entries[2].transaction.clone()).unwrap();
|
||||||
assert_eq!(buffer.text(), "1abx234");
|
assert_eq!(buffer.text(), "1abx234");
|
||||||
buffer.undo_or_redo(transactions[1].clone()).unwrap();
|
buffer.undo_or_redo(entries[1].transaction.clone()).unwrap();
|
||||||
assert_eq!(buffer.text(), "1abyzef234");
|
assert_eq!(buffer.text(), "1abyzef234");
|
||||||
buffer.undo_or_redo(transactions[2].clone()).unwrap();
|
buffer.undo_or_redo(entries[2].transaction.clone()).unwrap();
|
||||||
assert_eq!(buffer.text(), "1abcdef234");
|
assert_eq!(buffer.text(), "1abcdef234");
|
||||||
|
|
||||||
buffer.undo_or_redo(transactions[2].clone()).unwrap();
|
buffer.undo_or_redo(entries[2].transaction.clone()).unwrap();
|
||||||
assert_eq!(buffer.text(), "1abyzef234");
|
assert_eq!(buffer.text(), "1abyzef234");
|
||||||
buffer.undo_or_redo(transactions[0].clone()).unwrap();
|
buffer.undo_or_redo(entries[0].transaction.clone()).unwrap();
|
||||||
assert_eq!(buffer.text(), "1yzef234");
|
assert_eq!(buffer.text(), "1yzef234");
|
||||||
buffer.undo_or_redo(transactions[1].clone()).unwrap();
|
buffer.undo_or_redo(entries[1].transaction.clone()).unwrap();
|
||||||
assert_eq!(buffer.text(), "1234");
|
assert_eq!(buffer.text(), "1234");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,7 +510,7 @@ fn test_avoid_grouping_next_transaction() {
|
||||||
buffer.end_transaction_at(now);
|
buffer.end_transaction_at(now);
|
||||||
assert_eq!(buffer.text(), "12cd56");
|
assert_eq!(buffer.text(), "12cd56");
|
||||||
|
|
||||||
buffer.avoid_grouping_next_transaction();
|
buffer.finalize_last_transaction();
|
||||||
buffer.start_transaction_at(now);
|
buffer.start_transaction_at(now);
|
||||||
buffer.edit(vec![4..5], "e");
|
buffer.edit(vec![4..5], "e");
|
||||||
buffer.end_transaction_at(now).unwrap();
|
buffer.end_transaction_at(now).unwrap();
|
||||||
|
|
|
@ -40,7 +40,7 @@ pub use subscription::*;
|
||||||
pub use sum_tree::Bias;
|
pub use sum_tree::Bias;
|
||||||
use sum_tree::{FilterCursor, SumTree};
|
use sum_tree::{FilterCursor, SumTree};
|
||||||
|
|
||||||
pub type TransactionId = usize;
|
pub type TransactionId = clock::Local;
|
||||||
|
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
snapshot: BufferSnapshot,
|
snapshot: BufferSnapshot,
|
||||||
|
@ -67,28 +67,33 @@ pub struct BufferSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Transaction {
|
pub struct HistoryEntry {
|
||||||
id: TransactionId,
|
transaction: Transaction,
|
||||||
start: clock::Global,
|
|
||||||
end: clock::Global,
|
|
||||||
edits: Vec<clock::Local>,
|
|
||||||
ranges: Vec<Range<FullOffset>>,
|
|
||||||
first_edit_at: Instant,
|
first_edit_at: Instant,
|
||||||
last_edit_at: Instant,
|
last_edit_at: Instant,
|
||||||
suppress_grouping: bool,
|
suppress_grouping: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction {
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Transaction {
|
||||||
|
pub id: TransactionId,
|
||||||
|
pub edit_ids: Vec<clock::Local>,
|
||||||
|
pub start: clock::Global,
|
||||||
|
pub end: clock::Global,
|
||||||
|
pub ranges: Vec<Range<FullOffset>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HistoryEntry {
|
||||||
fn push_edit(&mut self, edit: &EditOperation) {
|
fn push_edit(&mut self, edit: &EditOperation) {
|
||||||
self.edits.push(edit.timestamp.local());
|
self.transaction.edit_ids.push(edit.timestamp.local());
|
||||||
self.end.observe(edit.timestamp.local());
|
self.transaction.end.observe(edit.timestamp.local());
|
||||||
|
|
||||||
let mut other_ranges = edit.ranges.iter().peekable();
|
let mut other_ranges = edit.ranges.iter().peekable();
|
||||||
let mut new_ranges = Vec::new();
|
let mut new_ranges = Vec::new();
|
||||||
let insertion_len = edit.new_text.as_ref().map_or(0, |t| t.len());
|
let insertion_len = edit.new_text.as_ref().map_or(0, |t| t.len());
|
||||||
let mut delta = 0;
|
let mut delta = 0;
|
||||||
|
|
||||||
for mut self_range in self.ranges.iter().cloned() {
|
for mut self_range in self.transaction.ranges.iter().cloned() {
|
||||||
self_range.start += delta;
|
self_range.start += delta;
|
||||||
self_range.end += delta;
|
self_range.end += delta;
|
||||||
|
|
||||||
|
@ -122,7 +127,7 @@ impl Transaction {
|
||||||
delta += insertion_len;
|
delta += insertion_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ranges = new_ranges;
|
self.transaction.ranges = new_ranges;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,11 +136,10 @@ pub struct History {
|
||||||
// TODO: Turn this into a String or Rope, maybe.
|
// TODO: Turn this into a String or Rope, maybe.
|
||||||
pub base_text: Arc<str>,
|
pub base_text: Arc<str>,
|
||||||
operations: HashMap<clock::Local, Operation>,
|
operations: HashMap<clock::Local, Operation>,
|
||||||
undo_stack: Vec<Transaction>,
|
undo_stack: Vec<HistoryEntry>,
|
||||||
redo_stack: Vec<Transaction>,
|
redo_stack: Vec<HistoryEntry>,
|
||||||
transaction_depth: usize,
|
transaction_depth: usize,
|
||||||
group_interval: Duration,
|
group_interval: Duration,
|
||||||
next_transaction_id: TransactionId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl History {
|
impl History {
|
||||||
|
@ -147,7 +151,6 @@ impl History {
|
||||||
redo_stack: Vec::new(),
|
redo_stack: Vec::new(),
|
||||||
transaction_depth: 0,
|
transaction_depth: 0,
|
||||||
group_interval: Duration::from_millis(300),
|
group_interval: Duration::from_millis(300),
|
||||||
next_transaction_id: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,17 +158,23 @@ impl History {
|
||||||
self.operations.insert(op.local_timestamp(), op);
|
self.operations.insert(op.local_timestamp(), op);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_transaction(&mut self, start: clock::Global, now: Instant) -> Option<TransactionId> {
|
fn start_transaction(
|
||||||
|
&mut self,
|
||||||
|
start: clock::Global,
|
||||||
|
now: Instant,
|
||||||
|
local_clock: &mut clock::Local,
|
||||||
|
) -> Option<TransactionId> {
|
||||||
self.transaction_depth += 1;
|
self.transaction_depth += 1;
|
||||||
if self.transaction_depth == 1 {
|
if self.transaction_depth == 1 {
|
||||||
let id = self.next_transaction_id;
|
let id = local_clock.tick();
|
||||||
self.next_transaction_id += 1;
|
self.undo_stack.push(HistoryEntry {
|
||||||
self.undo_stack.push(Transaction {
|
transaction: Transaction {
|
||||||
id,
|
id,
|
||||||
start: start.clone(),
|
start: start.clone(),
|
||||||
end: start,
|
end: start,
|
||||||
edits: Vec::new(),
|
edit_ids: Default::default(),
|
||||||
ranges: Vec::new(),
|
ranges: Default::default(),
|
||||||
|
},
|
||||||
first_edit_at: now,
|
first_edit_at: now,
|
||||||
last_edit_at: now,
|
last_edit_at: now,
|
||||||
suppress_grouping: false,
|
suppress_grouping: false,
|
||||||
|
@ -176,17 +185,24 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_transaction(&mut self, now: Instant) -> Option<&Transaction> {
|
fn end_transaction(&mut self, now: Instant) -> Option<&HistoryEntry> {
|
||||||
assert_ne!(self.transaction_depth, 0);
|
assert_ne!(self.transaction_depth, 0);
|
||||||
self.transaction_depth -= 1;
|
self.transaction_depth -= 1;
|
||||||
if self.transaction_depth == 0 {
|
if self.transaction_depth == 0 {
|
||||||
if self.undo_stack.last().unwrap().ranges.is_empty() {
|
if self
|
||||||
|
.undo_stack
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.transaction
|
||||||
|
.ranges
|
||||||
|
.is_empty()
|
||||||
|
{
|
||||||
self.undo_stack.pop();
|
self.undo_stack.pop();
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let transaction = self.undo_stack.last_mut().unwrap();
|
let entry = self.undo_stack.last_mut().unwrap();
|
||||||
transaction.last_edit_at = now;
|
entry.last_edit_at = now;
|
||||||
Some(transaction)
|
Some(entry)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -195,16 +211,15 @@ impl History {
|
||||||
|
|
||||||
fn group(&mut self) -> Option<TransactionId> {
|
fn group(&mut self) -> Option<TransactionId> {
|
||||||
let mut new_len = self.undo_stack.len();
|
let mut new_len = self.undo_stack.len();
|
||||||
let mut transactions = self.undo_stack.iter_mut();
|
let mut entries = self.undo_stack.iter_mut();
|
||||||
|
|
||||||
if let Some(mut transaction) = transactions.next_back() {
|
if let Some(mut entry) = entries.next_back() {
|
||||||
while let Some(prev_transaction) = transactions.next_back() {
|
while let Some(prev_entry) = entries.next_back() {
|
||||||
if !prev_transaction.suppress_grouping
|
if !prev_entry.suppress_grouping
|
||||||
&& transaction.first_edit_at - prev_transaction.last_edit_at
|
&& entry.first_edit_at - prev_entry.last_edit_at <= self.group_interval
|
||||||
<= self.group_interval
|
&& entry.transaction.start == prev_entry.transaction.end
|
||||||
&& transaction.start == prev_transaction.end
|
|
||||||
{
|
{
|
||||||
transaction = prev_transaction;
|
entry = prev_entry;
|
||||||
new_len -= 1;
|
new_len -= 1;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
@ -212,45 +227,39 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (transactions_to_keep, transactions_to_merge) = self.undo_stack.split_at_mut(new_len);
|
let (entries_to_keep, entries_to_merge) = self.undo_stack.split_at_mut(new_len);
|
||||||
if let Some(last_transaction) = transactions_to_keep.last_mut() {
|
if let Some(last_entry) = entries_to_keep.last_mut() {
|
||||||
for transaction in &*transactions_to_merge {
|
for entry in &*entries_to_merge {
|
||||||
for edit_id in &transaction.edits {
|
for edit_id in &entry.transaction.edit_ids {
|
||||||
last_transaction.push_edit(self.operations[edit_id].as_edit().unwrap());
|
last_entry.push_edit(self.operations[edit_id].as_edit().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(transaction) = transactions_to_merge.last_mut() {
|
if let Some(entry) = entries_to_merge.last_mut() {
|
||||||
last_transaction.last_edit_at = transaction.last_edit_at;
|
last_entry.last_edit_at = entry.last_edit_at;
|
||||||
last_transaction.end = transaction.end.clone();
|
last_entry.transaction.end = entry.transaction.end.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.undo_stack.truncate(new_len);
|
self.undo_stack.truncate(new_len);
|
||||||
self.undo_stack.last().map(|t| t.id)
|
self.undo_stack.last().map(|e| e.transaction.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn avoid_grouping_next_transaction(&mut self) {
|
fn finalize_last_transaction(&mut self) -> Option<&Transaction> {
|
||||||
if let Some(transaction) = self.undo_stack.last_mut() {
|
self.undo_stack.last_mut().map(|entry| {
|
||||||
transaction.suppress_grouping = true;
|
entry.suppress_grouping = true;
|
||||||
}
|
&entry.transaction
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_transaction(&mut self, edit_ids: impl IntoIterator<Item = clock::Local>, now: Instant) {
|
fn push_transaction(&mut self, transaction: Transaction, now: Instant) {
|
||||||
assert_eq!(self.transaction_depth, 0);
|
assert_eq!(self.transaction_depth, 0);
|
||||||
let mut edit_ids = edit_ids.into_iter().peekable();
|
self.undo_stack.push(HistoryEntry {
|
||||||
|
transaction,
|
||||||
if let Some(first_edit) = edit_ids
|
first_edit_at: now,
|
||||||
.peek()
|
last_edit_at: now,
|
||||||
.and_then(|e| self.operations.get(&e)?.as_edit())
|
suppress_grouping: false,
|
||||||
{
|
});
|
||||||
let version = first_edit.version.clone();
|
|
||||||
self.start_transaction(version, now);
|
|
||||||
for edit_id in edit_ids {
|
|
||||||
self.push_undo(edit_id);
|
|
||||||
}
|
|
||||||
self.end_transaction(now);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_undo(&mut self, op_id: clock::Local) {
|
fn push_undo(&mut self, op_id: clock::Local) {
|
||||||
|
@ -261,21 +270,25 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_undo(&mut self) -> Option<&Transaction> {
|
fn pop_undo(&mut self) -> Option<&HistoryEntry> {
|
||||||
assert_eq!(self.transaction_depth, 0);
|
assert_eq!(self.transaction_depth, 0);
|
||||||
if let Some(transaction) = self.undo_stack.pop() {
|
if let Some(entry) = self.undo_stack.pop() {
|
||||||
self.redo_stack.push(transaction);
|
self.redo_stack.push(entry);
|
||||||
self.redo_stack.last()
|
self.redo_stack.last()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&Transaction> {
|
fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&HistoryEntry> {
|
||||||
assert_eq!(self.transaction_depth, 0);
|
assert_eq!(self.transaction_depth, 0);
|
||||||
if let Some(transaction_ix) = self.undo_stack.iter().rposition(|t| t.id == transaction_id) {
|
if let Some(entry_ix) = self
|
||||||
let transaction = self.undo_stack.remove(transaction_ix);
|
.undo_stack
|
||||||
self.redo_stack.push(transaction);
|
.iter()
|
||||||
|
.rposition(|entry| entry.transaction.id == transaction_id)
|
||||||
|
{
|
||||||
|
let entry = self.undo_stack.remove(entry_ix);
|
||||||
|
self.redo_stack.push(entry);
|
||||||
self.redo_stack.last()
|
self.redo_stack.last()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -284,30 +297,40 @@ impl History {
|
||||||
|
|
||||||
fn forget(&mut self, transaction_id: TransactionId) {
|
fn forget(&mut self, transaction_id: TransactionId) {
|
||||||
assert_eq!(self.transaction_depth, 0);
|
assert_eq!(self.transaction_depth, 0);
|
||||||
if let Some(transaction_ix) = self.undo_stack.iter().rposition(|t| t.id == transaction_id) {
|
if let Some(entry_ix) = self
|
||||||
self.undo_stack.remove(transaction_ix);
|
.undo_stack
|
||||||
} else if let Some(transaction_ix) =
|
.iter()
|
||||||
self.redo_stack.iter().rposition(|t| t.id == transaction_id)
|
.rposition(|entry| entry.transaction.id == transaction_id)
|
||||||
{
|
{
|
||||||
self.undo_stack.remove(transaction_ix);
|
self.undo_stack.remove(entry_ix);
|
||||||
|
} else if let Some(entry_ix) = self
|
||||||
|
.redo_stack
|
||||||
|
.iter()
|
||||||
|
.rposition(|entry| entry.transaction.id == transaction_id)
|
||||||
|
{
|
||||||
|
self.undo_stack.remove(entry_ix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_redo(&mut self) -> Option<&Transaction> {
|
fn pop_redo(&mut self) -> Option<&HistoryEntry> {
|
||||||
assert_eq!(self.transaction_depth, 0);
|
assert_eq!(self.transaction_depth, 0);
|
||||||
if let Some(transaction) = self.redo_stack.pop() {
|
if let Some(entry) = self.redo_stack.pop() {
|
||||||
self.undo_stack.push(transaction);
|
self.undo_stack.push(entry);
|
||||||
self.undo_stack.last()
|
self.undo_stack.last()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_from_redo(&mut self, transaction_id: TransactionId) -> Option<&Transaction> {
|
fn remove_from_redo(&mut self, transaction_id: TransactionId) -> Option<&HistoryEntry> {
|
||||||
assert_eq!(self.transaction_depth, 0);
|
assert_eq!(self.transaction_depth, 0);
|
||||||
if let Some(transaction_ix) = self.redo_stack.iter().rposition(|t| t.id == transaction_id) {
|
if let Some(entry_ix) = self
|
||||||
let transaction = self.redo_stack.remove(transaction_ix);
|
.redo_stack
|
||||||
self.undo_stack.push(transaction);
|
.iter()
|
||||||
|
.rposition(|entry| entry.transaction.id == transaction_id)
|
||||||
|
{
|
||||||
|
let entry = self.redo_stack.remove(entry_ix);
|
||||||
|
self.undo_stack.push(entry);
|
||||||
self.undo_stack.last()
|
self.undo_stack.last()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -1123,7 +1146,7 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn peek_undo_stack(&self) -> Option<&Transaction> {
|
pub fn peek_undo_stack(&self) -> Option<&HistoryEntry> {
|
||||||
self.history.undo_stack.last()
|
self.history.undo_stack.last()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1132,7 +1155,8 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_transaction_at(&mut self, now: Instant) -> Option<TransactionId> {
|
pub fn start_transaction_at(&mut self, now: Instant) -> Option<TransactionId> {
|
||||||
self.history.start_transaction(self.version.clone(), now)
|
self.history
|
||||||
|
.start_transaction(self.version.clone(), now, &mut self.local_clock)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_transaction(&mut self) -> Option<(TransactionId, clock::Global)> {
|
pub fn end_transaction(&mut self) -> Option<(TransactionId, clock::Global)> {
|
||||||
|
@ -1140,8 +1164,8 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_transaction_at(&mut self, now: Instant) -> Option<(TransactionId, clock::Global)> {
|
pub fn end_transaction_at(&mut self, now: Instant) -> Option<(TransactionId, clock::Global)> {
|
||||||
if let Some(transaction) = self.history.end_transaction(now) {
|
if let Some(entry) = self.history.end_transaction(now) {
|
||||||
let since = transaction.start.clone();
|
let since = entry.transaction.start.clone();
|
||||||
let id = self.history.group().unwrap();
|
let id = self.history.group().unwrap();
|
||||||
Some((id, since))
|
Some((id, since))
|
||||||
} else {
|
} else {
|
||||||
|
@ -1149,8 +1173,8 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn avoid_grouping_next_transaction(&mut self) {
|
pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> {
|
||||||
self.history.avoid_grouping_next_transaction()
|
self.history.finalize_last_transaction()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base_text(&self) -> &Arc<str> {
|
pub fn base_text(&self) -> &Arc<str> {
|
||||||
|
@ -1169,7 +1193,8 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&mut self) -> Option<(TransactionId, Operation)> {
|
pub fn undo(&mut self) -> Option<(TransactionId, Operation)> {
|
||||||
if let Some(transaction) = self.history.pop_undo().cloned() {
|
if let Some(entry) = self.history.pop_undo() {
|
||||||
|
let transaction = entry.transaction.clone();
|
||||||
let transaction_id = transaction.id;
|
let transaction_id = transaction.id;
|
||||||
let op = self.undo_or_redo(transaction).unwrap();
|
let op = self.undo_or_redo(transaction).unwrap();
|
||||||
Some((transaction_id, op))
|
Some((transaction_id, op))
|
||||||
|
@ -1179,7 +1204,8 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo_transaction(&mut self, transaction_id: TransactionId) -> Option<Operation> {
|
pub fn undo_transaction(&mut self, transaction_id: TransactionId) -> Option<Operation> {
|
||||||
if let Some(transaction) = self.history.remove_from_undo(transaction_id).cloned() {
|
if let Some(entry) = self.history.remove_from_undo(transaction_id) {
|
||||||
|
let transaction = entry.transaction.clone();
|
||||||
let op = self.undo_or_redo(transaction).unwrap();
|
let op = self.undo_or_redo(transaction).unwrap();
|
||||||
Some(op)
|
Some(op)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1192,7 +1218,8 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redo(&mut self) -> Option<(TransactionId, Operation)> {
|
pub fn redo(&mut self) -> Option<(TransactionId, Operation)> {
|
||||||
if let Some(transaction) = self.history.pop_redo().cloned() {
|
if let Some(entry) = self.history.pop_redo() {
|
||||||
|
let transaction = entry.transaction.clone();
|
||||||
let transaction_id = transaction.id;
|
let transaction_id = transaction.id;
|
||||||
let op = self.undo_or_redo(transaction).unwrap();
|
let op = self.undo_or_redo(transaction).unwrap();
|
||||||
Some((transaction_id, op))
|
Some((transaction_id, op))
|
||||||
|
@ -1202,7 +1229,8 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redo_transaction(&mut self, transaction_id: TransactionId) -> Option<Operation> {
|
pub fn redo_transaction(&mut self, transaction_id: TransactionId) -> Option<Operation> {
|
||||||
if let Some(transaction) = self.history.remove_from_redo(transaction_id).cloned() {
|
if let Some(entry) = self.history.remove_from_redo(transaction_id) {
|
||||||
|
let transaction = entry.transaction.clone();
|
||||||
let op = self.undo_or_redo(transaction).unwrap();
|
let op = self.undo_or_redo(transaction).unwrap();
|
||||||
Some(op)
|
Some(op)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1212,7 +1240,7 @@ impl Buffer {
|
||||||
|
|
||||||
fn undo_or_redo(&mut self, transaction: Transaction) -> Result<Operation> {
|
fn undo_or_redo(&mut self, transaction: Transaction) -> Result<Operation> {
|
||||||
let mut counts = HashMap::default();
|
let mut counts = HashMap::default();
|
||||||
for edit_id in transaction.edits {
|
for edit_id in transaction.edit_ids {
|
||||||
counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1);
|
counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1232,12 +1260,9 @@ impl Buffer {
|
||||||
Ok(operation)
|
Ok(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_transaction(
|
pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) {
|
||||||
&mut self,
|
self.history.push_transaction(transaction, now);
|
||||||
edit_ids: impl IntoIterator<Item = clock::Local>,
|
self.history.finalize_last_transaction();
|
||||||
now: Instant,
|
|
||||||
) {
|
|
||||||
self.history.push_transaction(edit_ids, now);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe(&mut self) -> Subscription {
|
pub fn subscribe(&mut self) -> Subscription {
|
||||||
|
@ -1364,7 +1389,8 @@ impl Buffer {
|
||||||
|
|
||||||
let mut ops = Vec::new();
|
let mut ops = Vec::new();
|
||||||
for _ in 0..rng.gen_range(1..=5) {
|
for _ in 0..rng.gen_range(1..=5) {
|
||||||
if let Some(transaction) = self.history.undo_stack.choose(rng).cloned() {
|
if let Some(entry) = self.history.undo_stack.choose(rng) {
|
||||||
|
let transaction = entry.transaction.clone();
|
||||||
log::info!(
|
log::info!(
|
||||||
"undoing buffer {} transaction {:?}",
|
"undoing buffer {} transaction {:?}",
|
||||||
self.replica_id,
|
self.replica_id,
|
||||||
|
|
Loading…
Reference in a new issue