From f9723ae16b9c9aa63d3b37626f9a4141f550f583 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Feb 2022 11:41:47 +0100 Subject: [PATCH] Undo temporary edits before performing rename or canceling it Also, wire up remote renames. --- crates/editor/src/editor.rs | 76 +++++++++++++++++++-------- crates/editor/src/multi_buffer.rs | 60 +++++++++++++++++++++- crates/language/src/buffer.rs | 6 ++- crates/project/src/lsp_command.rs | 26 +++++++--- crates/project/src/project.rs | 85 +++++++++++++++++++++++++++++++ crates/rpc/src/proto.rs | 1 + crates/server/src/rpc.rs | 30 +++++++++++ crates/text/src/text.rs | 72 ++++++++++++++++---------- crates/util/src/lib.rs | 35 ++++++++++++- 9 files changed, 331 insertions(+), 60 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b1ce9e3454..47238646ea 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -439,6 +439,7 @@ pub struct Editor { next_completion_id: CompletionId, available_code_actions: Option<(ModelHandle, Arc<[CodeAction]>)>, code_actions_task: Option>, + pending_rename: Option, } pub struct EditorSnapshot { @@ -477,6 +478,11 @@ struct SnippetState { active_index: usize, } +struct RenameState { + range: Range, + first_transaction: Option, +} + struct InvalidationStack(Vec); enum ContextMenu { @@ -892,6 +898,7 @@ impl Editor { next_completion_id: 0, available_code_actions: Default::default(), code_actions_task: Default::default(), + pending_rename: Default::default(), }; this.end_selection(cx); this @@ -1913,6 +1920,10 @@ impl Editor { } fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext) { + if self.pending_rename.is_some() { + return; + } + let project = if let Some(project) = self.project.clone() { project } else { @@ -4101,11 +4112,17 @@ impl Editor { let offset = position.to_offset(&buffer); let start = offset - lookbehind; let end = offset + lookahead; - let highlight_range = buffer.anchor_before(start)..buffer.anchor_after(end); + let rename_range = buffer.anchor_before(start)..buffer.anchor_after(end); drop(buffer); + this.buffer + .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx)); + this.pending_rename = Some(RenameState { + range: rename_range.clone(), + first_transaction: None, + }); this.select_ranges([start..end], None, cx); - this.highlight_ranges::(vec![highlight_range], Color::red(), cx); + this.highlight_ranges::(vec![rename_range], Color::red(), cx); }); } @@ -4121,17 +4138,16 @@ impl Editor { let editor = workspace.active_item(cx)?.act_as::(cx)?; let (buffer, position, new_name) = editor.update(cx, |editor, cx| { - let range = editor.take_rename_range(cx)?; - let multibuffer = editor.buffer.read(cx); - let (buffer, position) = - multibuffer.text_anchor_for_position(range.start.clone(), cx)?; - let snapshot = multibuffer.read(cx); - let new_name = snapshot.text_for_range(range.clone()).collect::(); + let (range, new_name) = editor.take_rename(cx)?; + let (buffer, position) = editor + .buffer + .read(cx) + .text_anchor_for_position(range.start.clone(), cx)?; Some((buffer, position, new_name)) })?; let rename = workspace.project().clone().update(cx, |project, cx| { - project.perform_rename(buffer, position, new_name.clone(), cx) + project.perform_rename(buffer, position, new_name.clone(), true, cx) }); Some(cx.spawn(|workspace, cx| async move { @@ -4147,14 +4163,23 @@ impl Editor { })) } - fn rename_range(&self) -> Option<&Range> { - self.highlighted_ranges_for_type::() - .and_then(|(_, range)| range.last()) - } + fn take_rename(&mut self, cx: &mut ViewContext) -> Option<(Range, String)> { + let rename = self.pending_rename.take()?; + let new_name = self + .buffer + .read(cx) + .read(cx) + .text_for_range(rename.range.clone()) + .collect::(); - fn take_rename_range(&mut self, cx: &mut ViewContext) -> Option> { - self.clear_highlighted_ranges::(cx) - .and_then(|(_, mut ranges)| ranges.pop()) + self.clear_highlighted_ranges::(cx); + if let Some(transaction_id) = rename.first_transaction { + self.buffer.update(cx, |buffer, cx| { + buffer.undo_to_transaction(transaction_id, false, cx) + }); + } + + Some((rename.range, new_name)) } fn invalidate_rename_range( @@ -4162,15 +4187,16 @@ impl Editor { buffer: &MultiBufferSnapshot, cx: &mut ViewContext, ) { - if let Some(range) = &self.rename_range() { + if let Some(rename) = self.pending_rename.as_ref() { if self.selections.len() == 1 { - let head = self.selections[0].head().to_offset(&buffer); - if range.start.to_offset(&buffer) <= head && range.end.to_offset(&buffer) >= head { + let head = self.selections[0].head().to_offset(buffer); + let range = rename.range.to_offset(buffer).to_inclusive(); + if range.contains(&head) { return; } } - eprintln!("clearing highlight range"); - self.clear_highlighted_ranges::(cx); + + self.take_rename(cx); } } @@ -4659,6 +4685,12 @@ impl Editor { .buffer .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) { + if let Some(rename) = self.pending_rename.as_mut() { + if rename.first_transaction.is_none() { + rename.first_transaction = Some(tx_id); + } + } + if let Some((_, end_selections)) = self.selection_history.get_mut(&tx_id) { *end_selections = Some(self.selections.clone()); } else { @@ -5197,7 +5229,7 @@ impl View for Editor { EditorMode::Full => "full", }; cx.map.insert("mode".into(), mode.into()); - if self.rename_range().is_some() { + if self.pending_rename.is_some() { cx.set.insert("renaming".into()); } match self.context_menu.as_ref() { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 8180acacc5..a32e3b2307 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -27,6 +27,7 @@ use text::{ AnchorRangeExt as _, Edit, Point, PointUtf16, TextSummary, }; use theme::SyntaxTheme; +use util::CowMut; const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize]; @@ -58,6 +59,7 @@ pub enum CharKind { Word, } +#[derive(Clone)] struct Transaction { id: TransactionId, buffer_transactions: HashMap, @@ -564,7 +566,7 @@ impl MultiBuffer { if let Some(entry) = buffer.peek_undo_stack() { *buffer_transaction_id = entry.transaction_id(); } - buffer.undo_to_transaction(undo_to, cx) + buffer.undo_to_transaction(undo_to, true, cx) }); } } @@ -577,6 +579,35 @@ impl MultiBuffer { None } + pub fn undo_to_transaction( + &mut self, + transaction_id: TransactionId, + push_redo: bool, + cx: &mut ModelContext, + ) -> bool { + if let Some(buffer) = self.as_singleton() { + return buffer.update(cx, |buffer, cx| { + buffer.undo_to_transaction(transaction_id, push_redo, cx) + }); + } + + let mut undone = false; + for transaction in &mut *self.history.remove_from_undo(transaction_id, push_redo) { + for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions { + if let Some(BufferState { buffer, .. }) = self.buffers.borrow().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, true, cx) + }); + } + } + } + undone + } + pub fn redo(&mut self, cx: &mut ModelContext) -> Option { if let Some(buffer) = self.as_singleton() { return buffer.update(cx, |buffer, cx| buffer.redo(cx)); @@ -591,7 +622,7 @@ impl MultiBuffer { if let Some(entry) = buffer.peek_redo_stack() { *buffer_transaction_id = entry.transaction_id(); } - buffer.redo_to_transaction(redo_to, cx) + buffer.redo_to_transaction(redo_to, true, cx) }); } } @@ -2314,6 +2345,31 @@ impl History { } } + fn remove_from_undo( + &mut self, + transaction_id: TransactionId, + push_redo: bool, + ) -> CowMut<[Transaction]> { + assert_eq!(self.transaction_depth, 0); + + if let Some(entry_ix) = self + .undo_stack + .iter() + .rposition(|transaction| transaction.id == transaction_id) + { + let transactions = self.undo_stack.drain(entry_ix..).rev(); + if push_redo { + let redo_stack_start_len = self.redo_stack.len(); + self.redo_stack.extend(transactions); + CowMut::Borrowed(&mut self.redo_stack[redo_stack_start_len..]) + } else { + CowMut::Owned(transactions.collect()) + } + } else { + CowMut::Owned(Default::default()) + } + } + fn pop_redo(&mut self) -> Option<&mut Transaction> { assert_eq!(self.transaction_depth, 0); if let Some(transaction) = self.redo_stack.pop() { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index b4543b02b0..b8ec2f9884 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1738,12 +1738,13 @@ impl Buffer { pub fn undo_to_transaction( &mut self, transaction_id: TransactionId, + push_redo: bool, cx: &mut ModelContext, ) -> bool { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); - let operations = self.text.undo_to_transaction(transaction_id); + let operations = self.text.undo_to_transaction(transaction_id, push_redo); let undone = !operations.is_empty(); for operation in operations { self.send_operation(Operation::Buffer(operation), cx); @@ -1770,12 +1771,13 @@ impl Buffer { pub fn redo_to_transaction( &mut self, transaction_id: TransactionId, + push_undo: bool, cx: &mut ModelContext, ) -> bool { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); - let operations = self.text.redo_to_transaction(transaction_id); + let operations = self.text.redo_to_transaction(transaction_id, push_undo); let redone = !operations.is_empty(); for operation in operations { self.send_operation(Operation::Buffer(operation), cx); diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 9e2f8643b3..0c5c084060 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -43,6 +43,7 @@ pub(crate) struct PerformRename { pub buffer: ModelHandle, pub position: PointUtf16, pub new_name: String, + pub push_to_history: bool, } impl LspCommand for PrepareRename { @@ -60,11 +61,14 @@ impl LspCommand for PrepareRename { } fn to_proto(&self, project_id: u64, cx: &AppContext) -> proto::PrepareRename { - let buffer_id = self.buffer.read(cx).remote_id(); + let buffer = &self.buffer.read(cx); + let buffer_id = buffer.remote_id(); proto::PrepareRename { project_id, buffer_id, - position: None, + position: Some(language::proto::serialize_anchor( + &buffer.anchor_before(self.position), + )), } } @@ -93,10 +97,15 @@ impl LspCommand for PrepareRename { self, message: proto::PrepareRenameResponse, _: ModelHandle, - _: AsyncAppContext, + mut cx: AsyncAppContext, ) -> LocalBoxFuture<'static, Result>>> { async move { if message.can_rename { + self.buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(message.version.into()) + }) + .await; let start = message.start.and_then(deserialize_anchor); let end = message.end.and_then(deserialize_anchor); Ok(start.zip(end).map(|(start, end)| start..end)) @@ -127,11 +136,14 @@ impl LspCommand for PerformRename { } fn to_proto(&self, project_id: u64, cx: &AppContext) -> proto::PerformRename { - let buffer_id = self.buffer.read(cx).remote_id(); + let buffer = &self.buffer.read(cx); + let buffer_id = buffer.remote_id(); proto::PerformRename { project_id, buffer_id, - position: None, + position: Some(language::proto::serialize_anchor( + &buffer.anchor_before(self.position), + )), new_name: self.new_name.clone(), } } @@ -158,7 +170,7 @@ impl LspCommand for PerformRename { Project::deserialize_workspace_edit( project, edit, - false, + self.push_to_history, language_name, language_server, &mut cx, @@ -183,7 +195,7 @@ impl LspCommand for PerformRename { .ok_or_else(|| anyhow!("missing transaction"))?; project .update(&mut cx, |project, cx| { - project.deserialize_project_transaction(message, false, cx) + project.deserialize_project_transaction(message, self.push_to_history, cx) }) .await } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 71ec078f4e..a8bee2ff43 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -184,6 +184,8 @@ impl Project { client.add_entity_request_handler(Self::handle_get_code_actions); client.add_entity_request_handler(Self::handle_get_completions); client.add_entity_request_handler(Self::handle_get_definition); + client.add_entity_request_handler(Self::handle_prepare_rename); + client.add_entity_request_handler(Self::handle_perform_rename); client.add_entity_request_handler(Self::handle_open_buffer); client.add_entity_request_handler(Self::handle_save_buffer); } @@ -1835,6 +1837,7 @@ impl Project { buffer: ModelHandle, position: T, new_name: String, + push_to_history: bool, cx: &mut ModelContext, ) -> Task> { let position = position.to_point_utf16(buffer.read(cx)); @@ -1844,6 +1847,7 @@ impl Project { buffer, position, new_name, + push_to_history, }, cx, ) @@ -2615,6 +2619,87 @@ impl Project { }) } + async fn handle_prepare_rename( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + let sender_id = envelope.original_sender_id()?; + let position = envelope + .payload + .position + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("invalid position"))?; + let (prepare_rename, version) = this.update(&mut cx, |this, cx| { + let buffer_handle = this + .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 buffer = buffer_handle.read(cx); + let version = buffer.version(); + if buffer.can_resolve(&position) { + Ok((this.prepare_rename(buffer_handle, position, cx), version)) + } else { + Err(anyhow!("cannot resolve position")) + } + })?; + + let range = prepare_rename.await?; + Ok(proto::PrepareRenameResponse { + can_rename: range.is_some(), + start: range + .as_ref() + .map(|range| language::proto::serialize_anchor(&range.start)), + end: range + .as_ref() + .map(|range| language::proto::serialize_anchor(&range.end)), + version: (&version).into(), + }) + } + + async fn handle_perform_rename( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + let sender_id = envelope.original_sender_id()?; + let position = envelope + .payload + .position + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("invalid position"))?; + let perform_rename = this.update(&mut cx, |this, cx| { + let buffer_handle = this + .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 buffer = buffer_handle.read(cx); + if buffer.can_resolve(&position) { + Ok(this.perform_rename( + buffer_handle, + position, + envelope.payload.new_name, + false, + cx, + )) + } else { + Err(anyhow!("cannot resolve position")) + } + })?; + + let transaction = perform_rename.await?; + let transaction = this.update(&mut cx, |this, cx| { + this.serialize_project_transaction_for_peer(transaction, sender_id, cx) + }); + Ok(proto::PerformRenameResponse { + transaction: Some(transaction), + }) + } + async fn handle_open_buffer( this: ModelHandle, envelope: TypedEnvelope, diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index b5f1d49c06..fa8b2f692c 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -239,6 +239,7 @@ entity_messages!( JoinProject, LeaveProject, OpenBuffer, + PerformRename, PrepareRename, RemoveProjectCollaborator, SaveBuffer, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 93b54cf88c..9c0a1e2ec7 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -91,6 +91,8 @@ impl Server { .add_request_handler(Server::apply_additional_edits_for_completion) .add_request_handler(Server::get_code_actions) .add_request_handler(Server::apply_code_action) + .add_request_handler(Server::prepare_rename) + .add_request_handler(Server::perform_rename) .add_request_handler(Server::get_channels) .add_request_handler(Server::get_users) .add_request_handler(Server::join_channel) @@ -708,6 +710,34 @@ impl Server { .await?) } + async fn prepare_rename( + self: Arc, + request: TypedEnvelope, + ) -> tide::Result { + let host = self + .state() + .read_project(request.payload.project_id, request.sender_id)? + .host_connection_id; + Ok(self + .peer + .forward_request(request.sender_id, host, request.payload.clone()) + .await?) + } + + async fn perform_rename( + self: Arc, + request: TypedEnvelope, + ) -> tide::Result { + let host = self + .state() + .read_project(request.payload.project_id, request.sender_id)? + .host_connection_id; + Ok(self + .peer + .forward_request(request.sender_id, host, request.payload.clone()) + .await?) + } + async fn update_buffer( self: Arc, request: TypedEnvelope, diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index da003b5d44..d041518e71 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -285,19 +285,31 @@ impl History { } } - fn remove_from_undo(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] { + fn remove_from_undo( + &mut self, + transaction_id: TransactionId, + push_redo: bool, + ) -> Vec { assert_eq!(self.transaction_depth, 0); - let redo_stack_start_len = self.redo_stack.len(); + let mut transactions = Vec::new(); if let Some(entry_ix) = self .undo_stack .iter() .rposition(|entry| entry.transaction.id == transaction_id) { - self.redo_stack - .extend(self.undo_stack.drain(entry_ix..).rev()); + transactions.extend( + self.undo_stack[entry_ix..] + .iter() + .rev() + .map(|entry| entry.transaction.clone()), + ); + let transactions = self.undo_stack.drain(entry_ix..).rev(); + if push_redo { + self.redo_stack.extend(transactions); + } } - &self.redo_stack[redo_stack_start_len..] + transactions } fn forget(&mut self, transaction_id: TransactionId) { @@ -327,19 +339,31 @@ impl History { } } - fn remove_from_redo(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] { + fn remove_from_redo( + &mut self, + transaction_id: TransactionId, + push_undo: bool, + ) -> Vec { assert_eq!(self.transaction_depth, 0); - let undo_stack_start_len = self.undo_stack.len(); + let mut transactions = Vec::new(); if let Some(entry_ix) = self .redo_stack .iter() .rposition(|entry| entry.transaction.id == transaction_id) { - self.undo_stack - .extend(self.redo_stack.drain(entry_ix..).rev()); + transactions.extend( + self.redo_stack[entry_ix..] + .iter() + .rev() + .map(|entry| entry.transaction.clone()), + ); + if push_undo { + self.undo_stack + .extend(self.redo_stack.drain(entry_ix..).rev()); + } } - &self.undo_stack[undo_stack_start_len..] + transactions } } @@ -1215,14 +1239,12 @@ impl Buffer { } } - pub fn undo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec { - let transactions = self - .history - .remove_from_undo(transaction_id) - .iter() - .map(|entry| entry.transaction.clone()) - .collect::>(); - + pub fn undo_to_transaction( + &mut self, + transaction_id: TransactionId, + push_redo: bool, + ) -> Vec { + let transactions = self.history.remove_from_undo(transaction_id, push_redo); transactions .into_iter() .map(|transaction| self.undo_or_redo(transaction).unwrap()) @@ -1244,14 +1266,12 @@ impl Buffer { } } - pub fn redo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec { - let transactions = self - .history - .remove_from_redo(transaction_id) - .iter() - .map(|entry| entry.transaction.clone()) - .collect::>(); - + pub fn redo_to_transaction( + &mut self, + transaction_id: TransactionId, + push_undo: bool, + ) -> Vec { + let transactions = self.history.remove_from_redo(transaction_id, push_undo); transactions .into_iter() .map(|transaction| self.undo_or_redo(transaction).unwrap()) diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index 919fecf8f9..26edf7f5a7 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -3,8 +3,9 @@ pub mod test; use futures::Future; use std::{ + borrow::{Borrow, BorrowMut}, cmp::Ordering, - ops::AddAssign, + ops::{AddAssign, Deref, DerefMut}, pin::Pin, task::{Context, Poll}, }; @@ -123,6 +124,38 @@ where } } +pub enum CowMut<'a, T: ?Sized + ToOwned> { + Borrowed(&'a mut T), + Owned(T::Owned), +} + +impl<'a, T> Deref for CowMut<'a, T> +where + T: ?Sized + ToOwned, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + CowMut::Borrowed(value) => value, + CowMut::Owned(value) => value.borrow(), + } + } +} + +impl<'a, T> DerefMut for CowMut<'a, T> +where + T: ?Sized + ToOwned, + T::Owned: BorrowMut, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + CowMut::Borrowed(value) => value, + CowMut::Owned(value) => value.borrow_mut(), + } + } +} + #[cfg(test)] mod tests { use super::*;