diff --git a/CHANGELOG.md b/CHANGELOG.md index 76279be25a..b82e5c51fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fixed duplicated key displays in the help panel https://github.com/Textualize/textual/issues/5037 +- Fixed `TextArea` mouse selection with tab characters https://github.com/Textualize/textual/issues/5212 ### Added diff --git a/src/textual/document/_wrapped_document.py b/src/textual/document/_wrapped_document.py index cef7a57dda..68846bedab 100644 --- a/src/textual/document/_wrapped_document.py +++ b/src/textual/document/_wrapped_document.py @@ -295,7 +295,9 @@ def offset_to_location(self, offset: Offset) -> Location: if not self._width: # No wrapping, so we directly map offset to location and clamp. line_index = min(y, len(self._wrap_offsets) - 1) - column_index = min(x, len(self.document.get_line(line_index))) + column_index = cell_width_to_column_index( + self.document.get_line(line_index), x, self._tab_width + ) return line_index, column_index # Find the line corresponding to the given y offset in the wrapped document. diff --git a/tests/text_area/test_selection.py b/tests/text_area/test_selection.py index 8b5424d2fb..0efc9ca62d 100644 --- a/tests/text_area/test_selection.py +++ b/tests/text_area/test_selection.py @@ -334,3 +334,22 @@ def compose(self) -> ComposeResult: assert text_area.cursor_screen_offset == (5, 1) assert app.cursor_position == (5, 1) + + +async def test_mouse_selection_with_tab_characters(): + """Regression test for https://github.com/Textualize/textual/issues/5212""" + + class TextAreaTabsApp(App): + def compose(self) -> ComposeResult: + yield TextArea(soft_wrap=False, text="\t\t") + + app = TextAreaTabsApp() + async with app.run_test() as pilot: + text_area = pilot.app.query_one(TextArea) + expected_selection = Selection((0, 0), (0, 0)) + assert text_area.selection == expected_selection + + await pilot.mouse_down(text_area, offset=(2, 1)) + await pilot.hover(text_area, offset=(3, 1)) + + assert text_area.selection == expected_selection