Merge branch 'main' into no-container-scroll

This commit is contained in:
Dave Pearson
2023-04-24 11:35:47 +01:00
committed by GitHub
63 changed files with 54 additions and 255 deletions

View File

@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Changed
- `textual run` execs apps in a new context.
- Textual console no longer parses console markup.
- Breaking change: `Container` no longer shows required scrollbars by default https://github.com/Textualize/textual/issues/2361
- Breaking change: `VerticalScroll` no longer shows a required horizontal scrollbar by default
- Breaking change: `HorizontalScroll` no longer shows a required vertical scrollbar by default

View File

@@ -1,10 +1,8 @@
"""
Simulates a screenshot of the Textual devtools
"""
from textual.app import App
from textual.devtools.renderables import DevConsoleHeader
from textual.widgets import Static

View File

@@ -1,5 +1,5 @@
from textual.app import App, ComposeResult
from textual.widgets import Static, Header
from textual.widgets import Header, Static
TEXT = """\
Docking a widget removes it from the layout and fixes its position, aligned to either the top, right, bottom, or left edges of a container.

View File

@@ -45,8 +45,6 @@ class ListViewExample(App):
)
yield Footer()
```
"""

View File

@@ -4,7 +4,6 @@ Code browser example.
Run with:
python code_browser.py PATH
"""
import sys

View File

@@ -131,7 +131,6 @@ class GameCell(Button):
Args:
row (int): The row of the cell.
col (int): The column of the cell.
"""
super().__init__("", id=self.at(row, col))
self.row = row

View File

