Refine inline transformation UX (#12939)

https://github.com/zed-industries/zed/assets/482957/1790e32e-1f59-4831-8a4c-722cf441e7e9



Release Notes:

- N/A

---------

Co-authored-by: Richard <richard@zed.dev>
Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2024-06-13 08:35:22 +02:00 committed by GitHub
parent 9e3c5f3e12
commit e1f4dfc068
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 419 additions and 219 deletions

View file

@ -116,15 +116,16 @@ use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use smallvec::SmallVec;
use snippet::Snippet;
use std::ops::Not as _;
use std::{
any::TypeId,
borrow::Cow,
cell::RefCell,
cmp::{self, Ordering, Reverse},
mem,
num::NonZeroU32,
ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
path::Path,
rc::Rc,
sync::Arc,
time::{Duration, Instant},
};
@ -377,6 +378,19 @@ impl Default for EditorStyle {
type CompletionId = usize;
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
struct EditorActionId(usize);
impl EditorActionId {
pub fn post_inc(&mut self) -> Self {
let answer = self.0;
*self = Self(answer + 1);
Self(answer)
}
}
// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
@ -512,7 +526,8 @@ pub struct Editor {
gutter_dimensions: GutterDimensions,
pub vim_replace_map: HashMap<Range<usize>, String>,
style: Option<EditorStyle>,
editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
next_editor_action_id: EditorActionId,
editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
use_autoclose: bool,
auto_replace_emoji_shortcode: bool,
show_git_blame_gutter: bool,
@ -1805,7 +1820,8 @@ impl Editor {
style: None,
show_cursor_names: false,
hovered_cursors: Default::default(),
editor_actions: Default::default(),
next_editor_action_id: EditorActionId::default(),
editor_actions: Rc::default(),
vim_replace_map: Default::default(),
show_inline_completions: mode == EditorMode::Full,
custom_context_menu: None,
@ -6448,29 +6464,9 @@ impl Editor {
return;
}
if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() {
self.change_selections(None, cx, |s| {
s.select_anchors(selections.to_vec());
});
}
self.request_autoscroll(Autoscroll::fit(), cx);
self.unmark_text(cx);
self.refresh_inline_completion(true, cx);
cx.emit(EditorEvent::Edited);
cx.emit(EditorEvent::TransactionUndone {
transaction_id: tx_id,
});
}
}
pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
if self.read_only(cx) {
return;
}
if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned()
if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
if let Some((selections, _)) =
self.selection_history.transaction(transaction_id).cloned()
{
self.change_selections(None, cx, |s| {
s.select_anchors(selections.to_vec());
@ -6479,7 +6475,28 @@ impl Editor {
self.request_autoscroll(Autoscroll::fit(), cx);
self.unmark_text(cx);
self.refresh_inline_completion(true, cx);
cx.emit(EditorEvent::Edited);
cx.emit(EditorEvent::Edited { transaction_id });
cx.emit(EditorEvent::TransactionUndone { transaction_id });
}
}
pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
if self.read_only(cx) {
return;
}
if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
if let Some((_, Some(selections))) =
self.selection_history.transaction(transaction_id).cloned()
{
self.change_selections(None, cx, |s| {
s.select_anchors(selections.to_vec());
});
}
self.request_autoscroll(Autoscroll::fit(), cx);
self.unmark_text(cx);
self.refresh_inline_completion(true, cx);
cx.emit(EditorEvent::Edited { transaction_id });
}
}
@ -9590,18 +9607,20 @@ impl Editor {
now: Instant,
cx: &mut ViewContext<Self>,
) -> Option<TransactionId> {
if let Some(tx_id) = self
if let Some(transaction_id) = self
.buffer
.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
{
if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) {
if let Some((_, end_selections)) =
self.selection_history.transaction_mut(transaction_id)
{
*end_selections = Some(self.selections.disjoint_anchors());
} else {
log::error!("unexpectedly ended a transaction that wasn't started by this editor");
}
cx.emit(EditorEvent::Edited);
Some(tx_id)
cx.emit(EditorEvent::Edited { transaction_id });
Some(transaction_id)
} else {
None
}
@ -11293,21 +11312,28 @@ impl Editor {
pub fn register_action<A: Action>(
&mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
) -> &mut Self {
) -> Subscription {
let id = self.next_editor_action_id.post_inc();
let listener = Arc::new(listener);
self.editor_actions.borrow_mut().insert(
id,
Box::new(move |cx| {
let _view = cx.view().clone();
let cx = cx.window_context();
let listener = listener.clone();
cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
listener(action, cx)
}
})
}),
);
self.editor_actions.push(Box::new(move |cx| {
let _view = cx.view().clone();
let cx = cx.window_context();
let listener = listener.clone();
cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
listener(action, cx)
}
})
}));
self
let editor_actions = self.editor_actions.clone();
Subscription::new(move || {
editor_actions.borrow_mut().remove(&id);
})
}
pub fn file_header_size(&self) -> u8 {
@ -11764,7 +11790,9 @@ pub enum EditorEvent {
ids: Vec<ExcerptId>,
},
BufferEdited,
Edited,
Edited {
transaction_id: clock::Lamport,
},
Reparsed,
Focused,
Blurred,

View file

@ -57,10 +57,10 @@ fn test_edit_events(cx: &mut TestAppContext) {
let events = events.clone();
|cx| {
let view = cx.view().clone();
cx.subscribe(&view, move |_, _, event: &EditorEvent, _| {
if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
events.borrow_mut().push(("editor1", event.clone()));
}
cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
_ => {}
})
.detach();
Editor::for_buffer(buffer.clone(), None, cx)
@ -70,11 +70,16 @@ fn test_edit_events(cx: &mut TestAppContext) {
let editor2 = cx.add_window({
let events = events.clone();
|cx| {
cx.subscribe(&cx.view().clone(), move |_, _, event: &EditorEvent, _| {
if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
events.borrow_mut().push(("editor2", event.clone()));
}
})
cx.subscribe(
&cx.view().clone(),
move |_, _, event: &EditorEvent, _| match event {
EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
EditorEvent::BufferEdited => {
events.borrow_mut().push(("editor2", "buffer edited"))
}
_ => {}
},
)
.detach();
Editor::for_buffer(buffer.clone(), None, cx)
}
@ -87,9 +92,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
("editor1", EditorEvent::Edited),
("editor1", EditorEvent::BufferEdited),
("editor2", EditorEvent::BufferEdited),
("editor1", "edited"),
("editor1", "buffer edited"),
("editor2", "buffer edited"),
]
);
@ -98,9 +103,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
("editor2", EditorEvent::Edited),
("editor1", EditorEvent::BufferEdited),
("editor2", EditorEvent::BufferEdited),
("editor2", "edited"),
("editor1", "buffer edited"),
("editor2", "buffer edited"),
]
);
@ -109,9 +114,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
("editor1", EditorEvent::Edited),
("editor1", EditorEvent::BufferEdited),
("editor2", EditorEvent::BufferEdited),
("editor1", "edited"),
("editor1", "buffer edited"),
("editor2", "buffer edited"),
]
);
@ -120,9 +125,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
("editor1", EditorEvent::Edited),
("editor1", EditorEvent::BufferEdited),
("editor2", EditorEvent::BufferEdited),
("editor1", "edited"),
("editor1", "buffer edited"),
("editor2", "buffer edited"),
]
);
@ -131,9 +136,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
("editor2", EditorEvent::Edited),
("editor1", EditorEvent::BufferEdited),
("editor2", EditorEvent::BufferEdited),
("editor2", "edited"),
("editor1", "buffer edited"),
("editor2", "buffer edited"),
]
);
@ -142,9 +147,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
("editor2", EditorEvent::Edited),
("editor1", EditorEvent::BufferEdited),
("editor2", EditorEvent::BufferEdited),
("editor2", "edited"),
("editor1", "buffer edited"),
("editor2", "buffer edited"),
]
);

