Skip to content

Commit

Permalink
Return 'self' in some widget verb methods.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
rodrigogiraoserrao committed Mar 22, 2023
1 parent 2969273 commit 16bdcf8
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 51 deletions.
6 changes: 3 additions & 3 deletions src/textual/dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
9 changes: 6 additions & 3 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions src/textual/widgets/_button.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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."""
Expand Down
34 changes: 17 additions & 17 deletions src/textual/widgets/_data_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/textual/widgets/_placeholder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions src/textual/widgets/_switch.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import ClassVar
from typing import TYPE_CHECKING, ClassVar

from rich.console import RenderableType

Expand All @@ -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.
Expand Down Expand Up @@ -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
8 changes: 6 additions & 2 deletions src/textual/widgets/_tabs.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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."""
Expand Down Expand Up @@ -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.
Expand Down
15 changes: 10 additions & 5 deletions src/textual/widgets/_text_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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."""
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
8 changes: 6 additions & 2 deletions src/textual/widgets/_toggle_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 16bdcf8

Please sign in to comment.