code browser and fixes

This commit is contained in:
Will McGugan
2022-09-08 11:00:05 +01:00
parent f1f7a620af
commit afd22265f4
10 changed files with 120 additions and 31 deletions

26
examples/code_browser.css Normal file
View File

@@ -0,0 +1,26 @@
#tree-view {
display: none;
scrollbar-gutter: stable;
}
CodeBrowser.-show-tree #tree-view {
display: block;
dock: left;
height: 100%;
width: auto;
background: $surface;
}
CodeBrowser{
background: $surface-darken-1;
}
DirectoryTree {
padding-right: 1;
}
#code {
width: auto;
}

49
examples/code_browser.py Normal file
View File

@@ -0,0 +1,49 @@
import sys
from rich.syntax import Syntax
from rich.traceback import Traceback
from textual.app import App, ComposeResult
from textual.layout import Container, Vertical
from textual.reactive import Reactive
from textual.widgets import DirectoryTree, Footer, Header, Static
class CodeBrowser(App):
show_tree = Reactive.init(True)
def watch_show_tree(self, show_tree: bool) -> None:
self.set_class(show_tree, "-show-tree")
def on_load(self) -> None:
self.bind("t", "toggle_tree", description="Toggle Tree")
self.bind("q", "quit", description="Quit")
def compose(self) -> ComposeResult:
path = "./" if len(sys.argv) < 2 else sys.argv[1]
yield Header()
yield Container(
Vertical(DirectoryTree(path), id="tree-view"),
Vertical(Static(id="code"), id="code-view"),
)
yield Footer()
def on_directory_tree_file_click(self, event: DirectoryTree.FileClick) -> None:
code_view = self.query_one("#code", Static)
try:
syntax = Syntax.from_path(event.path, line_numbers=True, word_wrap=True)
except Exception:
code_view.update(Traceback())
self.sub_title = "ERROR"
else:
code_view.update(syntax)
self.query_one("#code-view").scroll_home(animate=False)
self.sub_title = event.path
def action_toggle_tree(self) -> None:
self.show_tree = not self.show_tree
app = CodeBrowser(css_path="code_browser.css")
if __name__ == "__main__":
app.run()

View File

@@ -65,9 +65,14 @@ class Layout(ABC):
int: Width of the content. int: Width of the content.
""" """
width: int | None = None width: int | None = None
widget_gutter = widget.gutter.width
for child in widget.displayed_children: for child in widget.displayed_children:
if not child.is_container: if not child.is_container:
child_width = child.get_content_width(container, viewport) child_width = (
child.get_content_width(container, viewport)
+ widget_gutter
+ child.gutter.width
)
width = child_width if width is None else max(width, child_width) width = child_width if width is None else max(width, child_width)
if width is None: if width is None:
width = container.width width = container.width

View File

@@ -544,7 +544,7 @@ class DOMNode(MessagePump):
return nodes return nodes
@property @property
def displayed_children(self) -> list[DOMNode]: def displayed_children(self) -> list[Widget]:
"""The children which don't have display: none set. """The children which don't have display: none set.
Returns: Returns:

View File

@@ -8,6 +8,7 @@ class Container(Widget):
Container { Container {
layout: vertical; layout: vertical;
overflow: auto; overflow: auto;
} }
""" """

View File

