Improve documentation.

This commit is contained in:
Rodrigo Girão Serrão
2023-11-22 15:50:20 +00:00
parent 896aa9f924
commit b902b1cae6
21 changed files with 102 additions and 45 deletions

View File

@@ -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

View File

@@ -0,0 +1 @@
::: textual.await_complete

View File

@@ -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

View File

@@ -69,6 +69,6 @@ The tree widget provides the following component classes:
---
::: textual.widgets.tree.TreeNode
::: textual.widgets.tree
options:
heading_level: 2

View File

@@ -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"

View File

@@ -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)

View File

@@ -1,5 +1,4 @@
"""
An *optionally* awaitable object returned by methods that remove widgets.
"""

View File

@@ -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

View File

@@ -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",

View File

@@ -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."""

View File

@@ -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."""

View File

@@ -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)

View File

@@ -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:

View File

@@ -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].
"""

View File

@@ -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."""

View File

@@ -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

View File

@@ -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.

View File

@@ -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",
]

View File

@@ -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",
]

View File

@@ -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()

View File

@@ -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))