@@ -144,7 +144,6 @@ class BoundAnimator:
delay: A delay (in seconds) before the animation starts.
easing: An easing method.
on_complete: A callable to invoke when the animation is finished.
"""
start_value = getattr(self._obj, attribute)
if isinstance(value, str) and hasattr(start_value, "parse"):

View File

@@ -1,6 +1,5 @@
"""
Compatibility layer for asyncio.
"""
from __future__ import annotations

View File

@@ -9,7 +9,6 @@ Note that stdlib's @lru_cache is implemented in C and faster! It's best to use
@lru_cache where you are caching things that are fairly quick and called many times.
Use LRUCache where you want increased flexibility and you are caching slow operations
where the overhead of the cache is a small fraction of the total processing time.
"""
from __future__ import annotations
@@ -35,7 +34,6 @@ class LRUCache(Generic[CacheKey, CacheValue]):
Each entry is stored as [PREV, NEXT, KEY, VALUE] where PREV is a reference
to the previous entry, and NEXT is a reference to the next value.
"""
__slots__ = [

View File

@@ -8,7 +8,6 @@ queries regarding the widget under an offset, or the style under an offset.
Additionally, the compositor can render portions of the screen which may have updated,
without having to render the entire screen.
"""
from __future__ import annotations
@@ -410,7 +409,6 @@ class Compositor:
Returns:
Set of widgets that were exposed by the scroll.
"""
self._cuts = None
self._layers = None
@@ -790,7 +788,6 @@ class Compositor:
Returns:
Widget's composition information.
"""
if self.root is None:
raise errors.NoWidget("Widget is not in layout")
@@ -932,7 +929,6 @@ class Compositor:
Returns:
A ChopsUpdate if there is anything to update, otherwise `None`.
"""
screen_region = self.size.region
update_regions = self._dirty_regions.copy()
@@ -1019,7 +1015,6 @@ class Compositor:
Args:
widgets: Set of Widgets to update.
"""
# If there are any *new* widgets we need to invalidate the full map
if not self._full_map_invalidated and not widgets.issubset(

View File

@@ -25,7 +25,6 @@ def _duration_as_seconds(duration: str) -> float:
DurationParseError: If the argument ``duration`` is not a valid duration string.
Returns:
The duration in seconds.
"""
match = _match_duration(duration)

View File

@@ -19,7 +19,6 @@ class NodeList(Sequence["Widget"]):
A container for widgets that forms one level of hierarchy.
Although named a list, widgets may appear only once, making them more like a set.
"""
def __init__(self) -> None:

View File

@@ -1,6 +1,5 @@
"""
Timer context manager, only used in debug.
"""
import contextlib

View File

@@ -21,7 +21,6 @@ class SpatialMap(Generic[ValueType]):
The SpatialMap is able to quickly retrieve the values under a given "window" region
by combining the values in the grid squares under the visible area.
"""
def __init__(self, grid_width: int = 100, grid_height: int = 20) -> None:

View File

@@ -65,7 +65,6 @@ class StylesCache:
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━┛
```
"""
def __init__(self) -> None:

View File

@@ -2,7 +2,6 @@
A version of `time.sleep` that is more accurate than the standard library (even on Python 3.11).
This should only be imported on Windows.
"""
from time import sleep as time_sleep

View File

@@ -1,7 +1,6 @@
"""
A decorator used to create [workers](/guide/workers).
"""
@@ -68,7 +67,6 @@ def work(
group: A short string to identify a group of workers.
exit_on_error: Exit the app if the worker raises an error. Set to `False` to suppress exceptions.
exclusive: Cancel all workers in the same group.
"""
def decorator(

View File

@@ -2,7 +2,6 @@
A class to manage [workers](/guide/workers) for an app.
You access this object via [App.workers][textual.app.App.workers] or [Widget.workers][textual.dom.DOMNode.workers].
"""
@@ -28,7 +27,6 @@ class WorkerManager:
You will not have to construct this class manually, as widgets, screens, and apps
have a worker manager accessibly via a `workers` attribute.
"""
def __init__(self, app: App) -> None:
@@ -163,7 +161,6 @@ class WorkerManager:
Returns:
List of cancelled workers.
"""
workers = [worker for worker in self._workers if worker.node == node]
for worker in workers:

View File

@@ -231,7 +231,6 @@ class XTermParser(Parser[events.Event]):
Returns:
Keys
"""
keys = ANSI_SEQUENCES_KEYS.get(sequence)
if keys is not None:

View File

@@ -3,7 +3,6 @@
Here you will find the [App][textual.app.App] class, which is the base class for Textual apps.
See [app basics](/guide/app) for how to build Textual apps.
"""
from __future__ import annotations
@@ -248,7 +247,6 @@ class App(Generic[ReturnType], DOMNode):
```python
self.app.dark = not self.app.dark # Toggle dark mode
```
"""
def __init__(
@@ -403,7 +401,6 @@ class App(Generic[ReturnType], DOMNode):
Returns:
An object to manage workers.
"""
return self._workers
@@ -412,7 +409,6 @@ class App(Generic[ReturnType], DOMNode):
"""The return value of the app, or `None` if it as not yet been set.
The return value is set when calling [exit][textual.app.App.exit].
"""
return self._return_value
@@ -425,7 +421,6 @@ class App(Generic[ReturnType], DOMNode):
Returns:
A sequence of widgets.
"""
try:
return (self.screen,)
@@ -477,7 +472,6 @@ class App(Generic[ReturnType], DOMNode):
delay: A delay (in seconds) before the animation starts.
easing: An easing method.
on_complete: A callable to invoke when the animation is finished.
"""
self._animate(
attribute,
@@ -500,7 +494,6 @@ class App(Generic[ReturnType], DOMNode):
"""Is the driver running in 'headless' mode?
Headless mode is used when running tests with [run_test][textual.app.App.run_test].
"""
return False if self._driver is None else self._driver.is_headless
@@ -510,7 +503,6 @@ class App(Generic[ReturnType], DOMNode):
Returns:
A snapshot of the current state of the screen stack.
"""
return self._screen_stack.copy()
@@ -537,7 +529,6 @@ class App(Generic[ReturnType], DOMNode):
Returns:
The currently focused widget, or `None` if nothing is focused.
"""
return self.screen.focused
@@ -553,7 +544,6 @@ class App(Generic[ReturnType], DOMNode):
Returns:
A mapping of keys on to node + binding.
"""
namespace_binding_map: dict[str, tuple[DOMNode, Binding]] = {}
@@ -571,7 +561,6 @@ class App(Generic[ReturnType], DOMNode):
"""Yield child widgets for a container.
This method should be implemented in a subclass.
"""
yield from ()
@@ -591,7 +580,6 @@ class App(Generic[ReturnType], DOMNode):
This method handles the transition between light and dark mode when you
change the [dark][textual.app.App.dark] attribute.
"""
self.set_class(dark, "-dark-mode")
self.set_class(not dark, "-light-mode")
@@ -692,7 +680,6 @@ class App(Generic[ReturnType], DOMNode):
Returns:
Size of the terminal.
"""
if self._driver is not None and self._driver._size is not None:
width, height = self._driver._size
@@ -712,7 +699,6 @@ class App(Generic[ReturnType], DOMNode):
Returns:
A Textual logger.
"""
return self._logger
@@ -837,7 +823,6 @@ class App(Generic[ReturnType], DOMNode):
Args:
title: The title of the exported screenshot or None
to use app title.
"""
assert self._driver is not None, "App must be running"
width, height = self.size
@@ -985,8 +970,6 @@ class App(Generic[ReturnType], DOMNode):
headless: Run in headless mode (no output or input).
size: Force terminal size to `(WIDTH, HEIGHT)`,
or None to auto-detect.
"""
from .pilot import Pilot
@@ -1364,7 +1347,6 @@ class App(Generic[ReturnType], DOMNode):
Returns:
A screen instance and an awaitable that awaits the children mounting.
"""
_screen = self.get_screen(screen)
if not _screen.is_running:
@@ -1385,7 +1367,6 @@ class App(Generic[ReturnType], DOMNode):
Returns:
The screen that was replaced.
"""
if self._screen_stack:
self.screen.refresh()
@@ -1432,7 +1413,6 @@ class App(Generic[ReturnType], DOMNode):
Args:
screen: Either a Screen object or screen name (the `name` argument when installed).
"""
if not isinstance(screen, (Screen, str)):
raise TypeError(
@@ -1770,7 +1750,6 @@ class App(Generic[ReturnType], DOMNode):
"""Called immediately prior to processing messages.
May be used as a hook for any operations that should run first.
"""
async def take_screenshot() -> None:
@@ -1859,7 +1838,6 @@ class App(Generic[ReturnType], DOMNode):
after: A location to mount after.
Returns:
List of modified widgets.
"""
if not widgets:
@@ -2061,8 +2039,6 @@ class App(Generic[ReturnType], DOMNode):
For terminals that support a bell, this typically makes a notification or error sound.
Some terminals may make no sound or display a visual bell indicator, depending on configuration.
"""
if not self.is_headless and self._driver is not None:
self._driver.write("\07")
@@ -2345,7 +2321,6 @@ class App(Generic[ReturnType], DOMNode):
Returns:
The child widgets of root.
"""
stack: list[Widget] = [root]
pop = stack.pop

View File

@@ -1,8 +1,6 @@
"""
An *optionally* awaitable object returned by methods that remove widgets.
"""
from asyncio import Event, Task

View File

@@ -3,7 +3,6 @@
A binding maps a key press on to an action.
See [bindings](/guide/input#bindings) in the guide for details.
"""

View File

@@ -4,7 +4,6 @@ Functions to run Textual apps with an updated environment.
Note that these methods will execute apps in a new process, and abandon the current process.
This means that (if they succeed) they will never return.
"""
from __future__ import annotations
@@ -98,7 +97,6 @@ def exec_python(args: list[str], environment: dict[str, str]) -> NoReturn:
Args:
args: Additional arguments.
environment: Environment variables.
"""
_flush()
os.execvpe(sys.executable, ["python", *args], environment)
@@ -148,7 +146,6 @@ def exec_import(
import_name: The Python import.
args: Command line arguments.
environment: Environment variables.
"""
module, _colon, app = import_name.partition(":")
app = app or "app"

View File

@@ -38,7 +38,6 @@ class Bar(Widget):
background: $surface;
color: $success;
}
"""
def watch_animation_running(self, running: bool) -> None:

View File

@@ -26,8 +26,6 @@ for name, triplet in sorted(COLOR_NAME_TO_RGB.items()):
)
output = table
```
"""
from __future__ import annotations
@@ -158,7 +156,6 @@ class Color(NamedTuple):
>>> color + color_with_alpha
Color(177, 25, 12)
```
"""
r: int
@@ -204,7 +201,6 @@ class Color(NamedTuple):
Returns:
Inverse color.
"""
r, g, b, a = self
return Color(255 - r, 255 - g, 255 - b, a)
@@ -245,7 +241,6 @@ class Color(NamedTuple):
Returns:
Normalized components.
"""
r, g, b, _a = self
return (r / 255, g / 255, b / 255)
@@ -264,7 +259,6 @@ class Color(NamedTuple):
Returns:
Color encoded in HSL format.
"""
r, g, b = self.normalized
h, l, s = rgb_to_hls(r, g, b)
@@ -276,7 +270,6 @@ class Color(NamedTuple):
A value of 1 is returned for pure white, and 0 for pure black.
Other colors lie on a gradient between the two extremes.
"""
r, g, b = self.normalized
brightness = (299 * r + 587 * g + 114 * b) / 1000
@@ -287,7 +280,6 @@ class Color(NamedTuple):
"""The color in CSS hex form, with 6 digits for RGB, and 8 digits for RGBA.
For example, `"#46b3de"` for an RGB color, or `"#3342457f"` for a color with alpha.
"""
r, g, b, a = self.clamped
return (
@@ -301,7 +293,6 @@ class Color(NamedTuple):
"""The color in CSS hex form, with 6 digits for RGB. Alpha is ignored.
For example, `"#46b3de"`.
"""
r, g, b, _a = self.clamped
return f"#{r:02X}{g:02X}{b:02X}"
@@ -311,7 +302,6 @@ class Color(NamedTuple):
"""The color in CSS RGB or RGBA form.
For example, `"rgb(10,20,30)"` for an RGB color, or `"rgb(50,70,80,0.5)"` for an RGBA color.
"""
r, g, b, a = self
return f"rgb({r},{g},{b})" if a == 1 else f"rgba({r},{g},{b},{a})"
@@ -322,7 +312,6 @@ class Color(NamedTuple):
Returns:
The monochrome (black and white) version of this color.
"""
r, g, b, a = self
gray = round(r * 0.2126 + g * 0.7152 + b * 0.0722)

View File

@@ -1,6 +1,5 @@
"""
Constants that we might want to expose via the public API.
"""
from __future__ import annotations

View File

@@ -1,6 +1,5 @@
"""
Container widgets for quick styling.
"""
from __future__ import annotations

View File

@@ -1,6 +1,5 @@
"""
A class to store a coordinate, used by the [DataTable][textual.widgets.DataTable].
"""
from __future__ import annotations

View File

@@ -4,7 +4,6 @@ setting attributes. This gives the developer more freedom in how to express styl
Descriptors also play nicely with Mypy, which is aware that attributes can have different types
when setting and getting.
"""
from __future__ import annotations
@@ -383,7 +382,6 @@ class BorderProperty:
Args:
layout: True if the layout should be refreshed after setting, False otherwise.
"""
def __init__(self, layout: bool) -> None:

View File

@@ -227,7 +227,6 @@ def _unresolved(variable_name: str, variables: Iterable[str], token: Token) -> N
Raises:
UnresolvedVariableError: Always raises a TokenError.
"""
message = f"reference to undefined variable '${variable_name}'"
suggested_variable = get_suggestion(variable_name, list(variables))

View File

@@ -770,7 +770,6 @@ class Styles(StylesBase):
Returns:
An iterable of CSS declarations.
"""
has_rule = rules.__contains__
@@ -1119,7 +1118,6 @@ class RenderStyles(StylesBase):
delay: A delay (in seconds) before the animation starts.
easing: An easing method.
on_complete: A callable to invoke when the animation is finished.
"""
if self._animate is None:
assert self.node is not None

View File

@@ -333,7 +333,6 @@ class Stylesheet:
Raises:
StylesheetError: If the CSS could not be read.
StylesheetParseError: If the CSS is invalid.
"""
# Do this in a fresh Stylesheet so if there are errors we don't break self.
stylesheet = Stylesheet(variables=self._variables)

View File

@@ -72,7 +72,6 @@ WELCOME_MD = """
## Textual Demo
**Welcome**! Textual is a framework for creating sophisticated applications with the terminal.
"""
@@ -83,8 +82,6 @@ Textual is built on **Rich**, the popular Python library for advanced terminal o
Add content to your Textual App with Rich *renderables* (this text is written in Markdown and formatted with Rich's Markdown class).
Here are some examples:
"""
CSS_MD = """
@@ -95,7 +92,6 @@ Textual uses Cascading Stylesheets (CSS) to create Rich interactive User Interfa
- **Live editing** - see your changes without restarting the app!
Here's an example of some CSS used in this app:
"""
@@ -143,7 +139,6 @@ Build your own or use the builtin widgets.
- **Tree** An generic tree with expandable nodes.
- **DirectoryTree** A tree of file and folders.
- *... many more planned ...*
"""
@@ -160,7 +155,6 @@ Here are some links. You can click these!
Built with ♥ by [@click="app.open_link('https://www.textualize.io')"]Textualize.io[/]
"""

View File

@@ -25,8 +25,6 @@ class ColorSystem:
Primary is the main theme color
Secondary is a second theme color
"""
COLOR_NAMES = [
@@ -96,7 +94,6 @@ class ColorSystem:
Returns:
A mapping of color name on to a CSS-style encoded color
"""
primary = self.primary
@@ -134,7 +131,6 @@ class ColorSystem:
Returns:
Iterable of tuples (<SHADE SUFFIX, LUMINOSITY DELTA>)
"""
luminosity_step = spread / 2
for n in range(-NUMBER_OF_SHADES, +NUMBER_OF_SHADES + 1):
@@ -196,7 +192,6 @@ def show_design(light: ColorSystem, dark: ColorSystem) -> Table:
Returns:
Table showing all colors.
"""
@group()

View File

@@ -211,9 +211,9 @@ class DevtoolsClient:
log: The log to write to devtools
"""
if isinstance(log.objects_or_string, str):
self.console.print(log.objects_or_string)
self.console.print(log.objects_or_string, markup=False)
else:
self.console.print(*log.objects_or_string)
self.console.print(*log.objects_or_string, markup=False)
segments = self.console.export_segments()

View File

@@ -2,8 +2,6 @@
A DOMNode is a base class for any object within the Textual Document Object Model,
which includes all Widgets, Screens, and Apps.
"""
@@ -74,7 +72,6 @@ def check_identifiers(description: str, *names: str) -> None:
Args:
description: Description of where identifier is used for error message.
*names: Identifiers to check.
"""
match = _re_identifier.match
for name in names:
@@ -203,7 +200,6 @@ class DOMNode(MessagePump):
Returns:
The node's children.
"""
return self._nodes
@@ -328,7 +324,6 @@ class DOMNode(MessagePump):
"""Called after styles are updated.
Implement this in a subclass if you want to clear any cached data when the CSS is reloaded.
"""
@property
@@ -450,7 +445,6 @@ class DOMNode(MessagePump):
"""The parent node.
All nodes have parent once added to the DOM, with the exception of the App which is the *root* node.
"""
return cast("DOMNode | None", self._parent)
@@ -463,7 +457,6 @@ class DOMNode(MessagePump):
Raises:
NoScreen: If this node isn't mounted (and has no screen).
"""
# Get the node by looking up a chain of parents
# Note that self.screen may not be the same as self.app.screen
@@ -490,7 +483,6 @@ class DOMNode(MessagePump):
Raises:
ValueError: If the ID has already been set.
"""
check_identifiers("id", new_id)
@@ -520,7 +512,6 @@ class DOMNode(MessagePump):
Returns:
A Rich Text object.
"""
tokens = Text.styled(self.__class__.__name__)
if self.id is not None:
@@ -584,7 +575,6 @@ class DOMNode(MessagePump):
```python
my_widget.display = False # Hide my_widget
```
"""
return self.styles.display != "none" and not (self._closing or self._closed)
@@ -616,7 +606,6 @@ class DOMNode(MessagePump):
May be set to a boolean to make the node visible (`True`) or invisible (`False`), or to any valid value for the `visibility` rule.
When a node is invisible, Textual will reserve space for it, but won't display anything there.
"""
return self.styles.visibility != "hidden"
@@ -645,7 +634,6 @@ class DOMNode(MessagePump):
Returns:
A Tree renderable.
"""
def render_info(node: DOMNode) -> Pretty:
@@ -745,7 +733,6 @@ class DOMNode(MessagePump):
Returns:
A Rich style.
"""
background = Color(0, 0, 0, 0)
color = Color(255, 255, 255, 0)
@@ -775,7 +762,6 @@ class DOMNode(MessagePump):
Returns:
A Rich style.
"""
styles = self.styles
if styles.auto_border_title_color:
@@ -798,7 +784,6 @@ class DOMNode(MessagePump):
Returns:
A Rich style.
"""
styles = self.styles
if styles.auto_border_subtitle_color:
@@ -817,7 +802,6 @@ class DOMNode(MessagePump):
Returns:
`(<background color>, <color>)`
"""
base_background = background = BLACK
for node in reversed(self.ancestors_with_self):
@@ -832,7 +816,6 @@ class DOMNode(MessagePump):
Returns:
`(<parent background>, <parent color>, <background>, <color>)`
"""
base_background = background = WHITE
base_color = color = BLACK
@@ -873,7 +856,6 @@ class DOMNode(MessagePump):
Returns:
A list of nodes.
"""
return self.ancestors_with_self[1:]
@@ -883,7 +865,6 @@ class DOMNode(MessagePump):
Returns:
A list of nodes.
"""
return [child for child in self._nodes if child.display]
@@ -995,7 +976,6 @@ class DOMNode(MessagePump):
Returns:
A list of nodes.
"""
check_type = filter_type or DOMNode

View File

@@ -9,7 +9,6 @@ Verbose events are excluded from the textual console, unless you explicit reques
```
textual console -v
```
"""
from __future__ import annotations
@@ -50,15 +49,6 @@ class Callback(Event, bubble=False, verbose=True):
yield "callback", self.callback
class InvokeCallbacks(Event, bubble=False, verbose=True):
"""An internal event, sent to the screen to run callbacks.
- [ ] Bubbles
- [X] Verbose
"""
class ShutdownRequest(Event):
pass
@@ -76,7 +66,6 @@ class Load(Event, bubble=False):
- [ ] Bubbles
- [ ] Verbose
"""
@@ -88,7 +77,6 @@ class Idle(Event, bubble=False):
- [ ] Bubbles
- [ ] Verbose
"""
@@ -113,7 +101,6 @@ class Resize(Event, bubble=False):
size: The new size of the Widget.
virtual_size: The virtual size (scrollable size) of the Widget.
container_size: The size of the Widget's container widget.
"""
__slots__ = ["size", "virtual_size", "container_size"]
@@ -143,7 +130,6 @@ class Compose(Event, bubble=False, verbose=True):
- [ ] Bubbles
- [X] Verbose
"""
@@ -152,7 +138,6 @@ class Mount(Event, bubble=False, verbose=False):
- [ ] Bubbles
- [ ] Verbose
"""
@@ -161,7 +146,6 @@ class Unmount(Mount, bubble=False, verbose=False):
- [ ] Bubbles
- [ ] Verbose
"""
@@ -170,7 +154,6 @@ class Show(Event, bubble=False):
- [ ] Bubbles
- [ ] Verbose
"""
@@ -182,7 +165,6 @@ class Hide(Event, bubble=False):
A widget may be hidden by setting its `visible` flag to `False`, if it is no longer in a layout,
or if it has been offset beyond the edges of the terminal.
"""
@@ -191,7 +173,6 @@ class Ready(Event, bubble=False):
- [ ] Bubbles
- [ ] Verbose
"""
@@ -206,7 +187,6 @@ class MouseCapture(Event, bubble=False):
Args:
mouse_position: The position of the mouse when captured.
"""
def __init__(self, mouse_position: Offset) -> None:
@@ -318,7 +298,6 @@ class MouseEvent(InputEvent, bubble=True):
screen_x: The absolute x coordinate.
screen_y: The absolute y coordinate.
style: The Rich Style under the mouse cursor.
"""
__slots__ = [
@@ -399,7 +378,6 @@ class MouseEvent(InputEvent, bubble=True):
Returns:
Mouse coordinate.
"""
return Offset(self.x, self.y)
@@ -418,7 +396,6 @@ class MouseEvent(InputEvent, bubble=True):
Returns:
Mouse coordinate.
"""
return Offset(self.delta_x, self.delta_y)
@@ -466,7 +443,6 @@ class MouseMove(MouseEvent, bubble=False, verbose=True):
- [ ] Bubbles
- [X] Verbose
"""
@@ -476,7 +452,6 @@ class MouseDown(MouseEvent, bubble=True, verbose=True):
- [X] Bubbles
- [X] Verbose
"""
@@ -486,7 +461,6 @@ class MouseUp(MouseEvent, bubble=True, verbose=True):
- [X] Bubbles
- [X] Verbose
"""
@@ -496,7 +470,6 @@ class MouseScrollDown(MouseEvent, bubble=True):
- [X] Bubbles
- [ ] Verbose
"""
@@ -506,7 +479,6 @@ class MouseScrollUp(MouseEvent, bubble=True):
- [X] Bubbles
- [ ] Verbose
"""
@@ -515,7 +487,6 @@ class Click(MouseEvent, bubble=True):
- [X] Bubbles
- [ ] Verbose
"""
@@ -525,8 +496,6 @@ class Timer(Event, bubble=False, verbose=True):
- [ ] Bubbles
- [X] Verbose
"""
__slots__ = ["time", "count", "callback"]
@@ -554,7 +523,6 @@ class Enter(Event, bubble=False, verbose=True):
- [ ] Bubbles
- [X] Verbose
"""
@@ -563,7 +531,6 @@ class Leave(Event, bubble=False, verbose=True):
- [ ] Bubbles
- [X] Verbose
"""
@@ -572,7 +539,6 @@ class Focus(Event, bubble=False):
- [X] Bubbles
- [ ] Verbose
"""
@@ -581,7 +547,6 @@ class Blur(Event, bubble=False):
- [X] Bubbles
- [ ] Verbose
"""
@@ -590,7 +555,6 @@ class DescendantFocus(Event, bubble=True, verbose=True):
- [X] Bubbles
- [X] Verbose
"""
@@ -599,7 +563,6 @@ class DescendantBlur(Event, bubble=True, verbose=True):
- [X] Bubbles
- [X] Verbose
"""
@@ -631,7 +594,6 @@ class ScreenResume(Event, bubble=False):
- [ ] Bubbles
- [ ] Verbose
"""
@@ -640,5 +602,4 @@ class ScreenSuspend(Event, bubble=False):
- [ ] Bubbles
- [ ] Verbose
"""

View File

@@ -1,7 +1,6 @@
"""
Functions and classes to manage terminal geometry (anything involving coordinates or dimensions).
"""
from __future__ import annotations
@@ -75,7 +74,6 @@ class Offset(NamedTuple):
>>> -offset
Offset(x=-13, y=-2)
```
"""
x: int = 0
@@ -167,7 +165,6 @@ class Size(NamedTuple):
>>> size + Size(10, 20)
Size(width=12, height=23)
```
"""
width: int = 0
@@ -283,7 +280,6 @@ class Region(NamedTuple):
>>> region.contains(10, 8)
True
```
"""
x: int = 0
@@ -407,7 +403,6 @@ class Region(NamedTuple):
"""A pair of integers for the start and end columns (x coordinates) in this region.
The end value is *exclusive*.
"""
return (self.x, self.x + self.width)
@@ -416,7 +411,6 @@ class Region(NamedTuple):
"""A pair of integers for the start and end lines (y coordinates) in this region.
The end value is *exclusive*.
"""
return (self.y, self.y + self.height)
@@ -441,7 +435,6 @@ class Region(NamedTuple):
Returns:
An offset.
"""
return Offset(*self[:2])
@@ -461,7 +454,6 @@ class Region(NamedTuple):
Returns:
An offset.
"""
x, y, width, _height = self
return Offset(x + width, y)
@@ -472,7 +464,6 @@ class Region(NamedTuple):
Returns:
An offset.
"""
x, y, width, height = self
return Offset(x + width, y + height)
@@ -504,7 +495,6 @@ class Region(NamedTuple):
Returns:
A region at the origin.
"""
_, _, width, height = self
return Region(0, 0, width, height)
@@ -894,7 +884,6 @@ class Spacing(NamedTuple):
>>> spacing.css
'1 2 3 4'
```
"""
top: int = 0

View File

@@ -34,7 +34,6 @@ class GridLayout(Layout):
Args:
column_count: Number of columns
"""
row = 0
while True:

View File

@@ -4,7 +4,6 @@ A Textual Logging handler.
If there is an active Textual app, then log messages will go via the app (and logged via textual console).
If there is *no* active app, then log messages will go to stderr or stdout, depending on configuration.
"""

View File

@@ -1,8 +1,6 @@
"""
The base class for all messages (including events).
"""
from __future__ import annotations

View File

@@ -1,7 +1,6 @@
"""
A message pump is a base class for any object which processes messages, which includes Widget, Screen, and App.
"""
from __future__ import annotations
@@ -47,7 +46,6 @@ class MessagePumpClosed(Exception):
class _MessagePumpMeta(type):
"""Metaclass for message pump. This exists to populate a Message inner class of a Widget with the
parent classes' name.
"""
def __new__(
@@ -122,7 +120,6 @@ class MessagePump(metaclass=_MessagePumpMeta):
with self.prevent(Input.Changed):
input.value = "foo"
```
"""
if message_types:
prevent_stack = self._prevent_message_types_stack
@@ -374,6 +371,7 @@ class MessagePump(metaclass=_MessagePumpMeta):
**kwargs: Keyword arguments to pass to the callable.
"""
self._next_callbacks.append(partial(callback, *args, **kwargs))
self.check_idle()
def _on_invoke_later(self, message: messages.InvokeLater) -> None:
# Forward InvokeLater message to the Screen
@@ -506,6 +504,7 @@ class MessagePump(metaclass=_MessagePumpMeta):
except Exception as error:
self.app._handle_exception(error)
break
await self._flush_next_callbacks()
async def _flush_next_callbacks(self) -> None:
"""Invoke pending callbacks in next callbacks queue."""
@@ -545,7 +544,6 @@ class MessagePump(metaclass=_MessagePumpMeta):
Args:
method_name: Handler method name.
message: Message object.
"""
private_method = f"_{method_name}"
for cls in self.__class__.__mro__:

View File

@@ -1,7 +1,6 @@
"""
The pilot object is used by [App.run_test][textual.app.App.run_test] to programmatically operate an app.
"""
from __future__ import annotations
@@ -63,7 +62,6 @@ class Pilot(Generic[ReturnType]):
Args:
*keys: Keys to press.
"""
if keys:
await self._app._press_keys(keys)
@@ -100,11 +98,11 @@ class Pilot(Generic[ReturnType]):
target_widget, offset, button=1, shift=shift, meta=meta, control=control
)
app.post_message(MouseDown(**message_arguments))
await self.pause()
await self.pause(0.1)
app.post_message(MouseUp(**message_arguments))
await self.pause()
await self.pause(0.1)
app.post_message(Click(**message_arguments))
await self.pause()
await self.pause(0.1)
async def hover(
self,

View File

@@ -1,7 +1,6 @@
"""
The `Reactive` class implements [reactivity](/guide/reactivity/).
"""
from __future__ import annotations
@@ -227,7 +226,6 @@ class Reactive(Generic[ReactiveType]):
watch_function: A watch function, which may be sync or async.
old_value: The old value of the attribute.
value: The new value of the attribute.
"""
_rich_traceback_omit = True
param_count = count_parameters(watch_function)

View File

@@ -57,7 +57,6 @@ class TextOpacity:
Returns:
Segments with applied opacity.
"""
_Segment = Segment
_from_color = Style.from_color

View File

@@ -38,7 +38,6 @@ class Tint:
Returns:
Segments with applied tint.
"""
from_rich_color = Color.from_rich_color
style_from_color = Style.from_color

View File

@@ -1,7 +1,6 @@
"""
The `Screen` class is a special widget which represents the content in the terminal. See [Screens](/guide/screens/) for details.
"""
from __future__ import annotations
@@ -444,15 +443,12 @@ class Screen(Generic[ScreenResultType], Widget):
or self._dirty_widgets
):
self._update_timer.resume()
return
# The Screen is idle - a good opportunity to invoke the scheduled callbacks
if self._callbacks:
self._on_timer_update()
await self._invoke_and_clear_callbacks()
def _on_timer_update(self) -> None:
"""Called by the _update_timer."""
self._update_timer.pause()
if self.is_current:
if self._layout_required:
@@ -465,12 +461,11 @@ class Screen(Generic[ScreenResultType], Widget):
self._scroll_required = False
if self._repaint_required:
self._update_timer.resume()
self._dirty_widgets.clear()
self._dirty_widgets.add(self)
self._repaint_required = False
if self._dirty_widgets and self.is_current:
if self._dirty_widgets:
self._compositor.update_widgets(self._dirty_widgets)
update = self._compositor.render_update(
screen_stack=self.app._background_screens
@@ -479,20 +474,12 @@ class Screen(Generic[ScreenResultType], Widget):
self._dirty_widgets.clear()
if self._callbacks:
self.post_message(events.InvokeCallbacks())
async def _on_invoke_callbacks(self, event: events.InvokeCallbacks) -> None:
"""Handle PostScreenUpdate events, which are sent after the screen is updated"""
await self._invoke_and_clear_callbacks()
self.call_next(self._invoke_and_clear_callbacks)
async def _invoke_and_clear_callbacks(self) -> None:
"""If there are scheduled callbacks to run, call them and clear
the callback queue."""
if self._callbacks:
display_update = self._compositor.render_update(
screen_stack=self.app._background_screens
)
self.app._display(self, display_update)
callbacks = self._callbacks[:]
self._callbacks.clear()
for callback in callbacks:
@@ -757,8 +744,6 @@ class ModalScreen(Screen[ScreenResultType]):
"""A screen with bindings that take precedence over the App's key bindings.
The default styling of a modal screen will dim the screen underneath.
"""
DEFAULT_CSS = """

View File

@@ -1,12 +1,12 @@
"""
`ScrollView` is a base class for [line api](/guide/widgets#line-api) widgets.
"""
from __future__ import annotations
from rich.console import RenderableType
from ._animator import EasingFunction
from .containers import ScrollableContainer
from .geometry import Size
@@ -15,7 +15,6 @@ class ScrollView(ScrollableContainer):
"""
A base class for a Widget that handles its own scrolling (i.e. doesn't rely
on the compositor to render children).
"""
DEFAULT_CSS = """
@@ -111,3 +110,37 @@ class ScrollView(ScrollableContainer):
from rich.panel import Panel
return Panel(f"{self.scroll_offset} {self.show_vertical_scrollbar}")
# Custom scroll to which doesn't require call_after_refresh
def scroll_to(
self,
x: float | None = None,
y: float | None = None,
*,
animate: bool = True,
speed: float | None = None,
duration: float | None = None,
easing: EasingFunction | str | None = None,
force: bool = False,
) -> None:
"""Scroll to a given (absolute) coordinate, optionally animating.
Args:
x: X coordinate (column) to scroll to, or `None` for no change.
y: Y coordinate (row) to scroll to, or `None` for no change.
animate: Animate to new scroll position.
speed: Speed of scroll if `animate` is `True`; or `None` to use `duration`.
duration: Duration of animation, if `animate` is `True` and `speed` is `None`.
easing: An easing method for the scrolling animation.
force: Force scrolling even when prohibited by overflow styling.
"""
self._scroll_to(
x,
y,
animate=animate,
speed=speed,
duration=duration,
easing=easing,
force=force,
)

View File

@@ -2,7 +2,6 @@
Timer objects are created by [set_interval][textual.message_pump.MessagePump.set_interval] or
[set_timer][textual.message_pump.MessagePump.set_timer].
"""
from __future__ import annotations
@@ -95,7 +94,6 @@ class Timer:
"""Pause the timer.
A paused timer will not send events until it is resumed.
"""
self._active.clear()
@@ -131,20 +129,18 @@ class Timer:
continue
now = _time.get_time()
wait_time = max(0, next_timer - now)
if wait_time > 1 / 1000:
await sleep(wait_time)
count += 1
try:
await self._tick(next_timer=next_timer, count=count)
except EventTargetGone:
break
await self._active.wait()
if self._reset:
start = _time.get_time()
count = 0
self._reset = False
continue
try:
await self._tick(next_timer=next_timer, count=count)
except EventTargetGone:
break
async def _tick(self, *, next_timer: float, count: int) -> None:
"""Triggers the Timer's action: either call its callback, or sends an event to its target"""

View File

@@ -4,7 +4,6 @@ Functions for *walking* the DOM.
!!! note
For most purposes you would be better off using [query][textual.dom.DOMNode.query], which uses these functions internally.
"""
from __future__ import annotations
@@ -57,7 +56,6 @@ def walk_depth_first(
Returns:
An iterable of DOMNodes, or the type specified in ``filter_type``.
"""
from textual.dom import DOMNode
@@ -118,7 +116,6 @@ def walk_breadth_first(
Returns:
An iterable of DOMNodes, or the type specified in ``filter_type``.
"""
from textual.dom import DOMNode

View File

@@ -1,6 +1,5 @@
"""
The base class for widgets.
"""
from __future__ import annotations
@@ -94,7 +93,6 @@ class AwaitMount:
```python
await self.mount(Static("foo"))
```
"""
def __init__(self, parent: Widget, widgets: Sequence[Widget]) -> None:
@@ -233,7 +231,6 @@ class Widget(DOMNode):
A Widget is the base class for Textual widgets.
See also [static][textual.widgets._static.Static] for starting point for your own widgets.
"""
DEFAULT_CSS = """
@@ -409,7 +406,6 @@ class Widget(DOMNode):
"""Check if vertical scroll is permitted.
May be overridden if you want different logic regarding allowing scrolling.
"""
return self.is_scrollable and self.show_vertical_scrollbar
@@ -418,7 +414,6 @@ class Widget(DOMNode):
"""Check if horizontal scroll is permitted.
May be overridden if you want different logic regarding allowing scrolling.
"""
return self.is_scrollable and self.show_horizontal_scrollbar
@@ -635,7 +630,6 @@ class Widget(DOMNode):
Returns:
An iterable of Widgets.
"""
if self._horizontal_scrollbar is not None:
yield self._horizontal_scrollbar
@@ -874,7 +868,6 @@ class Widget(DOMNode):
)
yield Footer()
```
"""
yield from ()
@@ -1134,7 +1127,6 @@ class Widget(DOMNode):
Returns:
ScrollBarCorner Widget.
"""
from .scrollbar import ScrollBarCorner
@@ -1248,7 +1240,6 @@ class Widget(DOMNode):
Returns:
A tuple of (<vertical scrollbar enabled>, <horizontal scrollbar enabled>)
"""
if not self.is_scrollable:
return False, False
@@ -1300,7 +1291,6 @@ class Widget(DOMNode):
Returns:
Additional spacing around content area.
"""
return self.styles.gutter + self.scrollbar_gutter
@@ -1359,7 +1349,6 @@ class Widget(DOMNode):
Returns:
Offset from widget's origin.
"""
x, y = self.gutter.top_left
return Offset(x, y)
@@ -1370,7 +1359,6 @@ class Widget(DOMNode):
Returns:
Content area size.
"""
return self.region.shrink(self.styles.gutter).size
@@ -1412,7 +1400,6 @@ class Widget(DOMNode):
Returns:
The virtual region.
"""
try:
return self.screen.find_widget(self).virtual_region
@@ -1461,7 +1448,6 @@ class Widget(DOMNode):
Returns:
List of widgets that can receive focus.
"""
focusable = [child for child in self._nodes if child.display and child.visible]
return sorted(focusable, key=attrgetter("_focus_sort_key"))
@@ -1493,7 +1479,6 @@ class Widget(DOMNode):
Returns:
A Rich console object.
"""
return active_app.get().console
@@ -1534,7 +1519,6 @@ class Widget(DOMNode):
delay: A delay (in seconds) before the animation starts.
easing: An easing method.
on_complete: A callable to invoke when the animation is finished.
"""
if self._animate is None:
self._animate = self.app.animator.bind(self)
@@ -1556,7 +1540,6 @@ class Widget(DOMNode):
Returns:
A layout object.
"""
return self.styles.layout or self._default_layout
@@ -1576,7 +1559,6 @@ class Widget(DOMNode):
Returns:
Name of layer.
"""
return self.styles.layer or "default"
@@ -1600,7 +1582,6 @@ class Widget(DOMNode):
Returns:
Rich style.
"""
styles = self.styles
_, background = self.background_colors
@@ -1622,7 +1603,6 @@ class Widget(DOMNode):
Returns:
Rich Style.
"""
styles = self.styles
_, background = self.background_colors
@@ -1646,7 +1626,6 @@ class Widget(DOMNode):
Args:
*regions: Regions which require a repaint.
"""
if regions:
content_offset = self.content_offset
@@ -2559,7 +2538,6 @@ class Widget(DOMNode):
Returns:
Tuples of scrollbar Widget and region.
"""
show_vertical_scrollbar, show_horizontal_scrollbar = self.scrollbars_enabled
@@ -2613,7 +2591,6 @@ class Widget(DOMNode):
Returns:
Names of the pseudo classes.
"""
node: MessagePump | None = self
while isinstance(node, Widget):

View File

@@ -143,7 +143,6 @@ class Button(Static, can_focus=True):
border-bottom: tall $error-lighten-2;
border-top: tall $error-darken-2;
}
"""
BINDINGS = [Binding("enter", "press", "Press Button", show=False)]

View File

@@ -130,7 +130,6 @@ class MarkdownH1(MarkdownHeader):
text-style: bold;
color: $text;
}
"""
@@ -148,7 +147,6 @@ class MarkdownH2(MarkdownHeader):
padding: 1;
text-style: bold;
}
"""
@@ -378,7 +376,6 @@ class MarkdownTable(MarkdownBlock):
background: $panel;
border: wide $background;
}
"""
def compose(self) -> ComposeResult:
@@ -830,7 +827,6 @@ class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True):
MarkdownViewer.-show-table-of-contents > MarkdownTableOfContents {
display: block;
}
"""
show_table_of_contents = reactive(True)

View File

@@ -69,7 +69,6 @@ class Placeholder(Widget):
Placeholder.-text {
padding: 1;
}
"""
# Consecutive placeholders get assigned consecutive colors.

View File

@@ -36,7 +36,6 @@ class TabPane(Widget):
"""A container for switchable content, with additional title.
This widget is intended to be used with [TabbedContent][textual.widgets.TabbedContent].
"""
DEFAULT_CSS = """

View File

@@ -42,7 +42,6 @@ class Underline(Widget):
| Class | Description |
| :- | :- |
| `underline-bar` | Style of the bar (may be used to change the color). |
"""
highlight_start = reactive(0)

View File

@@ -418,7 +418,6 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
Tree > .tree--highlight-line {
background: $boost;
}
"""
show_root = reactive(True)

View File

@@ -21,7 +21,6 @@ I will face my fear.
I will permit it to pass over me and through me.
And when it has gone past, I will turn the inner eye to see its path.
Where the fear has gone there will be nothing. Only I will remain."
"""

View File

@@ -1,6 +1,5 @@
"""
A class to manage concurrent [work](/guide/workers).
"""
from __future__ import annotations
@@ -204,7 +203,6 @@ class Worker(Generic[ResultType]):
"""Has the work been cancelled?
Note that cancelled work may still be running.
"""
return self._cancelled
@@ -237,7 +235,6 @@ class Worker(Generic[ResultType]):
"""Progress as a percentage.
If the total steps is None, then this will return 0. The percentage will be clamped between 0 and 100.
"""
if not self._total_steps:
return 0.0
@@ -267,7 +264,6 @@ class Worker(Generic[ResultType]):
Args:
steps: Number of steps to advance.
"""
self._completed_steps += steps
@@ -278,7 +274,6 @@ class Worker(Generic[ResultType]):
Returns:
Return value of work.
"""
if (

View File

@@ -29,7 +29,6 @@ class ScrollViewTester(App[None]):
background: $primary-darken-2;
border: round red;
}
"""
def compose(self) -> ComposeResult:

View File

@@ -9,7 +9,6 @@ class AnimApp(App):
#foo {
height: 1;
}
"""
def compose(self) -> ComposeResult:

View File

@@ -39,4 +39,4 @@ async def test_call_after_refresh() -> None:
app.call_after_refresh(callback)
await asyncio.wait_for(called_event.wait(), 1)
app_display_count = app.display_count
assert app_display_count > display_count
assert app_display_count == display_count

View File

@@ -12,7 +12,6 @@ re_link_ids = re.compile(r"id=[\d\.\-]*?;.*?\x1b")
def replace_link_ids(render: str) -> str:
"""Link IDs have a random ID and system path which is a problem for
reproducible tests.
"""
return re_link_ids.sub("id=0;foo\x1b", render)