@@ -7,8 +7,13 @@ from operator import attrgetter
from typing import TYPE_CHECKING, ClassVar, Collection, Iterable, NamedTuple from typing import TYPE_CHECKING, ClassVar, Collection, Iterable, NamedTuple
import rich.repr import rich.repr
from rich.console import Console, JustifyMethod, RenderableType from rich.console import (
from rich.measure import Measurement Console,
ConsoleRenderable,
Measurement,
JustifyMethod,
RenderableType,
)
from rich.segment import Segment from rich.segment import Segment
from rich.style import Style from rich.style import Style
from rich.styled import Styled from rich.styled import Styled
@@ -313,17 +318,15 @@ class Widget(DOMNode):
""" """
if self.is_container: if self.is_container:
assert self._layout is not None assert self._layout is not None
return ( return self._layout.get_content_width(self, container, viewport)
self._layout.get_content_width(self, container, viewport)
+ self.scrollbar_size_vertical
)
cache_key = container.width cache_key = container.width
if self._content_width_cache[0] == cache_key: if self._content_width_cache[0] == cache_key:
return self._content_width_cache[1] return self._content_width_cache[1]
console = self.app.console console = self.app.console
renderable = self.render() renderable = self.post_render(self.render())
measurement = Measurement.get( measurement = Measurement.get(
console, console,
console.options.update_width(container.width), console.options.update_width(container.width),
@@ -509,18 +512,18 @@ class Widget(DOMNode):
@property @property
def scrollbar_size_vertical(self) -> int: def scrollbar_size_vertical(self) -> int:
"""Get the width used by the *vertical* scrollbar.""" """Get the width used by the *vertical* scrollbar."""
return ( styles = self.styles
self.styles.scrollbar_size_vertical if self.show_vertical_scrollbar else 0 if styles.scrollbar_gutter == "stable" and styles.overflow_y == "auto":
) return styles.scrollbar_size_vertical
return styles.scrollbar_size_vertical if self.show_vertical_scrollbar else 0
@property @property
def scrollbar_size_horizontal(self) -> int: def scrollbar_size_horizontal(self) -> int:
"""Get the height used by the *horizontal* scrollbar.""" """Get the height used by the *horizontal* scrollbar."""
return ( styles = self.styles
self.styles.scrollbar_size_horizontal if styles.scrollbar_gutter == "stable" and styles.overflow_x == "auto":
if self.show_horizontal_scrollbar return self.styles.scrollbar_size_horizontal
else 0 return styles.scrollbar_size_horizontal if self.show_horizontal_scrollbar else 0
)
@property @property
def scrollbar_gutter(self) -> Spacing: def scrollbar_gutter(self) -> Spacing:
@@ -1214,7 +1217,7 @@ class Widget(DOMNode):
if self.descendant_has_focus: if self.descendant_has_focus:
yield "focus-within" yield "focus-within"
def post_render(self, renderable: RenderableType) -> RenderableType: def post_render(self, renderable: RenderableType) -> ConsoleRenderable:
"""Applies style attributes to the default renderable. """Applies style attributes to the default renderable.
Returns: Returns:

View File

@@ -21,14 +21,13 @@ class DirEntry:
is_dir: bool is_dir: bool
@rich.repr.auto
class FileClick(Message, bubble=True):
def __init__(self, sender: MessageTarget, path: str) -> None:
self.path = path
super().__init__(sender)
class DirectoryTree(TreeControl[DirEntry]): class DirectoryTree(TreeControl[DirEntry]):
@rich.repr.auto
class FileClick(Message, bubble=True):
def __init__(self, sender: MessageTarget, path: str) -> None:
self.path = path
super().__init__(sender)
def __init__( def __init__(
self, self,
path: str, path: str,
@@ -112,7 +111,7 @@ class DirectoryTree(TreeControl[DirEntry]):
) -> None: ) -> None:
dir_entry = message.node.data dir_entry = message.node.data
if not dir_entry.is_dir: if not dir_entry.is_dir:
await self.emit(FileClick(self, dir_entry.path)) await self.emit(self.FileClick(self, dir_entry.path))
else: else:
if not message.node.loaded: if not message.node.loaded:
await self.load_directory(message.node) await self.load_directory(message.node)

View File

@@ -63,7 +63,8 @@ class HeaderTitle(Widget):
def render(self) -> Text: def render(self) -> Text:
text = Text(self.text, no_wrap=True, overflow="ellipsis") text = Text(self.text, no_wrap=True, overflow="ellipsis")
if self.sub_text: if self.sub_text:
text.append(f" - {self.sub_text}", "dim") text.append(" ")
text.append(self.sub_text, "dim")
return text return text
@@ -83,8 +84,13 @@ class Header(Widget):
} }
""" """
tall = Reactive(True)
DEFAULT_CLASSES = "tall" DEFAULT_CLASSES = "tall"
def watch_tall(self, tall: bool) -> None:
self.set_class(tall, "tall")
async def on_click(self, event): async def on_click(self, event):
self.toggle_class("tall") self.toggle_class("tall")

View File

@@ -1,9 +1,9 @@
from __future__ import annotations from __future__ import annotations
from rich.console import RenderableType from rich.console import RenderableType
from rich.protocol import is_renderable from rich.protocol import is_renderable
from ..reactive import Reactive
from ..errors import RenderError from ..errors import RenderError
from ..widget import Widget from ..widget import Widget
@@ -49,4 +49,4 @@ class Static(Widget):
def update(self, renderable: RenderableType) -> None: def update(self, renderable: RenderableType) -> None:
_check_renderable(renderable) _check_renderable(renderable)
self.renderable = renderable self.renderable = renderable
self.refresh() self.refresh(layout=True)

View File

@@ -13,7 +13,7 @@ from ..geometry import Region, Size
from .. import events from .. import events
from ..reactive import Reactive from ..reactive import Reactive
from .._types import MessageTarget from .._types import MessageTarget
from ..widget import Widget from ..widgets import Static
from ..message import Message from ..message import Message
from .. import messages from .. import messages
@@ -161,7 +161,7 @@ class TreeNode(Generic[NodeDataType]):
return self._control.render_node(self) return self._control.render_node(self)
class TreeControl(Generic[NodeDataType], Widget, can_focus=True): class TreeControl(Generic[NodeDataType], Static, can_focus=True):
DEFAULT_CSS = """ DEFAULT_CSS = """
TreeControl { TreeControl {
background: $surface; background: $surface;