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:
parent
9e3c5f3e12
commit
e1f4dfc068
20 changed files with 419 additions and 219 deletions
|
@ -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,
|
||||
|
|
|
@ -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"),
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue