diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd4d4212c..5200430ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [6.2.1] - 2025-10-01 + +- Fix inability to copy text outside of an input/textarea when it was focused https://github.com/Textualize/textual/pull/6148 +- Fix issue when copying text after a double click https://github.com/Textualize/textual/pull/6148 + ## [6.2.0] - 2025-09-30 ### Changed @@ -3129,6 +3134,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040 - New handler system for messages that doesn't require inheritance - Improved traceback handling +[6.2.1]: https://github.com/Textualize/textual/compare/v6.2.0...v6.2.1 [6.2.0]: https://github.com/Textualize/textual/compare/v6.1.0...v6.2.0 [6.1.0]: https://github.com/Textualize/textual/compare/v6.0.0...v6.1.0 [6.0.0]: https://github.com/Textualize/textual/compare/v5.3.0...v6.0.0 diff --git a/pyproject.toml b/pyproject.toml index 593082ea36..34b95711e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual" -version = "6.2.0" +version = "6.2.1" homepage = "https://github.com/Textualize/textual" repository = "https://github.com/Textualize/textual" documentation = "https://textual.textualize.io/" diff --git a/src/textual/screen.py b/src/textual/screen.py index f3633d4711..d96dcbd909 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -924,7 +924,7 @@ def get_selected_text(self) -> str | None: if selected_text_in_widget is not None: widget_text.extend(selected_text_in_widget) - selected_text = "".join(widget_text) + selected_text = "".join(widget_text).rstrip("\n") return selected_text def action_copy_text(self) -> None: diff --git a/src/textual/selection.py b/src/textual/selection.py index 0b12fc27ba..464d218f85 100644 --- a/src/textual/selection.py +++ b/src/textual/selection.py @@ -46,8 +46,8 @@ def extract(self, text: str) -> str: start_line, start_offset = self.start.transpose if self.end is None: - end_line = len(lines) - 1 - end_offset = len(lines[end_line]) + end_line = len(lines) + end_offset = len(lines[-1]) else: end_line, end_offset = self.end.transpose end_line = min(len(lines), end_line) diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index c56234f9c1..d1ef93d05a 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -11,6 +11,7 @@ from typing_extensions import Literal from textual import events +from textual.actions import SkipAction from textual.expand_tabs import expand_tabs_inline from textual.screen import Screen from textual.scroll_view import ScrollView @@ -1106,7 +1107,11 @@ def action_cut(self) -> None: def action_copy(self) -> None: """Copy the current selection to the clipboard.""" - self.app.copy_to_clipboard(self.selected_text) + selected_text = self.selected_text + if selected_text: + self.app.copy_to_clipboard(selected_text) + else: + raise SkipAction() def action_paste(self) -> None: """Paste from the local clipboard.""" diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index 82856067bb..34946c399f 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -16,6 +16,7 @@ from textual._text_area_theme import TextAreaTheme from textual._tree_sitter import TREE_SITTER, get_language +from textual.actions import SkipAction from textual.cache import LRUCache from textual.color import Color from textual.content import Content @@ -2513,6 +2514,8 @@ def action_copy(self) -> None: selected_text = self.selected_text if selected_text: self.app.copy_to_clipboard(selected_text) + else: + raise SkipAction() def action_paste(self) -> None: """Paste from local clipboard.""" diff --git a/tests/test_selection.py b/tests/test_selection.py new file mode 100644 index 0000000000..13aaa5d33b --- /dev/null +++ b/tests/test_selection.py @@ -0,0 +1,22 @@ +import pytest + +from textual.geometry import Offset +from textual.selection import Selection + + +@pytest.mark.parametrize( + "text,selection,expected", + [ + ("Hello", Selection(None, None), "Hello"), + ("Hello\nWorld", Selection(None, None), "Hello\nWorld"), + ("Hello\nWorld", Selection(Offset(0, 1), None), "World"), + ("Hello\nWorld", Selection(None, Offset(5, 0)), "Hello"), + ("Foo", Selection(Offset(0, 0), Offset(1, 0)), "F"), + ("Foo", Selection(Offset(1, 0), Offset(2, 0)), "o"), + ("Foo", Selection(Offset(0, 0), Offset(2, 0)), "Fo"), + ("Foo", Selection(Offset(0, 0), None), "Foo"), + ], +) +def test_extract(text: str, selection: Selection, expected: str) -> None: + """Test Selection.extract""" + assert selection.extract(text) == expected