diff --git a/CHANGELOG.md b/CHANGELOG.md index a76f222901..a294e9a46f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Widget.release_mouse will now only release the mouse, if it was captured by self https://github.com/Textualize/textual/pull/5900 +## Added + +- Added `TextArea.highlight_cursor_line` toggle https://github.com/Textualize/textual/pull/5924 + ## [3.5.0] - 2025-06-20 ### Changed diff --git a/src/textual/widgets/_text_area.py b/src/textual/widgets/_text_area.py index f180c28f30..1e666699d2 100644 --- a/src/textual/widgets/_text_area.py +++ b/src/textual/widgets/_text_area.py @@ -374,6 +374,9 @@ class TextArea(ScrollView): compact: reactive[bool] = reactive(False, toggle_class="-textual-compact") """Enable compact display?""" + highlight_cursor_line: reactive[bool] = reactive(True) + """Highlight the line under the cursor?""" + _cursor_visible: Reactive[bool] = reactive(False, repaint=False, init=False) """Indicates where the cursor is in the blink cycle. If it's currently not visible due to blinking, this is False.""" @@ -427,6 +430,7 @@ def __init__( disabled: bool = False, tooltip: RenderableType | None = None, compact: bool = False, + highlight_cursor_line: bool = True, ) -> None: """Construct a new `TextArea`. @@ -446,6 +450,7 @@ def __init__( disabled: True if the widget is disabled. tooltip: Optional tooltip. compact: Enable compact style (without borders). + highlight_cursor_line: Highlight the line under the cursor. """ super().__init__(name=name, id=id, classes=classes, disabled=disabled) @@ -504,6 +509,7 @@ def __init__( self.set_reactive(TextArea.read_only, read_only) self.set_reactive(TextArea.show_line_numbers, show_line_numbers) self.set_reactive(TextArea.line_number_start, line_number_start) + self.set_reactive(TextArea.highlight_cursor_line, highlight_cursor_line) self._set_document(text, language) @@ -541,6 +547,7 @@ def code_editor( disabled: bool = False, tooltip: RenderableType | None = None, compact: bool = False, + highlight_cursor_line: bool = True, ) -> TextArea: """Construct a new `TextArea` with sensible defaults for editing code. @@ -561,6 +568,7 @@ def code_editor( disabled: True if the widget is disabled. tooltip: Optional tooltip compact: Enable compact style (without borders). + highlight_cursor_line: Highlight the line under the cursor. """ return cls( text, @@ -578,6 +586,7 @@ def code_editor( disabled=disabled, tooltip=tooltip, compact=compact, + highlight_cursor_line=highlight_cursor_line, ) @staticmethod @@ -1149,7 +1158,11 @@ def render_line(self, y: int) -> Strip: selection_bottom_row, selection_bottom_column = selection_bottom cursor_line_style = theme.cursor_line_style if theme else None - if cursor_line_style and cursor_row == line_index: + if ( + cursor_line_style + and cursor_row == line_index + and self.highlight_cursor_line + ): line.stylize(cursor_line_style) # Selection styling @@ -1241,7 +1254,7 @@ def render_line(self, y: int) -> Strip: # Build the gutter text for this line gutter_width = self.gutter_width if self.show_line_numbers: - if cursor_row == line_index: + if cursor_row == line_index and self.highlight_cursor_line: gutter_style = theme.cursor_line_gutter_style else: gutter_style = theme.gutter_style @@ -1306,7 +1319,7 @@ def render_line(self, y: int) -> Strip: text_strip = text_strip.crop(scroll_x, scroll_x + virtual_width) # Stylize the line the cursor is currently on. - if cursor_row == line_index: + if cursor_row == line_index and self.highlight_cursor_line: line_style = cursor_line_style else: line_style = theme.base_style if theme else None diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_textarea_line_highlight.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_textarea_line_highlight.svg new file mode 100644 index 0000000000..9a97470459 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_textarea_line_highlight.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextAreaLine + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +Hello                                                                        +World!                                                                       + + + + + + + + + + + + + + + + + + + + +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index fea3b0c5a4..603a8903a7 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -3540,7 +3540,6 @@ def compose(self) -> ComposeResult: assert snap_compare(FocusWithinTransparentApp(), press=["tab"]) - def test_setting_transparency(snap_compare): """Check that setting a widget's background color to transparent works as expected using python. @@ -3570,14 +3569,15 @@ def compose(self): yield TextArea("Baseline normal TextArea, not transparent") text_area2 = TextArea( - "This TextArea made transparent by adding a CSS class", classes="-transparent" + "This TextArea made transparent by adding a CSS class", + classes="-transparent", ) yield text_area2 text_area3 = TextArea( "This TextArea made transparent by setting style with python" ) text_area3.styles.background = "transparent" - yield text_area3 + yield text_area3 option_list = OptionList( "1) This is an OptionList\n", "2) With a transparent background\n", @@ -4264,3 +4264,20 @@ class DC(App): """ assert snap_compare(DC()) + + +def test_textarea_line_highlight(snap_compare): + """Check the highlighted line may be disabled in the TextArea. + + You should see a TextArea with the text Hello\nWorld. + + There should be a cursor, but the line will *not* be highlighted.""" + + class TextAreaLine(App): + def compose(self) -> ComposeResult: + yield TextArea("Hello\nWorld!", highlight_cursor_line=False) + + def on_mount(self) -> None: + self.query_one(TextArea).move_cursor((0, 2)) + + assert snap_compare(TextAreaLine())