editor: Restore selections to positions after last edit (#28527)
Closes #22692 Makes it so when undoing a format operation, the selections are set to where they were at the last edit. Release Notes: - Made it so the cursor position is reset to where it was after the last edit when undoing a format operation. This will only result in different behavior when you make an edit, scroll away, initiate formatting (either by saving or manually) and then undo the format. --------- Co-authored-by: Zed AI <ai@zed.dev>
This commit is contained in:
parent
26f4705198
commit
fbbc23bec3
3 changed files with 117 additions and 4 deletions
|
@ -14175,12 +14175,28 @@ impl Editor {
|
|||
}
|
||||
};
|
||||
|
||||
let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
|
||||
let selections_prev = transaction_id_prev
|
||||
.and_then(|transaction_id_prev| {
|
||||
// default to selections as they were after the last edit, if we have them,
|
||||
// instead of how they are now.
|
||||
// This will make it so that editing, moving somewhere else, formatting, then undoing the format
|
||||
// will take you back to where you made the last edit, instead of staying where you scrolled
|
||||
self.selection_history
|
||||
.transaction(transaction_id_prev)
|
||||
.map(|t| t.0.clone())
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
|
||||
self.selections.disjoint_anchors()
|
||||
});
|
||||
|
||||
let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
|
||||
let format = project.update(cx, |project, cx| {
|
||||
project.format(buffers, target, true, trigger, cx)
|
||||
});
|
||||
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
let transaction = futures::select_biased! {
|
||||
transaction = format.log_err().fuse() => transaction,
|
||||
() = timeout => {
|
||||
|
@ -14200,6 +14216,19 @@ impl Editor {
|
|||
})
|
||||
.ok();
|
||||
|
||||
if let Some(transaction_id_now) =
|
||||
buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
|
||||
{
|
||||
let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
|
||||
if has_new_transaction {
|
||||
_ = editor.update(cx, |editor, _| {
|
||||
editor
|
||||
.selection_history
|
||||
.insert_transaction(transaction_id_now, selections_prev);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5873,6 +5873,83 @@ async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
document_formatting_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
line 1
|
||||
line 2
|
||||
linˇe 3
|
||||
line 4
|
||||
line 5
|
||||
"});
|
||||
|
||||
// Make an edit
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.handle_input("X", window, cx);
|
||||
});
|
||||
|
||||
// Move cursor to a different position
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
|
||||
});
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
line 1
|
||||
line 2
|
||||
linXe 3
|
||||
line 4
|
||||
liˇne 5
|
||||
"});
|
||||
|
||||
cx.lsp
|
||||
.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
|
||||
Ok(Some(vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
|
||||
"PREFIX ".to_string(),
|
||||
)]))
|
||||
});
|
||||
|
||||
cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
PREFIX line 1
|
||||
line 2
|
||||
linXe 3
|
||||
line 4
|
||||
liˇne 5
|
||||
"});
|
||||
|
||||
// Undo formatting
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.undo(&Default::default(), window, cx);
|
||||
});
|
||||
|
||||
// Verify cursor moved back to position after edit
|
||||
cx.assert_editor_state(indoc! {"
|
||||
line 1
|
||||
line 2
|
||||
linXˇe 3
|
||||
line 4
|
||||
line 5
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
|
|
@ -1118,9 +1118,16 @@ impl MultiBuffer {
|
|||
self.history.start_transaction(now)
|
||||
}
|
||||
|
||||
pub fn last_transaction_id(&self) -> Option<TransactionId> {
|
||||
let last_transaction = self.history.undo_stack.last()?;
|
||||
return Some(last_transaction.id);
|
||||
pub fn last_transaction_id(&self, cx: &App) -> Option<TransactionId> {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
return buffer.read_with(cx, |b, _| {
|
||||
b.peek_undo_stack()
|
||||
.map(|history_entry| history_entry.transaction_id())
|
||||
});
|
||||
} else {
|
||||
let last_transaction = self.history.undo_stack.last()?;
|
||||
return Some(last_transaction.id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_transaction(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue