ai-station/.venv/lib/python3.12/site-packages/textual/walk.py

170 lines
4.5 KiB
Python

"""
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
from collections import deque
from typing import TYPE_CHECKING, Iterable, Iterator, TypeVar, overload
if TYPE_CHECKING:
from textual.dom import DOMNode
WalkType = TypeVar("WalkType", bound=DOMNode)
if TYPE_CHECKING:
@overload
def walk_depth_first(
root: DOMNode,
*,
with_root: bool = True,
) -> Iterable[DOMNode]: ...
@overload
def walk_depth_first(
root: WalkType,
filter_type: type[WalkType],
*,
with_root: bool = True,
) -> Iterable[WalkType]: ...
def walk_depth_first(
root: DOMNode,
filter_type: type[WalkType] | None = None,
*,
with_root: bool = True,
) -> Iterable[DOMNode] | Iterable[WalkType]:
"""Walk the tree depth first (parents first).
!!! note
Avoid changing the DOM (mounting, removing etc.) while iterating with this function.
Consider [walk_children][textual.dom.DOMNode.walk_children] which doesn't have this limitation.
Args:
root: The root note (starting point).
filter_type: Optional DOMNode subclass to filter by, or `None` for no filter.
with_root: Include the root in the walk.
Returns:
An iterable of DOMNodes, or the type specified in `filter_type`.
"""
stack: list[Iterator[DOMNode]] = [iter(root.children)]
pop = stack.pop
push = stack.append
if filter_type is None:
if with_root:
yield root
while stack:
if (node := next(stack[-1], None)) is None:
pop()
else:
yield node
if children := node._nodes:
push(iter(children))
else:
if with_root and isinstance(root, filter_type):
yield root
while stack:
if (node := next(stack[-1], None)) is None:
pop()
else:
if isinstance(node, filter_type):
yield node
if children := node._nodes:
push(iter(children))
if TYPE_CHECKING:
@overload
def walk_breadth_first(
root: DOMNode,
*,
with_root: bool = True,
) -> Iterable[DOMNode]: ...
@overload
def walk_breadth_first(
root: WalkType,
filter_type: type[WalkType],
*,
with_root: bool = True,
) -> Iterable[WalkType]: ...
def walk_breadth_first(
root: DOMNode,
filter_type: type[WalkType] | None = None,
*,
with_root: bool = True,
) -> Iterable[DOMNode] | Iterable[WalkType]:
"""Walk the tree breadth first (children first).
!!! note
Avoid changing the DOM (mounting, removing etc.) while iterating with this function.
Consider [walk_children][textual.dom.DOMNode.walk_children] which doesn't have this limitation.
Args:
root: The root note (starting point).
filter_type: Optional DOMNode subclass to filter by, or `None` for no filter.
with_root: Include the root in the walk.
Returns:
An iterable of DOMNodes, or the type specified in `filter_type`.
"""
from textual.dom import DOMNode
queue: deque[DOMNode] = deque()
popleft = queue.popleft
extend = queue.extend
check_type = filter_type or DOMNode
if with_root and isinstance(root, check_type):
yield root
extend(root.children)
while queue:
node = popleft()
if isinstance(node, check_type):
yield node
extend(node._nodes)
def walk_breadth_search_id(
root: DOMNode, node_id: str, *, with_root: bool = True
) -> DOMNode | None:
"""Special case to walk breadth first searching for a node with a given id.
This is more efficient than [walk_breadth_first][textual.walk.walk_breadth_first] for this special case, as it can use an index.
Args:
root: The root node (starting point).
node_id: Node id to search for.
with_root: Consider the root node? If the root has the node id, then return it.
Returns:
A DOMNode if a node was found, otherwise `None`.
"""
if with_root and root.id == node_id:
return root
queue: deque[DOMNode] = deque()
queue.append(root)
while queue:
node = queue.popleft()
if (found_node := node._nodes._get_by_id(node_id)) is not None:
return found_node
queue.extend(node._nodes)
return None