@@ -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> {