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
|
@ -38,7 +38,7 @@ use text::{operation_queue::OperationQueue, rope::TextDimension};
|
|||
pub use text::{Buffer as TextBuffer, Operation as _, *};
|
||||
use theme::SyntaxTheme;
|
||||
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"))]
|
||||
pub use tree_sitter_rust;
|
||||
|
@ -214,7 +214,7 @@ pub trait File {
|
|||
buffer_id: u64,
|
||||
completion: Completion<Anchor>,
|
||||
cx: &mut MutableAppContext,
|
||||
) -> Task<Result<Vec<clock::Local>>>;
|
||||
) -> Task<Result<Option<Transaction>>>;
|
||||
|
||||
fn code_actions(
|
||||
&self,
|
||||
|
@ -307,7 +307,7 @@ impl File for FakeFile {
|
|||
_: u64,
|
||||
_: Completion<Anchor>,
|
||||
_: &mut MutableAppContext,
|
||||
) -> Task<Result<Vec<clock::Local>>> {
|
||||
) -> Task<Result<Option<Transaction>>> {
|
||||
Task::ready(Ok(Default::default()))
|
||||
}
|
||||
|
||||
|
@ -1339,16 +1339,12 @@ impl Buffer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn push_transaction(
|
||||
&mut self,
|
||||
edit_ids: impl IntoIterator<Item = clock::Local>,
|
||||
now: Instant,
|
||||
) {
|
||||
self.text.push_transaction(edit_ids, now);
|
||||
pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) {
|
||||
self.text.push_transaction(transaction, now);
|
||||
}
|
||||
|
||||
pub fn avoid_grouping_next_transaction(&mut self) {
|
||||
self.text.avoid_grouping_next_transaction();
|
||||
pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> {
|
||||
self.text.finalize_last_transaction()
|
||||
}
|
||||
|
||||
pub fn forget_transaction(&mut self, transaction_id: TransactionId) {
|
||||
|
@ -1536,7 +1532,7 @@ impl Buffer {
|
|||
edits: impl IntoIterator<Item = lsp::TextEdit>,
|
||||
version: Option<i32>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<Vec<(Range<Anchor>, clock::Local)>> {
|
||||
) -> Result<()> {
|
||||
let mut anchored_edits = Vec::new();
|
||||
let snapshot =
|
||||
if let Some((version, language_server)) = version.zip(self.language_server.as_mut()) {
|
||||
|
@ -1560,14 +1556,11 @@ impl Buffer {
|
|||
}
|
||||
|
||||
self.start_transaction();
|
||||
let edit_ids = anchored_edits
|
||||
.into_iter()
|
||||
.filter_map(|(range, new_text)| {
|
||||
Some((range.clone(), self.edit([range], new_text, cx)?))
|
||||
})
|
||||
.collect();
|
||||
for (range, new_text) in anchored_edits {
|
||||
self.edit([range], new_text, cx);
|
||||
}
|
||||
self.end_transaction(cx);
|
||||
Ok(edit_ids)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn did_edit(
|
||||
|
@ -1941,7 +1934,7 @@ impl Buffer {
|
|||
completion: Completion<Anchor>,
|
||||
push_to_history: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<clock::Local>>> {
|
||||
) -> Task<Result<Option<Transaction>>> {
|
||||
let file = if let Some(file) = self.file.as_ref() {
|
||||
file
|
||||
} else {
|
||||
|
@ -1961,20 +1954,22 @@ impl Buffer {
|
|||
.await?;
|
||||
if let Some(additional_edits) = resolved_completion.additional_text_edits {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if !push_to_history {
|
||||
this.avoid_grouping_next_transaction();
|
||||
}
|
||||
this.finalize_last_transaction();
|
||||
this.start_transaction();
|
||||
let edits = this.apply_lsp_edits(additional_edits, None, cx);
|
||||
if let Some(transaction_id) = this.end_transaction(cx) {
|
||||
this.apply_lsp_edits(additional_edits, None, cx).log_err();
|
||||
let transaction = if this.end_transaction(cx).is_some() {
|
||||
let transaction = this.finalize_last_transaction().unwrap().clone();
|
||||
if !push_to_history {
|
||||
this.forget_transaction(transaction_id);
|
||||
this.forget_transaction(transaction.id);
|
||||
}
|
||||
}
|
||||
Ok(edits?.into_iter().map(|(_, edit_id)| edit_id).collect())
|
||||
Some(transaction)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(transaction)
|
||||
})
|
||||
} else {
|
||||
Ok(Default::default())
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
@ -1984,17 +1979,20 @@ impl Buffer {
|
|||
cx.as_mut(),
|
||||
);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let edit_ids = apply_edits.await?;
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.wait_for_edits(edit_ids.iter().copied())
|
||||
})
|
||||
.await;
|
||||
if push_to_history {
|
||||
if let Some(transaction) = apply_edits.await? {
|
||||
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,
|
||||
local_timestamp: undo.id.value,
|
||||
lamport_timestamp: lamport_timestamp.value,
|
||||
ranges: undo
|
||||
.ranges
|
||||
.iter()
|
||||
.map(|r| proto::Range {
|
||||
start: r.start.0 as u64,
|
||||
end: r.end.0 as u64,
|
||||
})
|
||||
.collect(),
|
||||
ranges: undo.ranges.iter().map(serialize_range).collect(),
|
||||
counts: undo
|
||||
.counts
|
||||
.iter()
|
||||
|
@ -75,20 +68,12 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation {
|
|||
}
|
||||
|
||||
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 {
|
||||
replica_id: operation.timestamp.replica_id as u32,
|
||||
local_timestamp: operation.timestamp.local,
|
||||
lamport_timestamp: operation.timestamp.lamport,
|
||||
version: From::from(&operation.version),
|
||||
ranges,
|
||||
ranges: operation.ranges.iter().map(serialize_range).collect(),
|
||||
new_text: operation.new_text.clone(),
|
||||
}
|
||||
}
|
||||
|
@ -211,11 +196,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
|||
)
|
||||
})
|
||||
.collect(),
|
||||
ranges: undo
|
||||
.ranges
|
||||
.into_iter()
|
||||
.map(|r| FullOffset(r.start as usize)..FullOffset(r.end as usize))
|
||||
.collect(),
|
||||
ranges: undo.ranges.into_iter().map(deserialize_range).collect(),
|
||||
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 {
|
||||
let ranges = edit
|
||||
.ranges
|
||||
.into_iter()
|
||||
.map(|range| FullOffset(range.start as usize)..FullOffset(range.end as usize))
|
||||
.collect();
|
||||
EditOperation {
|
||||
timestamp: InsertionTimestamp {
|
||||
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,
|
||||
},
|
||||
version: edit.version.into(),
|
||||
ranges,
|
||||
ranges: edit.ranges.into_iter().map(deserialize_range).collect(),
|
||||
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(
|
||||
edit_id: clock::Local,
|
||||
old_range: &Range<Anchor>,
|
||||
) -> proto::CodeActionEdit {
|
||||
proto::CodeActionEdit {
|
||||
id: Some(serialize_edit_id(edit_id)),
|
||||
old_start: Some(serialize_anchor(&old_range.start)),
|
||||
old_end: Some(serialize_anchor(&old_range.end)),
|
||||
pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction {
|
||||
proto::Transaction {
|
||||
id: Some(serialize_local_timestamp(transaction.id)),
|
||||
edit_ids: transaction
|
||||
.edit_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.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(
|
||||
edit: proto::CodeActionEdit,
|
||||
) -> Result<(Range<Anchor>, clock::Local)> {
|
||||
let old_start = edit
|
||||
.old_start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid old_start"))?;
|
||||
let old_end = edit
|
||||
.old_end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid old_end"))?;
|
||||
let edit_id = deserialize_edit_id(edit.id.ok_or_else(|| anyhow!("invalid edit_id"))?);
|
||||
Ok((old_start..old_end, edit_id))
|
||||
pub fn deserialize_transaction(transaction: proto::Transaction) -> Result<Transaction> {
|
||||
Ok(Transaction {
|
||||
id: deserialize_local_timestamp(
|
||||
transaction
|
||||
.id
|
||||
.ok_or_else(|| anyhow!("missing transaction id"))?,
|
||||
),
|
||||
edit_ids: transaction
|
||||
.edit_ids
|
||||
.into_iter()
|
||||
.map(deserialize_local_timestamp)
|
||||
.collect(),
|
||||
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 {
|
||||
proto::EditId {
|
||||
replica_id: edit_id.replica_id as u32,
|
||||
local_timestamp: edit_id.value,
|
||||
pub fn serialize_local_timestamp(timestamp: clock::Local) -> proto::LocalTimestamp {
|
||||
proto::LocalTimestamp {
|
||||
replica_id: timestamp.replica_id as u32,
|
||||
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 {
|
||||
replica_id: edit_id.replica_id as ReplicaId,
|
||||
value: edit_id.local_timestamp,
|
||||
replica_id: timestamp.replica_id as ReplicaId,
|
||||
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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue