From d765e75bad1a9d8553b7939a877c125af48a5343 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Feb 2022 16:22:38 +0100 Subject: [PATCH] Apply additional edits for completion when the buffer is remote --- Cargo.lock | 1 + crates/editor/src/editor.rs | 4 +- crates/editor/src/multi_buffer.rs | 25 ++++-- crates/language/Cargo.toml | 1 + crates/language/src/buffer.rs | 121 +++++++++++++++++++++++------- crates/language/src/proto.rs | 27 ++++++- crates/project/src/project.rs | 71 +++++++++++++++--- crates/project/src/worktree.rs | 52 +++++++++---- crates/rpc/proto/zed.proto | 43 +++++++---- crates/rpc/src/proto.rs | 7 ++ crates/server/src/rpc.rs | 25 ++++++ crates/text/src/text.rs | 42 ++++++++++- 12 files changed, 342 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 986d46d545..719e231f96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2635,6 +2635,7 @@ dependencies = [ "rand 0.8.3", "rpc", "serde", + "serde_json", "similar", "smallvec", "smol", diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a0e61f053b..0acfef3086 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1683,9 +1683,9 @@ impl Editor { }); } - self.buffer.update(cx, |buffer, cx| { + Some(self.buffer.update(cx, |buffer, cx| { buffer.apply_additional_edits_for_completion(completion.clone(), cx) - }) + })) } pub fn has_completions(&self) -> bool { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 6f74a419fe..bce223fb88 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -313,9 +313,9 @@ impl MultiBuffer { .map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot)); return buffer.update(cx, |buffer, cx| { if autoindent { - buffer.edit_with_autoindent(ranges, new_text, cx) + buffer.edit_with_autoindent(ranges, new_text, cx); } else { - buffer.edit(ranges, new_text, cx) + buffer.edit(ranges, new_text, cx); } }); } @@ -922,14 +922,18 @@ impl MultiBuffer { &self, completion: Completion, cx: &mut ModelContext, - ) -> Option>> { - let buffer = self + ) -> Task> { + let buffer = if let Some(buffer_state) = self .buffers .borrow() - .get(&completion.old_range.start.buffer_id)? - .buffer - .clone(); - buffer.update(cx, |buffer, cx| { + .get(&completion.old_range.start.buffer_id) + { + buffer_state.buffer.clone() + } else { + return Task::ready(Ok(())); + }; + + let apply_edits = buffer.update(cx, |buffer, cx| { buffer.apply_additional_edits_for_completion( Completion { old_range: completion.old_range.start.text_anchor @@ -937,8 +941,13 @@ impl MultiBuffer { new_text: completion.new_text, lsp_completion: completion.lsp_completion, }, + true, cx, ) + }); + cx.foreground().spawn(async move { + apply_edits.await?; + Ok(()) }) } diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 6c29708b5e..f0a3096a93 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -36,6 +36,7 @@ parking_lot = "0.11.1" postage = { version = "0.4.1", features = ["futures-traits"] } rand = { version = "0.8.3", optional = true } serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["preserve_order"] } similar = "1.3" smallvec = { version = "1.6", features = ["union"] } smol = "1.2" diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 29b29e26ed..f4554e0d45 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -206,6 +206,13 @@ pub trait File { cx: &mut MutableAppContext, ) -> Task>>>; + fn apply_additional_edits_for_completion( + &self, + buffer_id: u64, + completion: Completion, + cx: &mut MutableAppContext, + ) -> Task>>; + fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext); fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext); @@ -284,6 +291,15 @@ impl File for FakeFile { Task::ready(Ok(Default::default())) } + fn apply_additional_edits_for_completion( + &self, + _: u64, + _: Completion, + _: &mut MutableAppContext, + ) -> Task>> { + Task::ready(Ok(Default::default())) + } + fn buffer_updated(&self, _: u64, _: Operation, _: &mut MutableAppContext) {} fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) {} @@ -595,7 +611,8 @@ impl Buffer { if let Some(edits) = edits { this.update(&mut cx, |this, cx| { if this.version == version { - this.apply_lsp_edits(edits, cx) + this.apply_lsp_edits(edits, cx)?; + Ok(()) } else { Err(anyhow!("buffer edited since starting to format")) } @@ -1295,7 +1312,9 @@ impl Buffer { let range = offset..(offset + len); match tag { ChangeTag::Equal => offset += len, - ChangeTag::Delete => self.edit(Some(range), "", cx), + ChangeTag::Delete => { + self.edit(Some(range), "", cx); + } ChangeTag::Insert => { self.edit(Some(offset..offset), &diff.new_text[range], cx); offset += len; @@ -1409,7 +1428,12 @@ impl Buffer { .blocking_send(Some(snapshot)); } - pub fn edit(&mut self, ranges_iter: I, new_text: T, cx: &mut ModelContext) + pub fn edit( + &mut self, + ranges_iter: I, + new_text: T, + cx: &mut ModelContext, + ) -> Option where I: IntoIterator>, S: ToOffset, @@ -1423,7 +1447,8 @@ impl Buffer { ranges_iter: I, new_text: T, cx: &mut ModelContext, - ) where + ) -> Option + where I: IntoIterator>, S: ToOffset, T: Into, @@ -1437,7 +1462,8 @@ impl Buffer { new_text: T, autoindent: bool, cx: &mut ModelContext, - ) where + ) -> Option + where I: IntoIterator>, S: ToOffset, T: Into, @@ -1461,7 +1487,7 @@ impl Buffer { } } if ranges.is_empty() { - return; + return None; } self.start_transaction(); @@ -1488,6 +1514,7 @@ impl Buffer { let new_text_len = new_text.len(); let edit = self.text.edit(ranges.iter().cloned(), new_text); + let edit_id = edit.timestamp.local(); if let Some((before_edit, edited)) = autoindent_request { let mut inserted = None; @@ -1517,13 +1544,14 @@ impl Buffer { self.end_transaction(cx); self.send_operation(Operation::Buffer(text::Operation::Edit(edit)), cx); + Some(edit_id) } fn apply_lsp_edits( &mut self, edits: Vec, cx: &mut ModelContext, - ) -> Result<()> { + ) -> Result> { for edit in &edits { let range = range_from_lsp(edit.range); if self.clip_point_utf16(range.start, Bias::Left) != range.start @@ -1535,11 +1563,14 @@ impl Buffer { } } - for edit in edits.into_iter().rev() { - self.edit([range_from_lsp(edit.range)], edit.new_text, cx); - } - - Ok(()) + self.start_transaction(); + let edit_ids = edits + .into_iter() + .rev() + .filter_map(|edit| self.edit([range_from_lsp(edit.range)], edit.new_text, cx)) + .collect(); + self.end_transaction(cx); + Ok(edit_ids) } fn did_edit( @@ -1835,21 +1866,59 @@ impl Buffer { pub fn apply_additional_edits_for_completion( &mut self, completion: Completion, + push_to_history: bool, cx: &mut ModelContext, - ) -> Option>> { - self.file.as_ref()?.as_local()?; - let server = self.language_server.as_ref()?.server.clone(); - Some(cx.spawn(|this, mut cx| async move { - let resolved_completion = server - .request::(completion.lsp_completion) - .await?; - if let Some(additional_edits) = resolved_completion.additional_text_edits { - this.update(&mut cx, |this, cx| { - this.apply_lsp_edits(additional_edits, cx) - })?; - } - Ok::<_, anyhow::Error>(()) - })) + ) -> Task>> { + let file = if let Some(file) = self.file.as_ref() { + file + } else { + return Task::ready(Ok(Default::default())); + }; + + if file.is_local() { + let server = if let Some(lang) = self.language_server.as_ref() { + lang.server.clone() + } else { + return Task::ready(Ok(Default::default())); + }; + + cx.spawn(|this, mut cx| async move { + let resolved_completion = server + .request::(completion.lsp_completion) + .await?; + if let Some(additional_edits) = resolved_completion.additional_text_edits { + this.update(&mut cx, |this, cx| { + this.avoid_grouping_next_transaction(); + this.start_transaction(); + let edit_ids = this.apply_lsp_edits(additional_edits, cx); + if let Some(transaction_id) = this.end_transaction(cx) { + if !push_to_history { + this.text.forget_transaction(transaction_id); + } + } + edit_ids + }) + } else { + Ok(Default::default()) + } + }) + } else { + let apply_edits = file.apply_additional_edits_for_completion( + self.remote_id(), + completion, + cx.as_mut(), + ); + cx.spawn(|this, mut cx| async move { + let edit_ids = apply_edits.await?; + if push_to_history { + this.update(&mut cx, |this, _| { + this.text + .push_transaction(edit_ids.iter().copied(), Instant::now()); + }); + } + Ok(edit_ids) + }) + } } pub fn completion_triggers(&self) -> &[String] { diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index ec75018148..c95735493d 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,4 +1,4 @@ -use crate::{diagnostic_set::DiagnosticEntry, Diagnostic, Operation}; +use crate::{diagnostic_set::DiagnosticEntry, Completion, Diagnostic, Operation}; use anyhow::{anyhow, Result}; use clock::ReplicaId; use collections::HashSet; @@ -377,3 +377,28 @@ pub fn deserialize_anchor(anchor: proto::Anchor) -> Option { }, }) } + +pub fn serialize_completion(completion: &Completion) -> proto::Completion { + proto::Completion { + old_start: Some(serialize_anchor(&completion.old_range.start)), + old_end: Some(serialize_anchor(&completion.old_range.end)), + new_text: completion.new_text.clone(), + lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(), + } +} + +pub fn deserialize_completion(completion: proto::Completion) -> Result> { + let old_start = completion + .old_start + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("invalid old start"))?; + let old_end = completion + .old_end + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("invalid old end"))?; + Ok(Completion { + old_range: old_start..old_end, + new_text: completion.new_text, + lsp_completion: serde_json::from_slice(&completion.lsp_completion)?, + }) +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7109f02cec..d01fb08ee7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -335,6 +335,11 @@ impl Project { client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved), client.subscribe_to_entity(remote_id, cx, Self::handle_format_buffer), client.subscribe_to_entity(remote_id, cx, Self::handle_get_completions), + client.subscribe_to_entity( + remote_id, + cx, + Self::handle_apply_additional_edits_for_completion, + ), client.subscribe_to_entity(remote_id, cx, Self::handle_get_definition), ]); } @@ -1712,17 +1717,63 @@ impl Project { receipt, proto::GetCompletionsResponse { completions: completions + .iter() + .map(language::proto::serialize_completion) + .collect(), + }, + ) + .await + } + Err(error) => { + rpc.respond_with_error( + receipt, + proto::Error { + message: error.to_string(), + }, + ) + .await + } + } + }) + .detach_and_log_err(cx); + Ok(()) + } + + fn handle_apply_additional_edits_for_completion( + &mut self, + envelope: TypedEnvelope, + rpc: Arc, + cx: &mut ModelContext, + ) -> Result<()> { + let receipt = envelope.receipt(); + let sender_id = envelope.original_sender_id()?; + let buffer = self + .shared_buffers + .get(&sender_id) + .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned()) + .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?; + let completion = language::proto::deserialize_completion( + envelope + .payload + .completion + .ok_or_else(|| anyhow!("invalid position"))?, + )?; + cx.spawn(|_, mut cx| async move { + match buffer + .update(&mut cx, |buffer, cx| { + buffer.apply_additional_edits_for_completion(completion, false, cx) + }) + .await + { + Ok(edit_ids) => { + rpc.respond( + receipt, + proto::ApplyCompletionAdditionalEditsResponse { + additional_edits: edit_ids .into_iter() - .map(|completion| proto::Completion { - old_start: Some(language::proto::serialize_anchor( - &completion.old_range.start, - )), - old_end: Some(language::proto::serialize_anchor( - &completion.old_range.end, - )), - new_text: completion.new_text, - lsp_completion: serde_json::to_vec(&completion.lsp_completion) - .unwrap(), + .map(|edit_id| proto::AdditionalEdit { + replica_id: edit_id.replica_id as u32, + local_timestamp: edit_id.value, }) .collect(), }, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 77d9d4293f..1f654751cb 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1448,25 +1448,47 @@ impl language::File for File { response .completions .into_iter() - .map(|completion| { - let old_start = completion - .old_start - .and_then(language::proto::deserialize_anchor) - .ok_or_else(|| anyhow!("invalid old start"))?; - let old_end = completion - .old_end - .and_then(language::proto::deserialize_anchor) - .ok_or_else(|| anyhow!("invalid old end"))?; - Ok(Completion { - old_range: old_start..old_end, - new_text: completion.new_text, - lsp_completion: serde_json::from_slice(&completion.lsp_completion)?, - }) - }) + .map(language::proto::deserialize_completion) .collect() }) } + fn apply_additional_edits_for_completion( + &self, + buffer_id: u64, + completion: Completion, + cx: &mut MutableAppContext, + ) -> Task>> { + let worktree = self.worktree.read(cx); + let worktree = if let Some(worktree) = worktree.as_remote() { + worktree + } else { + return Task::ready(Err(anyhow!( + "remote additional edits application requested on a local worktree" + ))); + }; + let rpc = worktree.client.clone(); + let project_id = worktree.project_id; + cx.foreground().spawn(async move { + let response = rpc + .request(proto::ApplyCompletionAdditionalEdits { + project_id, + buffer_id, + completion: Some(language::proto::serialize_completion(&completion)), + }) + .await?; + + Ok(response + .additional_edits + .into_iter() + .map(|edit| clock::Local { + replica_id: edit.replica_id as ReplicaId, + value: edit.local_timestamp, + }) + .collect()) + }) + } + fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext) { self.worktree.update(cx, |worktree, cx| { worktree.send_buffer_update(buffer_id, operation, cx); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index beb41a2ec5..2f2364fc24 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -42,22 +42,24 @@ message Envelope { FormatBuffer format_buffer = 34; GetCompletions get_completions = 35; GetCompletionsResponse get_completions_response = 36; + ApplyCompletionAdditionalEdits apply_completion_additional_edits = 37; + ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 38; - GetChannels get_channels = 37; - GetChannelsResponse get_channels_response = 38; - JoinChannel join_channel = 39; - JoinChannelResponse join_channel_response = 40; - LeaveChannel leave_channel = 41; - SendChannelMessage send_channel_message = 42; - SendChannelMessageResponse send_channel_message_response = 43; - ChannelMessageSent channel_message_sent = 44; - GetChannelMessages get_channel_messages = 45; - GetChannelMessagesResponse get_channel_messages_response = 46; + GetChannels get_channels = 39; + GetChannelsResponse get_channels_response = 40; + JoinChannel join_channel = 41; + JoinChannelResponse join_channel_response = 42; + LeaveChannel leave_channel = 43; + SendChannelMessage send_channel_message = 44; + SendChannelMessageResponse send_channel_message_response = 45; + ChannelMessageSent channel_message_sent = 46; + GetChannelMessages get_channel_messages = 47; + GetChannelMessagesResponse get_channel_messages_response = 48; - UpdateContacts update_contacts = 47; + UpdateContacts update_contacts = 49; - GetUsers get_users = 48; - GetUsersResponse get_users_response = 49; + GetUsers get_users = 50; + GetUsersResponse get_users_response = 51; } } @@ -215,6 +217,21 @@ message GetCompletionsResponse { repeated Completion completions = 1; } +message ApplyCompletionAdditionalEdits { + uint64 project_id = 1; + uint64 buffer_id = 2; + Completion completion = 3; +} + +message ApplyCompletionAdditionalEditsResponse { + repeated AdditionalEdit additional_edits = 1; +} + +message AdditionalEdit { + uint32 replica_id = 1; + uint32 local_timestamp = 2; +} + message Completion { Anchor old_start = 1; Anchor old_end = 2; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 166d9e44b9..a940795265 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -122,6 +122,8 @@ macro_rules! entity_messages { messages!( Ack, AddProjectCollaborator, + ApplyCompletionAdditionalEdits, + ApplyCompletionAdditionalEditsResponse, BufferReloaded, BufferSaved, ChannelMessageSent, @@ -169,6 +171,10 @@ messages!( ); request_messages!( + ( + ApplyCompletionAdditionalEdits, + ApplyCompletionAdditionalEditsResponse + ), (FormatBuffer, Ack), (GetChannelMessages, GetChannelMessagesResponse), (GetChannels, GetChannelsResponse), @@ -191,6 +197,7 @@ request_messages!( entity_messages!( project_id, AddProjectCollaborator, + ApplyCompletionAdditionalEdits, BufferReloaded, BufferSaved, CloseBuffer, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 5e14cafd2e..70e55c66f9 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -84,6 +84,7 @@ impl Server { .add_handler(Server::save_buffer) .add_handler(Server::format_buffer) .add_handler(Server::get_completions) + .add_handler(Server::apply_additional_edits_for_completion) .add_handler(Server::get_channels) .add_handler(Server::get_users) .add_handler(Server::join_channel) @@ -747,6 +748,30 @@ impl Server { Ok(()) } + async fn apply_additional_edits_for_completion( + self: Arc, + request: TypedEnvelope, + ) -> tide::Result<()> { + let host; + { + let state = self.state(); + let project = state + .read_project(request.payload.project_id, request.sender_id) + .ok_or_else(|| anyhow!(NO_SUCH_PROJECT))?; + host = project.host_connection_id; + } + + let sender = request.sender_id; + let receipt = request.receipt(); + let response = self + .peer + .forward_request(sender, host, request.payload.clone()) + .await?; + self.peer.respond(receipt, response).await?; + + Ok(()) + } + async fn update_buffer( self: Arc, request: TypedEnvelope, diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index b619aba2ce..ad9857e264 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -233,6 +233,20 @@ impl History { } } + fn push_transaction(&mut self, edit_ids: impl IntoIterator, now: Instant) { + assert_eq!(self.transaction_depth, 0); + let mut edit_ids = edit_ids.into_iter().peekable(); + + if let Some(first_edit_id) = edit_ids.peek() { + let version = self.ops[first_edit_id].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, edit_id: clock::Local) { assert_ne!(self.transaction_depth, 0); let last_transaction = self.undo_stack.last_mut().unwrap(); @@ -260,6 +274,17 @@ impl History { } } + fn forget(&mut self, transaction_id: TransactionId) { + assert_eq!(self.transaction_depth, 0); + if let Some(transaction_ix) = self.undo_stack.iter().rposition(|t| t.id == transaction_id) { + self.undo_stack.remove(transaction_ix); + } else if let Some(transaction_ix) = + self.redo_stack.iter().rposition(|t| t.id == transaction_id) + { + self.undo_stack.remove(transaction_ix); + } + } + fn pop_redo(&mut self) -> Option<&Transaction> { assert_eq!(self.transaction_depth, 0); if let Some(transaction) = self.redo_stack.pop() { @@ -377,14 +402,14 @@ pub struct InsertionTimestamp { } impl InsertionTimestamp { - fn local(&self) -> clock::Local { + pub fn local(&self) -> clock::Local { clock::Local { replica_id: self.replica_id, value: self.local, } } - fn lamport(&self) -> clock::Lamport { + pub fn lamport(&self) -> clock::Lamport { clock::Lamport { replica_id: self.replica_id, value: self.lamport, @@ -1188,6 +1213,7 @@ impl Buffer { pub fn undo(&mut self) -> Option<(TransactionId, Operation)> { if let Some(transaction) = self.history.pop_undo().cloned() { + dbg!(&transaction); let transaction_id = transaction.id; let op = self.undo_or_redo(transaction).unwrap(); Some((transaction_id, op)) @@ -1205,6 +1231,10 @@ impl Buffer { } } + pub fn forget_transaction(&mut self, transaction_id: TransactionId) { + self.history.forget(transaction_id); + } + pub fn redo(&mut self) -> Option<(TransactionId, Operation)> { if let Some(transaction) = self.history.pop_redo().cloned() { let transaction_id = transaction.id; @@ -1245,6 +1275,14 @@ impl Buffer { }) } + pub fn push_transaction( + &mut self, + edit_ids: impl IntoIterator, + now: Instant, + ) { + self.history.push_transaction(edit_ids, now); + } + pub fn subscribe(&mut self) -> Subscription { self.subscriptions.subscribe() }