View file

@ -153,7 +153,7 @@ impl EditorElement {
fn register_actions(&self, cx: &mut WindowContext) {
let view = &self.editor;
view.update(cx, |editor, cx| {
for action in editor.editor_actions.iter() {
for action in editor.editor_actions.borrow().values() {
(action)(cx)
}
});

View file

@ -615,32 +615,36 @@ fn editor_with_deleted_text(
]);
let original_multi_buffer_range = hunk.multi_buffer_range.clone();
let diff_base_range = hunk.diff_base_byte_range.clone();
editor.register_action::<RevertSelectedHunks>(move |_, cx| {
parent_editor
.update(cx, |editor, cx| {
let Some((buffer, original_text)) = editor.buffer().update(cx, |buffer, cx| {
let (_, buffer, _) =
buffer.excerpt_containing(original_multi_buffer_range.start, cx)?;
let original_text =
buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
Some((buffer, Arc::from(original_text.to_string())))
}) else {
return;
};
buffer.update(cx, |buffer, cx| {
buffer.edit(
Some((
original_multi_buffer_range.start.text_anchor
..original_multi_buffer_range.end.text_anchor,
original_text,
)),
None,
cx,
)
});
})
.ok();
});
editor
.register_action::<RevertSelectedHunks>(move |_, cx| {
parent_editor
.update(cx, |editor, cx| {
let Some((buffer, original_text)) =
editor.buffer().update(cx, |buffer, cx| {
let (_, buffer, _) = buffer
.excerpt_containing(original_multi_buffer_range.start, cx)?;
let original_text =
buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
Some((buffer, Arc::from(original_text.to_string())))
})
else {
return;
};
buffer.update(cx, |buffer, cx| {
buffer.edit(
Some((
original_multi_buffer_range.start.text_anchor
..original_multi_buffer_range.end.text_anchor,
original_text,
)),
None,
cx,
)
});
})
.ok();
})
.detach();
editor
});

View file

@ -234,7 +234,7 @@ impl FollowableItem for Editor {
fn to_follow_event(event: &EditorEvent) -> Option<workspace::item::FollowEvent> {
match event {
EditorEvent::Edited => Some(FollowEvent::Unfollow),
EditorEvent::Edited { .. } => Some(FollowEvent::Unfollow),
EditorEvent::SelectionsChanged { local }
| EditorEvent::ScrollPositionChanged { local, .. } => {
if *local {