From f1ce4337af9549d70e427cc22f06f8722ac7357e Mon Sep 17 00:00:00 2001 From: David Danialy Date: Wed, 16 Sep 2026 14:27:29 -0700 Subject: [PATCH] fix(tui): let command-backspace delete preceding newlines --- src/tui/app.rs | 16 ++++++++++++++++ src/tui/editor.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 9fb10bcb..e3528505 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -6375,6 +6375,22 @@ mod tests { assert!(app.attachments.is_empty()); } + #[test] + fn line_start_deletion_keys_continue_through_newlines() { + for key in [ + modified_press(KeyCode::Backspace, KeyModifiers::SUPER), + modified_press(KeyCode::Char('u'), KeyModifiers::CONTROL), + ] { + let mut app = app(); + app.paste("first\nsecond"); + for expected in ["first\n", "first", "", ""] { + app.handle_key(key); + assert_eq!(app.editor.text(), expected); + assert_eq!(app.editor.cursor(), expected.len()); + } + } + } + #[test] fn attachment_placeholder_includes_a_separator_before_following_text() { let mut app = app(); diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 74bd86ce..bc36f6b5 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -236,9 +236,14 @@ impl Editor { self.text.replace_range(self.cursor..target, ""); } - /// `ctrl+u` / `cmd+backspace`: erase from the cursor to the line start. + /// `ctrl+u` / `cmd+backspace`: erase from the cursor to the line start, + /// or erase the preceding newline when already at the line start. pub fn delete_to_line_start(&mut self) { let (start, _) = self.line_bounds(self.cursor); + if start == self.cursor { + self.backspace(); + return; + } self.text.replace_range(start..self.cursor, ""); self.cursor = start; } @@ -447,6 +452,28 @@ mod tests { assert_eq!(editor.text(), "first\n"); } + #[test] + fn deleting_to_line_start_can_continue_through_previous_lines() { + let mut editor = editor("café\n\nsecond"); + for expected in ["café\n\n", "café\n", "café", "", ""] { + editor.delete_to_line_start(); + assert_eq!(editor.text(), expected); + assert_eq!(editor.cursor(), expected.len()); + } + } + + #[test] + fn deleting_at_line_start_preserves_text_after_the_cursor() { + let mut editor = editor("café\nsecond"); + editor.move_line_start(); + editor.delete_to_line_start(); + assert_eq!(editor.text(), "cafésecond"); + assert_eq!(editor.cursor(), "café".len()); + editor.delete_to_line_start(); + assert_eq!(editor.text(), "second"); + assert_eq!(editor.cursor(), 0); + } + #[test] fn keeps_the_column_when_moving_between_lines() { let mut editor = editor("abcdef\nxy\nlonger");