From 16bdcf8f1d17e258d8dfdc582c8f5c0a925fd6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 22 Mar 2023 08:17:00 +0000 Subject: [PATCH 1/4] Return 'self' in some widget verb methods. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I compiled a list of all widget methods that return 'None' and for which it _could_ make sense to make this change. (I filtered out some methods, like watch and action methods.) I tried choosing a subset of those methods, trying to only pick methods for which there weren't two things that could be returned (e.g., 'Widget.move_child' _could_ return either the widget or the child that was moved) and I also tried to only pick methods that have little or no parameters (e.g., 'Widget.animate' has many parameters and is typically called with quite a few. These are all the 'Widget' methods for which this could make sense: - 'move_child' (either return 'self' or the actual 'child' that was moved…) - 'animate' - 'scroll_to' / 'scroll_relate' / 'scroll_home' / 'scroll_end' / 'scroll_left' / 'scroll_right' / 'scroll_down' / 'scroll_up' / 'scroll_page_up' / 'scroll_page_down' / 'scroll_page_left' / 'scroll_page_right' / 'scroll_visible' - 'refresh' - 'focus' / 'reset_focus' - 'capture_mouse' / 'release_mouse' Additionally, I looked at each widget, and found these methods: - 'Tree' - 'TreeNode' - 'expand' / 'expand_all' / 'collapse' / 'collapse_all' / 'toggle' / 'toggle_all' - 'set_label' - 'clear' / 'reset' - 'select_node' (either return 'self' or the actual 'node' that was selected) - 'scroll_to_line' / 'scroll_to_node' - 'refresh_line' - 'ToggleButton' - 'toggle' (and 'action_toggle'?) - 'TextLog' - 'write' - 'clear' - 'Tabs' - 'add_tab' / 'remove_tab' - 'clear' - 'Switch' - 'toggle' (and 'action_toggle'?) - 'Static' - 'update' - 'Pretty' - 'update' - 'Placeholder' - 'cycle_variant' - '_markdown.py' - 'MarkdownBlock' - 'set_content' - 'MarkdownTableOfContents' - 'set_table_of_contents' - 'Input' - 'insert_text_at_cursor' - 'DirectoryTree' - 'load_directory' - 'DataTable' - 'update_cell' / 'update_cell_at' - 'clear' - 'refresh_coordinate' / 'refresh_row' / 'refresh_column' - 'sort' - 'Button' - 'press' Related issues: #1908 Related discussions: #1817 --- src/textual/dom.py | 6 ++--- src/textual/widget.py | 9 ++++--- src/textual/widgets/_button.py | 8 +++---- src/textual/widgets/_data_table.py | 34 +++++++++++++-------------- src/textual/widgets/_placeholder.py | 5 ++-- src/textual/widgets/_switch.py | 8 +++++-- src/textual/widgets/_tabs.py | 8 +++++-- src/textual/widgets/_text_log.py | 15 ++++++++---- src/textual/widgets/_toggle_button.py | 8 +++++-- src/textual/widgets/_tree.py | 29 ++++++++++++++--------- 10 files changed, 79 insertions(+), 51 deletions(-) diff --git a/src/textual/dom.py b/src/textual/dom.py index 23e878ba46..cb9881f653 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -42,7 +42,7 @@ from .css.query import DOMQuery, QueryType from .screen import Screen from .widget import Widget - from typing_extensions import TypeAlias + from typing_extensions import Self, TypeAlias from typing_extensions import Literal @@ -950,5 +950,5 @@ def has_pseudo_class(self, *class_names: str) -> bool: has_pseudo_classes = self.pseudo_classes.issuperset(class_names) return has_pseudo_classes - def refresh(self, *, repaint: bool = True, layout: bool = False) -> None: - pass + def refresh(self, *, repaint: bool = True, layout: bool = False) -> Self: + return self diff --git a/src/textual/widget.py b/src/textual/widget.py index 6ac329d6d1..e99be7e134 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -2542,7 +2542,7 @@ def refresh( *regions: Region, repaint: bool = True, layout: bool = False, - ) -> None: + ) -> Self: """Initiate a refresh of the widget. This method sets an internal flag to perform a refresh, which will be done on the @@ -2577,6 +2577,7 @@ def refresh( self._repaint_required = True self.check_idle() + return self def remove(self) -> AwaitRemove: """Remove the Widget from the DOM (effectively deleting it). @@ -2658,7 +2659,7 @@ def _check_refresh(self) -> None: self._layout_required = False screen.post_message(messages.Layout()) - def focus(self, scroll_visible: bool = True) -> None: + def focus(self, scroll_visible: bool = True) -> Self: """Give focus to this widget. Args: @@ -2674,13 +2675,15 @@ def set_focus(widget: Widget): pass self.app.call_later(set_focus, self) + return self - def reset_focus(self) -> None: + def reset_focus(self) -> Self: """Reset the focus (move it to the next available widget).""" try: self.screen._reset_focus(self) except NoScreen: pass + return self def capture_mouse(self, capture: bool = True) -> None: """Capture (or release) the mouse. diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 34a82a195c..e0d0625241 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -1,11 +1,10 @@ from __future__ import annotations from functools import partial -from typing import cast import rich.repr from rich.text import Text, TextType -from typing_extensions import Literal +from typing_extensions import Literal, Self from .. import events from ..css._error_tools import friendly_list @@ -233,14 +232,15 @@ async def _on_click(self, event: events.Click) -> None: event.stop() self.press() - def press(self) -> None: + def press(self) -> Self: """Respond to a button press.""" if self.disabled or not self.display: - return + return self # Manage the "active" effect: self._start_active_affect() # ...and let other components know that we've just been clicked: self.post_message(Button.Pressed(self)) + return self def _start_active_affect(self) -> None: """Start a small animation to show the button was clicked.""" diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 649e495e4b..3ba75068e8 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -13,7 +13,7 @@ from rich.segment import Segment from rich.style import Style from rich.text import Text, TextType -from typing_extensions import Literal, TypeAlias +from typing_extensions import Literal, Self, TypeAlias from .. import events from .._cache import LRUCache @@ -1156,7 +1156,7 @@ def _get_column_region(self, column_index: int) -> Region: full_column_region = Region(x, 0, width, height) return full_column_region - def clear(self, columns: bool = False) -> None: + def clear(self, columns: bool = False) -> Self: """Clear the table. Args: @@ -1175,7 +1175,7 @@ def clear(self, columns: bool = False) -> None: self.hover_coordinate = Coordinate(0, 0) self._label_column = Column(self._label_column_key, Text(), auto_width=True) self._labelled_row_exists = False - self.refresh() + return self.refresh() def add_column( self, label: TextType, *, width: int | None = None, key: str | None = None @@ -1333,49 +1333,49 @@ def on_idle(self) -> None: self._updated_cells.clear() self._update_column_widths(updated_columns) - def refresh_coordinate(self, coordinate: Coordinate) -> None: + def refresh_coordinate(self, coordinate: Coordinate) -> Self: """Refresh the cell at a coordinate. Args: coordinate: The coordinate to refresh. """ if not self.is_valid_coordinate(coordinate): - return + return self region = self._get_cell_region(coordinate) - self._refresh_region(region) + return self._refresh_region(region) - def refresh_row(self, row_index: int) -> None: + def refresh_row(self, row_index: int) -> Self: """Refresh the row at the given index. Args: row_index: The index of the row to refresh. """ if not self.is_valid_row_index(row_index): - return + return self region = self._get_row_region(row_index) - self._refresh_region(region) + return self._refresh_region(region) - def refresh_column(self, column_index: int) -> None: + def refresh_column(self, column_index: int) -> Self: """Refresh the column at the given index. Args: column_index: The index of the column to refresh. """ if not self.is_valid_column_index(column_index): - return + return self region = self._get_column_region(column_index) - self._refresh_region(region) + return self._refresh_region(region) - def _refresh_region(self, region: Region) -> None: + def _refresh_region(self, region: Region) -> Self: """Refresh a region of the DataTable, if it's visible within the window. This method will translate the region to account for scrolling.""" if not self.window_region.overlaps(region): - return + return self region = region.translate(-self.scroll_offset) - self.refresh(region) + return self.refresh(region) def is_valid_row_index(self, row_index: int) -> bool: """Return a boolean indicating whether the row_index is within table bounds. @@ -1839,7 +1839,7 @@ def sort( self, *columns: ColumnKey | str, reverse: bool = False, - ) -> None: + ) -> Self: """Sort the rows in the DataTable by one or more column keys. Args: @@ -1861,7 +1861,7 @@ def sort_by_column_keys( {key: new_index for new_index, (key, _) in enumerate(ordered_rows)} ) self._update_count += 1 - self.refresh() + return self.refresh() def _scroll_cursor_into_view(self, animate: bool = False) -> None: """When the cursor is at a boundary of the DataTable and moves out diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py index 81c877c99a..57947779f7 100644 --- a/src/textual/widgets/_placeholder.py +++ b/src/textual/widgets/_placeholder.py @@ -5,7 +5,7 @@ from itertools import cycle from rich.console import RenderableType -from typing_extensions import Literal +from typing_extensions import Literal, Self from .. import events from ..css._error_tools import friendly_list @@ -132,9 +132,10 @@ def render(self) -> RenderableType: """ return self._renderables[self.variant] - def cycle_variant(self) -> None: + def cycle_variant(self) -> Self: """Get the next variant in the cycle.""" self.variant = next(self._variants_cycle) + return self def watch_variant( self, old_variant: PlaceholderVariant, variant: PlaceholderVariant diff --git a/src/textual/widgets/_switch.py b/src/textual/widgets/_switch.py index 3f9044df49..418f3b4e61 100644 --- a/src/textual/widgets/_switch.py +++ b/src/textual/widgets/_switch.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import ClassVar +from typing import TYPE_CHECKING, ClassVar from rich.console import RenderableType @@ -11,6 +11,9 @@ from ..scrollbar import ScrollBarRender from ..widget import Widget +if TYPE_CHECKING: + from typing_extensions import Self + class Switch(Widget, can_focus=True): """A switch widget that represents a boolean value. @@ -158,10 +161,11 @@ def action_toggle(self) -> None: """Toggle the state of the switch.""" self.toggle() - def toggle(self) -> None: + def toggle(self) -> Self: """Toggle the switch value. As a result of the value changing, a `Switch.Changed` message will be posted. """ self.value = not self.value + return self diff --git a/src/textual/widgets/_tabs.py b/src/textual/widgets/_tabs.py index 1474975edc..79d1bfa19d 100644 --- a/src/textual/widgets/_tabs.py +++ b/src/textual/widgets/_tabs.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import ClassVar +from typing import TYPE_CHECKING, ClassVar import rich.repr from rich.style import Style @@ -18,6 +18,9 @@ from ..widget import Widget from ..widgets import Static +if TYPE_CHECKING: + from typing_extensions import Self + class Underline(Widget): """The animated underline beneath tabs.""" @@ -316,13 +319,14 @@ async def refresh_active() -> None: self.call_after_refresh(refresh_active) - def clear(self) -> None: + def clear(self) -> Self: """Clear all the tabs.""" underline = self.query_one(Underline) underline.highlight_start = 0 underline.highlight_end = 0 self.query("#tabs-list > Tab").remove() self.post_message(self.Cleared(self)) + return self def remove_tab(self, tab_or_id: Tab | str | None) -> None: """Remove a tab. diff --git a/src/textual/widgets/_text_log.py b/src/textual/widgets/_text_log.py index fa12226ea0..7f36319806 100644 --- a/src/textual/widgets/_text_log.py +++ b/src/textual/widgets/_text_log.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional, cast +from typing import TYPE_CHECKING, Optional, cast from rich.console import RenderableType from rich.highlighter import ReprHighlighter @@ -18,6 +18,9 @@ from ..scroll_view import ScrollView from ..strip import Strip +if TYPE_CHECKING: + from typing_extensions import Self + class TextLog(ScrollView, can_focus=True): """A widget for logging text.""" @@ -89,7 +92,7 @@ def write( width: int | None = None, expand: bool = False, shrink: bool = True, - ) -> None: + ) -> Self: """Write text or a rich renderable. Args: @@ -136,7 +139,7 @@ def write( ) lines = list(Segment.split_lines(segments)) if not lines: - return + return self self.max_width = max( self.max_width, @@ -154,14 +157,16 @@ def write( self.virtual_size = Size(self.max_width, len(self.lines)) self.scroll_end(animate=False) - def clear(self) -> None: + return self + + def clear(self) -> Self: """Clear the text log.""" self.lines.clear() self._line_cache.clear() self._start_line = 0 self.max_width = 0 self.virtual_size = Size(self.max_width, len(self.lines)) - self.refresh() + return self.refresh() def render_line(self, y: int) -> Strip: scroll_x, scroll_y = self.scroll_offset diff --git a/src/textual/widgets/_toggle_button.py b/src/textual/widgets/_toggle_button.py index 51efa137bc..b53c780c0b 100644 --- a/src/textual/widgets/_toggle_button.py +++ b/src/textual/widgets/_toggle_button.py @@ -5,7 +5,7 @@ from __future__ import annotations -from typing import ClassVar +from typing import TYPE_CHECKING, ClassVar from rich.style import Style from rich.text import Text, TextType @@ -17,6 +17,9 @@ from ..reactive import reactive from ._static import Static +if TYPE_CHECKING: + from typing_extensions import Self + class ToggleButton(Static, can_focus=True): """Base toggle button widget. @@ -201,9 +204,10 @@ def get_content_width(self, container: Size, viewport: Size) -> int: def get_content_height(self, container: Size, viewport: Size, width: int) -> int: return 1 - def toggle(self) -> None: + def toggle(self) -> Self: """Toggle the value of the widget.""" self.value = not self.value + return self def action_toggle(self) -> None: """Toggle the value of the widget when called as an action. diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index fb582dd796..5a97e27094 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -22,7 +22,7 @@ from ..strip import Strip if TYPE_CHECKING: - from typing_extensions import TypeAlias + from typing_extensions import Self, TypeAlias NodeID = NewType("NodeID", int) """The type of an ID applied to a [TreeNode][textual.widgets._tree.TreeNode].""" @@ -89,7 +89,7 @@ def __init__( *, expanded: bool = True, allow_expand: bool = True, - ) -> None: + ): """Initialise the node. Args: @@ -201,15 +201,17 @@ def _expand(self, expand_all: bool) -> None: for child in self.children: child._expand(expand_all) - def expand(self) -> None: + def expand(self) -> Self: """Expand the node (show its children).""" self._expand(False) self._tree._invalidate() + return self - def expand_all(self) -> None: + def expand_all(self) -> Self: """Expand the node (show its children) and all those below it.""" self._expand(True) self._tree._invalidate() + return self def _collapse(self, collapse_all: bool) -> None: """Mark the node as collapsed (its children are hidden). @@ -223,29 +225,33 @@ def _collapse(self, collapse_all: bool) -> None: for child in self.children: child._collapse(collapse_all) - def collapse(self) -> None: + def collapse(self) -> Self: """Collapse the node (hide its children).""" self._collapse(False) self._tree._invalidate() + return self - def collapse_all(self) -> None: + def collapse_all(self) -> Self: """Collapse the node (hide its children) and all those below it.""" self._collapse(True) self._tree._invalidate() + return self - def toggle(self) -> None: + def toggle(self) -> Self: """Toggle the node's expanded state.""" if self._expanded: self.collapse() else: self.expand() + return self - def toggle_all(self) -> None: + def toggle_all(self) -> Self: """Toggle the node's expanded state and make all those below it match.""" if self._expanded: self.collapse_all() else: self.expand_all() + return self @property def label(self) -> TextType: @@ -597,7 +603,7 @@ def get_label_width(self, node: TreeNode[TreeDataType]) -> int: label = self.render_label(node, NULL_STYLE, NULL_STYLE) return label.cell_len - def clear(self) -> None: + def clear(self) -> Self: """Clear all nodes under root.""" self._line_cache.clear() self._tree_lines_cached = None @@ -613,9 +619,9 @@ def clear(self) -> None: expanded=True, ) self._updates += 1 - self.refresh() + return self.refresh() - def reset(self, label: TextType, data: TreeDataType | None = None) -> None: + def reset(self, label: TextType, data: TreeDataType | None = None) -> Self: """Clear the tree and reset the root node. Args: @@ -625,6 +631,7 @@ def reset(self, label: TextType, data: TreeDataType | None = None) -> None: self.clear() self.root.label = label self.root.data = data + return self def select_node(self, node: TreeNode[TreeDataType] | None) -> None: """Move the cursor to the given node, or reset cursor. From d32694f1769e33434e3d5f898e675e06e9770968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 22 Mar 2023 09:39:40 +0000 Subject: [PATCH 2/4] Update docstrings. --- CHANGELOG.md | 10 ++++++ src/textual/widget.py | 19 ++++++++--- src/textual/widgets/_button.py | 5 ++- src/textual/widgets/_data_table.py | 29 ++++++++++++++--- src/textual/widgets/_placeholder.py | 6 +++- src/textual/widgets/_switch.py | 3 ++ src/textual/widgets/_tabs.py | 6 +++- src/textual/widgets/_text_log.py | 9 +++++- src/textual/widgets/_toggle_button.py | 6 +++- src/textual/widgets/_tree.py | 45 ++++++++++++++++++++++----- 10 files changed, 116 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f45fb94d63..fb688a7c3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Breaking change: changed default behaviour of `Vertical` (see `VerticalScroll`) https://github.com/Textualize/textual/issues/1957 - The default `overflow` style for `Horizontal` was changed to `hidden hidden` https://github.com/Textualize/textual/issues/1957 - `DirectoryTree` also accepts `pathlib.Path` objects as the path to list https://github.com/Textualize/textual/issues/1438 +- Some widget methods now return `self` instead of `None` https://github.com/Textualize/textual/pull/2102: + - `Widget`: `refresh`, `focus`, `reset_focus` + - `Button.press` + - `DataTable`: `clear`, `refresh_coordinate`, `refresh_row`, `refresh_column`, `sort` + - `Placehoder.cycle_variant` + - `Switch.toggle` + - `Tabs.clear` + - `TextLog`: `write`, `clear` + - `TreeNode`: `expand`, `expand_all`, `collapse`, `collapse_all`, `toggle`, `toggle_all` + - `Tree`: `clear`, `reset` ### Removed diff --git a/src/textual/widget.py b/src/textual/widget.py index e99be7e134..971bde61bb 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -2558,8 +2558,11 @@ def refresh( Args: *regions: Additional screen regions to mark as dirty. - repaint: Repaint the widget (will call render() again). Defaults to True. - layout: Also layout widgets in the view. Defaults to False. + repaint: Repaint the widget (will call render() again). + layout: Also layout widgets in the view. + + Returns: + The `Widget` instance. """ if layout and not self._layout_required: self._layout_required = True @@ -2663,8 +2666,10 @@ def focus(self, scroll_visible: bool = True) -> Self: """Give focus to this widget. Args: - scroll_visible: Scroll parent to make this widget - visible. Defaults to True. + scroll_visible: Scroll parent to make this widget visible. + + Returns: + The `Widget` instance. """ def set_focus(widget: Widget): @@ -2678,7 +2683,11 @@ def set_focus(widget: Widget): return self def reset_focus(self) -> Self: - """Reset the focus (move it to the next available widget).""" + """Reset the focus (move it to the next available widget). + + Returns: + The `Widget` instance. + """ try: self.screen._reset_focus(self) except NoScreen: diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index e0d0625241..dfa1867398 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -233,7 +233,10 @@ async def _on_click(self, event: events.Click) -> None: self.press() def press(self) -> Self: - """Respond to a button press.""" + """Respond to a button press. + + Returns: + The button instance.""" if self.disabled or not self.display: return self # Manage the "active" effect: diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 3ba75068e8..50c8814ce5 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -1160,7 +1160,10 @@ def clear(self, columns: bool = False) -> Self: """Clear the table. Args: - columns: Also clear the columns. Defaults to False. + columns: Also clear the columns. + + Returns: + The `DataTable` instance. """ self._clear_caches() self._y_offsets.clear() @@ -1338,6 +1341,9 @@ def refresh_coordinate(self, coordinate: Coordinate) -> Self: Args: coordinate: The coordinate to refresh. + + Returns: + The `DataTable` instance. """ if not self.is_valid_coordinate(coordinate): return self @@ -1349,6 +1355,9 @@ def refresh_row(self, row_index: int) -> Self: Args: row_index: The index of the row to refresh. + + Returns: + The `DataTable` instance. """ if not self.is_valid_row_index(row_index): return self @@ -1361,6 +1370,9 @@ def refresh_column(self, column_index: int) -> Self: Args: column_index: The index of the column to refresh. + + Returns: + The `DataTable` instance. """ if not self.is_valid_column_index(column_index): return self @@ -1369,9 +1381,13 @@ def refresh_column(self, column_index: int) -> Self: return self._refresh_region(region) def _refresh_region(self, region: Region) -> Self: - """Refresh a region of the DataTable, if it's visible within - the window. This method will translate the region to account - for scrolling.""" + """Refresh a region of the DataTable, if it's visible within the window. + + This method will translate the region to account for scrolling. + + Returns: + The `DataTable` instance. + """ if not self.window_region.overlaps(region): return self region = region.translate(-self.scroll_offset) @@ -1840,11 +1856,14 @@ def sort( *columns: ColumnKey | str, reverse: bool = False, ) -> Self: - """Sort the rows in the DataTable by one or more column keys. + """Sort the rows in the `DataTable` by one or more column keys. Args: columns: One or more columns to sort by the values in. reverse: If True, the sort order will be reversed. + + Returns: + The `DataTable` instance. """ def sort_by_column_keys( diff --git a/src/textual/widgets/_placeholder.py b/src/textual/widgets/_placeholder.py index 57947779f7..17b0614af4 100644 --- a/src/textual/widgets/_placeholder.py +++ b/src/textual/widgets/_placeholder.py @@ -133,7 +133,11 @@ def render(self) -> RenderableType: return self._renderables[self.variant] def cycle_variant(self) -> Self: - """Get the next variant in the cycle.""" + """Get the next variant in the cycle. + + Returns: + The `Placeholder` instance. + """ self.variant = next(self._variants_cycle) return self diff --git a/src/textual/widgets/_switch.py b/src/textual/widgets/_switch.py index 418f3b4e61..cf636b92ab 100644 --- a/src/textual/widgets/_switch.py +++ b/src/textual/widgets/_switch.py @@ -166,6 +166,9 @@ def toggle(self) -> Self: As a result of the value changing, a `Switch.Changed` message will be posted. + + Returns: + The `Switch` instance. """ self.value = not self.value return self diff --git a/src/textual/widgets/_tabs.py b/src/textual/widgets/_tabs.py index 79d1bfa19d..3191b1e47c 100644 --- a/src/textual/widgets/_tabs.py +++ b/src/textual/widgets/_tabs.py @@ -320,7 +320,11 @@ async def refresh_active() -> None: self.call_after_refresh(refresh_active) def clear(self) -> Self: - """Clear all the tabs.""" + """Clear all the tabs. + + Returns: + The `Tabs` instance. + """ underline = self.query_one(Underline) underline.highlight_start = 0 underline.highlight_end = 0 diff --git a/src/textual/widgets/_text_log.py b/src/textual/widgets/_text_log.py index 7f36319806..4fba9f24a2 100644 --- a/src/textual/widgets/_text_log.py +++ b/src/textual/widgets/_text_log.py @@ -100,6 +100,9 @@ def write( width: Width to render or ``None`` to use optimal width. expand: Enable expand to widget width, or ``False`` to use `width`. shrink: Enable shrinking of content to fit width. + + Returns: + The `TextLog` instance. """ renderable: RenderableType @@ -160,7 +163,11 @@ def write( return self def clear(self) -> Self: - """Clear the text log.""" + """Clear the text log. + + Returns: + The `TextLog` instance. + """ self.lines.clear() self._line_cache.clear() self._start_line = 0 diff --git a/src/textual/widgets/_toggle_button.py b/src/textual/widgets/_toggle_button.py index b53c780c0b..a25b283e7a 100644 --- a/src/textual/widgets/_toggle_button.py +++ b/src/textual/widgets/_toggle_button.py @@ -205,7 +205,11 @@ def get_content_height(self, container: Size, viewport: Size, width: int) -> int return 1 def toggle(self) -> Self: - """Toggle the value of the widget.""" + """Toggle the value of the widget. + + Returns: + The `ToggleButton` instance. + """ self.value = not self.value return self diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 5a97e27094..f0daca59e5 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -202,13 +202,21 @@ def _expand(self, expand_all: bool) -> None: child._expand(expand_all) def expand(self) -> Self: - """Expand the node (show its children).""" + """Expand the node (show its children). + + Returns: + The `TreeNode` instance. + """ self._expand(False) self._tree._invalidate() return self def expand_all(self) -> Self: - """Expand the node (show its children) and all those below it.""" + """Expand the node (show its children) and all those below it. + + Returns: + The `TreeNode` instance. + """ self._expand(True) self._tree._invalidate() return self @@ -226,19 +234,31 @@ def _collapse(self, collapse_all: bool) -> None: child._collapse(collapse_all) def collapse(self) -> Self: - """Collapse the node (hide its children).""" + """Collapse the node (hide its children). + + Returns: + The `TreeNode` instance. + """ self._collapse(False) self._tree._invalidate() return self def collapse_all(self) -> Self: - """Collapse the node (hide its children) and all those below it.""" + """Collapse the node (hide its children) and all those below it. + + Returns: + The `TreeNode` instance. + """ self._collapse(True) self._tree._invalidate() return self def toggle(self) -> Self: - """Toggle the node's expanded state.""" + """Toggle the node's expanded state. + + Returns: + The `TreeNode` instance. + """ if self._expanded: self.collapse() else: @@ -246,7 +266,11 @@ def toggle(self) -> Self: return self def toggle_all(self) -> Self: - """Toggle the node's expanded state and make all those below it match.""" + """Toggle the node's expanded state and make all those below it match. + + Returns: + The `TreeNode` instance. + """ if self._expanded: self.collapse_all() else: @@ -604,7 +628,11 @@ def get_label_width(self, node: TreeNode[TreeDataType]) -> int: return label.cell_len def clear(self) -> Self: - """Clear all nodes under root.""" + """Clear all nodes under root. + + Returns: + The `Tree` instance. + """ self._line_cache.clear() self._tree_lines_cached = None self._current_id = 0 @@ -627,6 +655,9 @@ def reset(self, label: TextType, data: TreeDataType | None = None) -> Self: Args: label: The label for the root node. data: Optional data for the root node. + + Returns: + The `Tree` instance. """ self.clear() self.root.label = label From b4c2afe7891cd8bdf671e02823e64fd7475b8ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 22 Mar 2023 09:53:54 +0000 Subject: [PATCH 3/4] Remove fat-finger deletion. --- src/textual/widgets/_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index f0daca59e5..e5da2166b7 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -89,7 +89,7 @@ def __init__( *, expanded: bool = True, allow_expand: bool = True, - ): + ) -> None: """Initialise the node. Args: From 65e005cffe69cbf11a1c42aaca1c5add4739faf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 23 Mar 2023 10:07:33 +0000 Subject: [PATCH 4/4] Return 'self' explicitly. Related review comment: https://github.com/Textualize/textual/pull/2102\#pullrequestreview-1354201387 --- src/textual/widgets/_data_table.py | 18 ++++++++++++------ src/textual/widgets/_text_log.py | 3 ++- src/textual/widgets/_tree.py | 3 ++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 50c8814ce5..4469f85a3a 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -1178,7 +1178,8 @@ def clear(self, columns: bool = False) -> Self: self.hover_coordinate = Coordinate(0, 0) self._label_column = Column(self._label_column_key, Text(), auto_width=True) self._labelled_row_exists = False - return self.refresh() + self.refresh() + return self def add_column( self, label: TextType, *, width: int | None = None, key: str | None = None @@ -1348,7 +1349,8 @@ def refresh_coordinate(self, coordinate: Coordinate) -> Self: if not self.is_valid_coordinate(coordinate): return self region = self._get_cell_region(coordinate) - return self._refresh_region(region) + self._refresh_region(region) + return self def refresh_row(self, row_index: int) -> Self: """Refresh the row at the given index. @@ -1363,7 +1365,8 @@ def refresh_row(self, row_index: int) -> Self: return self region = self._get_row_region(row_index) - return self._refresh_region(region) + self._refresh_region(region) + return self def refresh_column(self, column_index: int) -> Self: """Refresh the column at the given index. @@ -1378,7 +1381,8 @@ def refresh_column(self, column_index: int) -> Self: return self region = self._get_column_region(column_index) - return self._refresh_region(region) + self._refresh_region(region) + return self def _refresh_region(self, region: Region) -> Self: """Refresh a region of the DataTable, if it's visible within the window. @@ -1391,7 +1395,8 @@ def _refresh_region(self, region: Region) -> Self: if not self.window_region.overlaps(region): return self region = region.translate(-self.scroll_offset) - return self.refresh(region) + self.refresh(region) + return self def is_valid_row_index(self, row_index: int) -> bool: """Return a boolean indicating whether the row_index is within table bounds. @@ -1880,7 +1885,8 @@ def sort_by_column_keys( {key: new_index for new_index, (key, _) in enumerate(ordered_rows)} ) self._update_count += 1 - return self.refresh() + self.refresh() + return self def _scroll_cursor_into_view(self, animate: bool = False) -> None: """When the cursor is at a boundary of the DataTable and moves out diff --git a/src/textual/widgets/_text_log.py b/src/textual/widgets/_text_log.py index 4fba9f24a2..bcf0923546 100644 --- a/src/textual/widgets/_text_log.py +++ b/src/textual/widgets/_text_log.py @@ -173,7 +173,8 @@ def clear(self) -> Self: self._start_line = 0 self.max_width = 0 self.virtual_size = Size(self.max_width, len(self.lines)) - return self.refresh() + self.refresh() + return self def render_line(self, y: int) -> Strip: scroll_x, scroll_y = self.scroll_offset diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index e5da2166b7..db0d77cf7a 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -647,7 +647,8 @@ def clear(self) -> Self: expanded=True, ) self._updates += 1 - return self.refresh() + self.refresh() + return self def reset(self, label: TextType, data: TreeDataType | None = None) -> Self: """Clear the tree and reset the root node.