mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Improve documentation.
This commit is contained in:
@@ -46,6 +46,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Rich markup in markdown headings is now escaped when building the TOC https://github.com/Textualize/textual/issues/3689
|
||||
- Mechanics behind mouse clicks. See [this](https://github.com/Textualize/textual/pull/3495#issue-1934915047) for more details. https://github.com/Textualize/textual/pull/3495
|
||||
- Breaking change: max/min-width/height now includes padding and border. https://github.com/Textualize/textual/pull/3712
|
||||
- Method `MarkdownTableOfContents.set_table_of_contents` renamed to `MarkdownTableOfContents.rebuild_table_of_contents` https://github.com/Textualize/textual/pull/3730
|
||||
- Exception `Tree.UnknownNodeID` moved out of `Tree`, import from `textual.widgets.tree` https://github.com/Textualize/textual/pull/3730
|
||||
- Exception `TreeNode.RemoveRootError` moved out of `TreeNode`, import from `textual.widgets.tree` https://github.com/Textualize/textual/pull/3730
|
||||
|
||||
|
||||
## [0.41.0] - 2023-10-31
|
||||
|
||||
1
docs/api/await_complete.md
Normal file
1
docs/api/await_complete.md
Normal file
@@ -0,0 +1 @@
|
||||
::: textual.await_complete
|
||||
@@ -57,3 +57,9 @@ This widget has no component classes.
|
||||
::: textual.widgets.MarkdownViewer
|
||||
options:
|
||||
heading_level: 2
|
||||
|
||||
|
||||
::: textual.widgets.markdown
|
||||
options:
|
||||
show_root_heading: true
|
||||
show_root_toc_entry: true
|
||||
|
||||
@@ -69,6 +69,6 @@ The tree widget provides the following component classes:
|
||||
|
||||
---
|
||||
|
||||
::: textual.widgets.tree.TreeNode
|
||||
::: textual.widgets.tree
|
||||
options:
|
||||
heading_level: 2
|
||||
|
||||
@@ -169,6 +169,7 @@ nav:
|
||||
- API:
|
||||
- "api/index.md"
|
||||
- "api/app.md"
|
||||
- "api/await_complete.md"
|
||||
- "api/await_remove.md"
|
||||
- "api/binding.md"
|
||||
- "api/color.md"
|
||||
|
||||
@@ -110,7 +110,7 @@ class TextAreaTheme:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_builtin_theme(cls, theme_name: str) -> "TextAreaTheme" | None:
|
||||
def get_builtin_theme(cls, theme_name: str) -> TextAreaTheme | None:
|
||||
"""Get a `TextAreaTheme` by name.
|
||||
|
||||
Given a `theme_name`, return the corresponding `TextAreaTheme` object.
|
||||
@@ -120,7 +120,7 @@ class TextAreaTheme:
|
||||
|
||||
Returns:
|
||||
The `TextAreaTheme` corresponding to the name or `None` if the theme isn't
|
||||
found.
|
||||
found.
|
||||
"""
|
||||
return _BUILTIN_THEMES.get(theme_name)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""
|
||||
|
||||
An *optionally* awaitable object returned by methods that remove widgets.
|
||||
"""
|
||||
|
||||
|
||||
@@ -133,10 +133,10 @@ class DocumentBase(ABC):
|
||||
|
||||
def query_syntax_tree(
|
||||
self,
|
||||
query: "Query",
|
||||
query: Query,
|
||||
start_point: tuple[int, int] | None = None,
|
||||
end_point: tuple[int, int] | None = None,
|
||||
) -> list[tuple["Node", str]]:
|
||||
) -> list[tuple[Node, str]]:
|
||||
"""Query the tree-sitter syntax tree.
|
||||
|
||||
The default implementation always returns an empty list.
|
||||
@@ -153,7 +153,7 @@ class DocumentBase(ABC):
|
||||
"""
|
||||
return []
|
||||
|
||||
def prepare_query(self, query: str) -> "Query" | None:
|
||||
def prepare_query(self, query: str) -> Query | None:
|
||||
return None
|
||||
|
||||
@property
|
||||
|
||||
@@ -14,8 +14,15 @@ from ._types import (
|
||||
)
|
||||
from .actions import ActionParseResult
|
||||
from .css.styles import RenderStyles
|
||||
from .widgets._data_table import CursorType
|
||||
from .widgets._directory_tree import DirEntry
|
||||
from .widgets._input import InputValidationOn
|
||||
from .widgets._option_list import (
|
||||
DuplicateID,
|
||||
NewOptionListContent,
|
||||
OptionDoesNotExist,
|
||||
OptionListContent,
|
||||
)
|
||||
from .widgets._placeholder import PlaceholderVariant
|
||||
from .widgets._select import NoSelection, SelectType
|
||||
|
||||
__all__ = [
|
||||
@@ -24,13 +31,18 @@ __all__ = [
|
||||
"CallbackType",
|
||||
"CSSPathError",
|
||||
"CSSPathType",
|
||||
"CursorType",
|
||||
"DirEntry",
|
||||
"DuplicateID",
|
||||
"EasingFunction",
|
||||
"IgnoreReturnCallbackType",
|
||||
"InputValidationOn",
|
||||
"MessageTarget",
|
||||
"NewOptionListContent",
|
||||
"NoActiveAppError",
|
||||
"NoSelection",
|
||||
"OptionDoesNotExist",
|
||||
"OptionListContent",
|
||||
"PlaceholderVariant",
|
||||
"RenderStyles",
|
||||
"SelectType",
|
||||
"UnusedParameter",
|
||||
|
||||
@@ -40,6 +40,7 @@ RowCacheKey: TypeAlias = "tuple[RowKey, int, Style, Coordinate, Coordinate, Curs
|
||||
CursorType = Literal["cell", "row", "column", "none"]
|
||||
"""The valid types of cursors for [`DataTable.cursor_type`][textual.widgets.DataTable.cursor_type]."""
|
||||
CellType = TypeVar("CellType")
|
||||
"""Type used for cells in the DataTable."""
|
||||
|
||||
_DEFAULT_CELL_X_PADDING = 1
|
||||
"""Default padding to use on each side of a column in the data table."""
|
||||
|
||||
@@ -22,7 +22,7 @@ from ._tree import TOGGLE_STYLE, Tree, TreeNode
|
||||
|
||||
@dataclass
|
||||
class DirEntry:
|
||||
"""Attaches directory information to a node."""
|
||||
"""Attaches directory information to a [DirectoryTree][textual.widgets.DirectoryTree] node."""
|
||||
|
||||
path: Path
|
||||
"""The path of the directory entry."""
|
||||
|
||||
@@ -71,7 +71,7 @@ class LoadingIndicator(Widget):
|
||||
widget: A widget.
|
||||
|
||||
Returns:
|
||||
AwaitMount: An awaitable for mounting the indicator.
|
||||
An awaitable for mounting the indicator.
|
||||
"""
|
||||
self.add_class("-overlay")
|
||||
await_mount = widget.mount(self)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path, PurePath
|
||||
from typing import Callable, Iterable
|
||||
from typing import Callable, Iterable, Optional
|
||||
|
||||
from markdown_it import MarkdownIt
|
||||
from markdown_it.token import Token
|
||||
@@ -22,6 +22,10 @@ from ..widget import AwaitMount, Widget
|
||||
from ..widgets import Static, Tree
|
||||
|
||||
TableOfContentsType: TypeAlias = "list[tuple[int, str, str | None]]"
|
||||
"""Information about the table of contents of a markdown document.
|
||||
|
||||
The triples encode the level, the label, and the optional block id of each heading.
|
||||
"""
|
||||
|
||||
|
||||
class Navigator:
|
||||
@@ -709,7 +713,7 @@ class Markdown(Widget):
|
||||
"""Process an unhandled token.
|
||||
|
||||
Args:
|
||||
token: The token to handle.
|
||||
token: The MarkdownIt token to handle.
|
||||
|
||||
Returns:
|
||||
Either a widget to be added to the output, or `None`.
|
||||
@@ -872,6 +876,8 @@ class Markdown(Widget):
|
||||
|
||||
|
||||
class MarkdownTableOfContents(Widget, can_focus_children=True):
|
||||
"""Displays a table of contents for a markdown document."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
MarkdownTableOfContents {
|
||||
width: auto;
|
||||
@@ -884,7 +890,8 @@ class MarkdownTableOfContents(Widget, can_focus_children=True):
|
||||
}
|
||||
"""
|
||||
|
||||
table_of_contents = reactive["TableOfContentsType | None"](None, init=False)
|
||||
table_of_contents = reactive[Optional[TableOfContentsType]](None, init=False)
|
||||
"""Underlying data to populate the table of contents widget."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -903,7 +910,7 @@ class MarkdownTableOfContents(Widget, can_focus_children=True):
|
||||
classes: The CSS classes for the widget.
|
||||
disabled: Whether the widget is disabled or not.
|
||||
"""
|
||||
self.markdown = markdown
|
||||
self.markdown: Markdown = markdown
|
||||
"""The Markdown document associated with this table of contents."""
|
||||
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
|
||||
|
||||
@@ -917,10 +924,10 @@ class MarkdownTableOfContents(Widget, can_focus_children=True):
|
||||
|
||||
def watch_table_of_contents(self, table_of_contents: TableOfContentsType) -> None:
|
||||
"""Triggered when the table of contents changes."""
|
||||
self.set_table_of_contents(table_of_contents)
|
||||
self.rebuild_table_of_contents(table_of_contents)
|
||||
|
||||
def set_table_of_contents(self, table_of_contents: TableOfContentsType) -> None:
|
||||
"""Set the table of contents.
|
||||
def rebuild_table_of_contents(self, table_of_contents: TableOfContentsType) -> None:
|
||||
"""Rebuilds the tree representation of the table of contents data.
|
||||
|
||||
Args:
|
||||
table_of_contents: Table of contents.
|
||||
@@ -1005,12 +1012,12 @@ class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True):
|
||||
|
||||
@property
|
||||
def document(self) -> Markdown:
|
||||
"""The Markdown document object."""
|
||||
"""The [Markdown][textual.widgets.Markdown] document widget."""
|
||||
return self.query_one(Markdown)
|
||||
|
||||
@property
|
||||
def table_of_contents(self) -> MarkdownTableOfContents:
|
||||
"""The table of contents widget"""
|
||||
"""The [table of contents][textual.widgets.markdown.MarkdownTableOfContents] widget."""
|
||||
return self.query_one(MarkdownTableOfContents)
|
||||
|
||||
def _on_mount(self, _: Mount) -> None:
|
||||
|
||||
@@ -26,11 +26,11 @@ from ..strip import Strip
|
||||
|
||||
|
||||
class DuplicateID(Exception):
|
||||
"""Exception raised if a duplicate ID is used."""
|
||||
"""Raised if a duplicate ID is used when adding options to an option list."""
|
||||
|
||||
|
||||
class OptionDoesNotExist(Exception):
|
||||
"""Exception raised when a request has been made for an option that doesn't exist."""
|
||||
"""Raised when a request has been made for an option that doesn't exist."""
|
||||
|
||||
|
||||
class Option:
|
||||
@@ -126,7 +126,7 @@ NewOptionListContent: TypeAlias = "OptionListContent | None | RenderableType"
|
||||
"""The type of a new item of option list content to be added to an option list.
|
||||
|
||||
This type represents all of the types that will be accepted when adding new
|
||||
content to the option list. This is a superset of `OptionListContent`.
|
||||
content to the option list. This is a superset of [`OptionListContent`][textual.types.OptionListContent].
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -3,19 +3,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import cycle
|
||||
from typing import Iterator
|
||||
from typing import TYPE_CHECKING, Iterator
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from rich.console import RenderableType
|
||||
from typing_extensions import Literal, Self
|
||||
|
||||
from textual.app import App
|
||||
|
||||
from .. import events
|
||||
from ..css._error_tools import friendly_list
|
||||
from ..reactive import Reactive, reactive
|
||||
from ..widget import Widget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from textual.app import App
|
||||
|
||||
PlaceholderVariant = Literal["default", "size", "text"]
|
||||
"""The different variants of placeholder."""
|
||||
|
||||
|
||||
@@ -306,7 +306,7 @@ class TabbedContent(Widget):
|
||||
|
||||
Note:
|
||||
Only one of `before` or `after` can be provided. If both are
|
||||
provided a `Tabs.TabError` will be raised.
|
||||
provided an exception is raised.
|
||||
"""
|
||||
if isinstance(before, TabPane):
|
||||
before = before.id
|
||||
|
||||
@@ -42,6 +42,14 @@ LineCacheKey: TypeAlias = "tuple[int | tuple, ...]"
|
||||
TOGGLE_STYLE = Style.from_meta({"toggle": True})
|
||||
|
||||
|
||||
class RemoveRootError(Exception):
|
||||
"""Exception raised when trying to remove the root of a [`TreeNode`][textual.widgets.tree.TreeNode]."""
|
||||
|
||||
|
||||
class UnknownNodeID(Exception):
|
||||
"""Exception raised when referring to an unknown [`TreeNode`][textual.widgets.tree.TreeNode] ID."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class _TreeLine(Generic[TreeDataType]):
|
||||
path: list[TreeNode[TreeDataType]]
|
||||
@@ -352,9 +360,6 @@ class TreeNode(Generic[TreeDataType]):
|
||||
node = self.add(label, data, expand=False, allow_expand=False)
|
||||
return node
|
||||
|
||||
class RemoveRootError(Exception):
|
||||
"""Exception raised when trying to remove a tree's root node."""
|
||||
|
||||
def _remove_children(self) -> None:
|
||||
"""Remove child nodes of this node.
|
||||
|
||||
@@ -381,10 +386,10 @@ class TreeNode(Generic[TreeDataType]):
|
||||
"""Remove this node from the tree.
|
||||
|
||||
Raises:
|
||||
TreeNode.RemoveRootError: If there is an attempt to remove the root.
|
||||
RemoveRootError: If there is an attempt to remove the root.
|
||||
"""
|
||||
if self.is_root:
|
||||
raise self.RemoveRootError("Attempt to remove the root node of a Tree.")
|
||||
raise RemoveRootError("Attempt to remove the root node of a Tree.")
|
||||
self._remove()
|
||||
self._tree._invalidate()
|
||||
|
||||
@@ -758,9 +763,6 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
else:
|
||||
return line.node
|
||||
|
||||
class UnknownNodeID(Exception):
|
||||
"""Exception raised when referring to an unknown `TreeNode` ID."""
|
||||
|
||||
def get_node_by_id(self, node_id: NodeID) -> TreeNode[TreeDataType]:
|
||||
"""Get a tree node by its ID.
|
||||
|
||||
@@ -771,12 +773,12 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
The node associated with that ID.
|
||||
|
||||
Raises:
|
||||
Tree.UnknownID: Raised if the `TreeNode` ID is unknown.
|
||||
UnknownNodeID: Raised if the `TreeNode` ID is unknown.
|
||||
"""
|
||||
try:
|
||||
return self._tree_nodes[node_id]
|
||||
except KeyError:
|
||||
raise self.UnknownNodeID(f"Unknown NodeID ({node_id}) in tree") from None
|
||||
raise UnknownNodeID(f"Unknown NodeID ({node_id}) in tree") from None
|
||||
|
||||
def validate_cursor_line(self, value: int) -> int:
|
||||
"""Prevent cursor line from going outside of range.
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
from ._markdown import Markdown, MarkdownBlock, MarkdownTableOfContents
|
||||
from ._markdown import (
|
||||
Markdown,
|
||||
MarkdownBlock,
|
||||
MarkdownTableOfContents,
|
||||
TableOfContentsType,
|
||||
)
|
||||
|
||||
__all__ = ["MarkdownTableOfContents", "Markdown", "MarkdownBlock"]
|
||||
__all__ = [
|
||||
"MarkdownTableOfContents",
|
||||
"Markdown",
|
||||
"MarkdownBlock",
|
||||
"TableOfContentsType",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
"""Make non-widget Tree support classes available."""
|
||||
|
||||
from ._tree import TreeNode
|
||||
from ._tree import (
|
||||
EventTreeDataType,
|
||||
NodeID,
|
||||
RemoveRootError,
|
||||
TreeDataType,
|
||||
TreeNode,
|
||||
UnknownNodeID,
|
||||
)
|
||||
|
||||
__all__ = ["TreeNode"]
|
||||
__all__ = [
|
||||
"EventTreeDataType",
|
||||
"NodeID",
|
||||
"RemoveRootError",
|
||||
"TreeDataType",
|
||||
"TreeNode",
|
||||
"UnknownNodeID",
|
||||
]
|
||||
|
||||
@@ -4,7 +4,7 @@ import pytest
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Tree
|
||||
from textual.widgets.tree import TreeNode
|
||||
from textual.widgets.tree import RemoveRootError
|
||||
|
||||
|
||||
class VerseBody:
|
||||
@@ -106,5 +106,5 @@ async def test_tree_remove_children_of_root():
|
||||
async def test_attempt_to_remove_root():
|
||||
"""Attempting to remove the root should be an error."""
|
||||
async with TreeClearApp().run_test() as pilot:
|
||||
with pytest.raises(TreeNode.RemoveRootError):
|
||||
with pytest.raises(RemoveRootError):
|
||||
pilot.app.query_one(VerseTree).root.remove()
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import cast
|
||||
import pytest
|
||||
|
||||
from textual.widgets import Tree
|
||||
from textual.widgets._tree import NodeID
|
||||
from textual.widgets.tree import NodeID, UnknownNodeID
|
||||
|
||||
|
||||
def test_get_tree_node_by_id() -> None:
|
||||
@@ -14,5 +14,5 @@ def test_get_tree_node_by_id() -> None:
|
||||
assert tree.get_node_by_id(tree.root.id).id == tree.root.id
|
||||
assert tree.get_node_by_id(child.id).id == child.id
|
||||
assert tree.get_node_by_id(grandchild.id).id == grandchild.id
|
||||
with pytest.raises(Tree.UnknownNodeID):
|
||||
with pytest.raises(UnknownNodeID):
|
||||
tree.get_node_by_id(cast(NodeID, grandchild.id + 1000))
|
||||
|
||||
Reference in New Issue
Block a user