diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 475f6efb7b..3d0fa3a842 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -226,7 +226,6 @@ class TextArea(ScrollView): Binding( "ctrl+f", "delete_word_right", "Delete right to start of word", show=False ), - Binding("ctrl+shift+x", "delete_line", "Delete line", show=False), Binding("ctrl+x", "cut", "Cut", show=False), Binding("ctrl+c", "copy", "Copy", show=False), Binding("ctrl+v", "paste", "Paste", show=False), @@ -2185,22 +2184,22 @@ def action_delete_right(self) -> None: def action_delete_line(self) -> None: """Deletes the lines which intersect with the selection.""" + self._delete_cursor_line() + + def _delete_cursor_line(self) -> EditResult | None: + """Deletes the line (including the line terminator) that the cursor is on.""" start, end = self.selection start, end = sorted((start, end)) start_row, _start_column = start end_row, end_column = end - # Generally editors will only delete line the end line of the - # selection if the cursor is not at column 0 of that line. - if start_row != end_row and end_column == 0 and end_row >= 0: - end_row -= 1 - from_location = (start_row, 0) to_location = (end_row + 1, 0) deletion = self._delete_via_keyboard(from_location, to_location) if deletion is not None: self.move_cursor_relative(columns=end_column, record_width=False) + return deletion def action_cut(self) -> None: """Cut text (remove and copy to clipboard).""" @@ -2208,10 +2207,12 @@ def action_cut(self) -> None: return start, end = self.selection if start == end: - return - copy_text = self.get_text_range(start, end) - self.app.copy_to_clipboard(copy_text) - self._delete_via_keyboard(start, end) + edit_result = self._delete_cursor_line() + else: + edit_result = self._delete_via_keyboard(start, end) + + if edit_result is not None: + self.app.copy_to_clipboard(edit_result.replaced_text) def action_copy(self) -> None: """Copy selection to clipboard.""" diff --git a/tests/text_area/test_edit_via_bindings.py b/tests/text_area/test_edit_via_bindings.py index 4724b74ea8..ff5871b7fd 100644 --- a/tests/text_area/test_edit_via_bindings.py +++ b/tests/text_area/test_edit_via_bindings.py @@ -168,26 +168,27 @@ async def test_delete_right_end_of_line(): @pytest.mark.parametrize( - "selection,expected_result", + "selection,expected_result,expected_clipboard,cursor_end_location", [ - (Selection.cursor((0, 0)), ""), - (Selection.cursor((0, 4)), ""), - (Selection.cursor((0, 10)), ""), - (Selection((0, 2), (0, 4)), ""), - (Selection((0, 4), (0, 2)), ""), + (Selection.cursor((0, 0)), "", "0123456789", (0, 0)), + (Selection.cursor((0, 4)), "", "0123456789", (0, 0)), + (Selection.cursor((0, 10)), "", "0123456789", (0, 0)), + (Selection((0, 2), (0, 4)), "01456789", "23", (0, 2)), + (Selection((0, 4), (0, 2)), "01456789", "23", (0, 2)), ], ) -async def test_delete_line(selection, expected_result): +async def test_cut(selection, expected_result, expected_clipboard, cursor_end_location): app = TextAreaApp() async with app.run_test() as pilot: text_area = app.query_one(TextArea) text_area.load_text("0123456789") text_area.selection = selection - await pilot.press("ctrl+shift+x") + await pilot.press("ctrl+x") - assert text_area.selection == Selection.cursor((0, 0)) + assert text_area.selection == Selection.cursor(cursor_end_location) assert text_area.text == expected_result + assert app.clipboard == expected_clipboard @pytest.mark.parametrize( @@ -199,17 +200,8 @@ async def test_delete_line(selection, expected_result): (Selection.cursor((3, 1)), "012\n345\n678\n"), (Selection.cursor((4, 0)), "012\n345\n678\n9\n"), # Selections - (Selection((1, 1), (1, 2)), "012\n678\n9\n"), # non-empty single line selection - (Selection((1, 2), (2, 1)), "012\n9\n"), # delete lines selection touches - ( - Selection((1, 2), (3, 0)), - "012\n9\n", - ), # cursor at column 0 of line 3, should not be deleted! - ( - Selection((3, 0), (1, 2)), - "012\n9\n", - ), # opposite direction - (Selection((0, 0), (4, 0)), ""), # delete all lines + (Selection((1, 1), (1, 2)), "012\n35\n678\n9\n"), + (Selection((1, 2), (2, 1)), "012\n3478\n9\n"), ], ) async def test_delete_line_multiline_document(selection, expected_result): @@ -219,7 +211,7 @@ async def test_delete_line_multiline_document(selection, expected_result): text_area.load_text("012\n345\n678\n9\n") text_area.selection = selection - await pilot.press("ctrl+shift+x") + await pilot.press("ctrl+x") cursor_row, cursor_column = text_area.cursor_location assert text_area.selection == Selection.cursor((cursor_row, cursor_column))