From 16931a8563cf826e3a8603abbeb2f3b4307cc7ba Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 2 May 2023 14:19:55 +0100 Subject: [PATCH 01/18] Move DirectoryTree.FileSelected.path's doc to inline style This brings it in line with the documentation for other widgets and their messages. --- src/textual/widgets/_directory_tree.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 64e52d38a..e11d05c44 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -73,14 +73,12 @@ class DirectoryTree(Tree[DirEntry]): Can be handled using `on_directory_tree_file_selected` in a subclass of `DirectoryTree` or in a parent widget in the DOM. - - Attributes: - path: The path of the file that was selected. """ def __init__(self, path: str) -> None: - self.path: str = path super().__init__() + self.path: str = path + """The path of the file that was selected.""" def __init__( self, From d0639a0cc179300b7a22221ad7082bfe712073de Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 2 May 2023 15:29:36 +0100 Subject: [PATCH 02/18] Correct the type of the TreeExpanded event This looks to have been a slight typing bug all along. --- src/textual/widgets/_directory_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index e11d05c44..963cce70a 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -182,7 +182,7 @@ class DirectoryTree(Tree[DirEntry]): def _on_mount(self, _: Mount) -> None: self.load_directory(self.root) - def _on_tree_node_expanded(self, event: Tree.NodeSelected) -> None: + def _on_tree_node_expanded(self, event: Tree.NodeExpanded) -> None: event.stop() dir_entry = event.node.data if dir_entry is None: From 83c09b3cfd1b9cc4997df726bd8ae4178e71be30 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 2 May 2023 15:51:18 +0100 Subject: [PATCH 03/18] Add a missing type hint Looks like this has been missing since DirectoryTree was first written. --- src/textual/widgets/_directory_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 963cce70a..d5ad73eb4 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -100,7 +100,7 @@ class DirectoryTree(Tree[DirEntry]): disabled=disabled, ) - def process_label(self, label: TextType): + def process_label(self, label: TextType) -> Text: """Process a str or Text into a label. Maybe overridden in a subclass to modify how labels are rendered. Args: From 922934597dc80ca0b8da058093dff5778f29c00c Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 2 May 2023 16:30:16 +0100 Subject: [PATCH 04/18] Sort the component classes to match the docs --- src/textual/widgets/_directory_tree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index d5ad73eb4..bfd045baa 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -34,9 +34,9 @@ class DirectoryTree(Tree[DirEntry]): """ COMPONENT_CLASSES: ClassVar[set[str]] = { - "directory-tree--folder", - "directory-tree--file", "directory-tree--extension", + "directory-tree--file", + "directory-tree--folder", "directory-tree--hidden", } """ From d4af9d239fbb16432ca15195ff398ac1c21300e0 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 2 May 2023 16:33:22 +0100 Subject: [PATCH 05/18] Add a path reactive to DirectoryTree so the path it views can be changed A couple of things come with this, at least one being a breaking change of sorts: - DirectoryTree now has a path attribute - DirectoryTree.path is a reactive - When DirectoryTree.path is assigned to it rebuilds the tree content - DirectoryTree.path can be assigned a str or Path but always evaluates to a Path - DirEntry.path is now typed as a Path - DirEntry drops is_dir (Directory.path.is_dir() does that job) - DirectoryTree.FileSelected.path is now always a Path This is the first of what might be a few changes here; the main thrust of this commit being to allow changing a DirectoryTree to view a different directory, and also to move to a Path-first approach. --- src/textual/widgets/_directory_tree.py | 68 ++++++++++++++++++-------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index bfd045baa..513049e36 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -1,6 +1,5 @@ from __future__ import annotations -import os from dataclasses import dataclass from pathlib import Path from typing import ClassVar, Iterable @@ -10,6 +9,7 @@ from rich.text import Text, TextType from ..events import Mount from ..message import Message +from ..reactive import var from ._tree import TOGGLE_STYLE, Tree, TreeNode @@ -17,21 +17,14 @@ from ._tree import TOGGLE_STYLE, Tree, TreeNode class DirEntry: """Attaches directory information to a node.""" - path: str - is_dir: bool + path: Path + """The path of the directory entry.""" loaded: bool = False + """Has this been loaded?""" class DirectoryTree(Tree[DirEntry]): - """A Tree widget that presents files and directories. - - Args: - path: Path to directory. - name: The name of the widget, or None for no name. - id: The ID of the widget in the DOM, or None for no ID. - classes: A space-separated list of classes, or None for no classes. - disabled: Whether the directory tree is disabled or not. - """ + """A Tree widget that presents files and directories.""" COMPONENT_CLASSES: ClassVar[set[str]] = { "directory-tree--extension", @@ -75,11 +68,19 @@ class DirectoryTree(Tree[DirEntry]): `DirectoryTree` or in a parent widget in the DOM. """ - def __init__(self, path: str) -> None: + def __init__(self, path: Path) -> None: super().__init__() - self.path: str = path + self.path: Path = path """The path of the file that was selected.""" + path: var[str | Path] = var["str | Path"](Path("."), init=False) + """The path that is the root of the directory tree. + + Note: + This can be set to either a `str` or a `pathlib.Path` object, but + the value will always be a `pathlib.Path` object. + """ + def __init__( self, path: str | Path, @@ -89,17 +90,42 @@ class DirectoryTree(Tree[DirEntry]): classes: str | None = None, disabled: bool = False, ) -> None: - str_path = os.fspath(path) - self.path = str_path + """Initialise the directory tree. + + Args: + path: Path to directory. + name: The name of the widget, or None for no name. + id: The ID of the widget in the DOM, or None for no ID. + classes: A space-separated list of classes, or None for no classes. + disabled: Whether the directory tree is disabled or not. + """ + self.path = path super().__init__( - str_path, - data=DirEntry(str_path, True), + str(path), + data=DirEntry(Path(path)), name=name, id=id, classes=classes, disabled=disabled, ) + def validate_path(self, path: str | Path) -> Path: + """Ensure that the path is of the `Path` type.""" + return Path(path) + + def watch_path(self, new_path: str | Path) -> None: + """Watch for changes to the `path` of the directory tree. + + Args: + new_path: The new path being set. + + If the path is changed the directory tree will be repopulated using + the new value as the root. + """ + self.path = Path(new_path) + self.reset(str(self.path), DirEntry(self.path)) + self.load_directory(self.root) + def process_label(self, label: TextType) -> Text: """Process a str or Text into a label. Maybe overridden in a subclass to modify how labels are rendered. @@ -174,7 +200,7 @@ class DirectoryTree(Tree[DirEntry]): for path in directory: node.add( path.name, - data=DirEntry(str(path), path.is_dir()), + data=DirEntry(path), allow_expand=path.is_dir(), ) node.expand() @@ -187,7 +213,7 @@ class DirectoryTree(Tree[DirEntry]): dir_entry = event.node.data if dir_entry is None: return - if dir_entry.is_dir: + if dir_entry.path.is_dir(): if not dir_entry.loaded: self.load_directory(event.node) else: @@ -198,5 +224,5 @@ class DirectoryTree(Tree[DirEntry]): dir_entry = event.node.data if dir_entry is None: return - if not dir_entry.is_dir: + if not dir_entry.path.is_dir(): self.post_message(self.FileSelected(dir_entry.path)) From 40e042d7e3d55786d718bd60e541965766f5090d Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 2 May 2023 16:45:18 +0100 Subject: [PATCH 06/18] Make _load_directory private --- src/textual/widgets/_directory_tree.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 513049e36..93eb1f6b5 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -124,7 +124,7 @@ class DirectoryTree(Tree[DirEntry]): """ self.path = Path(new_path) self.reset(str(self.path), DirEntry(self.path)) - self.load_directory(self.root) + self._load_directory(self.root) def process_label(self, label: TextType) -> Text: """Process a str or Text into a label. Maybe overridden in a subclass to modify how labels are rendered. @@ -189,7 +189,7 @@ class DirectoryTree(Tree[DirEntry]): """ return paths - def load_directory(self, node: TreeNode[DirEntry]) -> None: + def _load_directory(self, node: TreeNode[DirEntry]) -> None: assert node.data is not None dir_path = Path(node.data.path) node.data.loaded = True @@ -206,7 +206,7 @@ class DirectoryTree(Tree[DirEntry]): node.expand() def _on_mount(self, _: Mount) -> None: - self.load_directory(self.root) + self._load_directory(self.root) def _on_tree_node_expanded(self, event: Tree.NodeExpanded) -> None: event.stop() @@ -215,7 +215,7 @@ class DirectoryTree(Tree[DirEntry]): return if dir_entry.path.is_dir(): if not dir_entry.loaded: - self.load_directory(event.node) + self._load_directory(event.node) else: self.post_message(self.FileSelected(dir_entry.path)) From 5f22cf8e7b602c65ddba5750ab1614673532157c Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 2 May 2023 16:46:07 +0100 Subject: [PATCH 07/18] Remove empty rule from DirectoryTree.DEFAULT_CSS --- src/textual/widgets/_directory_tree.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 93eb1f6b5..0a788dd4f 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -48,10 +48,6 @@ class DirectoryTree(Tree[DirEntry]): text-style: bold; } - DirectoryTree > .directory-tree--file { - - } - DirectoryTree > .directory-tree--extension { text-style: italic; } From 465f522ef34cf3f4f1377588aff335c22168dfb3 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Tue, 2 May 2023 16:48:06 +0100 Subject: [PATCH 08/18] Add a reminder to add the node to the FileSelected message --- src/textual/widgets/_directory_tree.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 0a788dd4f..5d7d2bfbe 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -66,6 +66,9 @@ class DirectoryTree(Tree[DirEntry]): def __init__(self, path: Path) -> None: super().__init__() + # TODO: Add the node here too to mimic what Tree provides. + # Without it there's no sensible way of knowing which tree sent + # the message. self.path: Path = path """The path of the file that was selected.""" From e4c0517cc6f6a1939329e593475d6185e46f4c15 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 3 May 2023 08:58:11 +0100 Subject: [PATCH 09/18] Add the node to the FileSelected message Until now it wasn't really possible to know *which* DirectoryTree widget had sent a given message; this makes it available by providing the `node`, which in turn will provide the `tree`. --- src/textual/widgets/_directory_tree.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 5d7d2bfbe..73e45209c 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -64,11 +64,16 @@ class DirectoryTree(Tree[DirEntry]): `DirectoryTree` or in a parent widget in the DOM. """ - def __init__(self, path: Path) -> None: + def __init__(self, node: TreeNode[DirEntry], path: Path) -> None: + """Initialise the FileSelected object. + + Args: + node: The tree node for the file that was selected. + path: The path of the file that was selected. + """ super().__init__() - # TODO: Add the node here too to mimic what Tree provides. - # Without it there's no sensible way of knowing which tree sent - # the message. + self.node: TreeNode[DirEntry] = node + """The tree node of the file that was selected.""" self.path: Path = path """The path of the file that was selected.""" @@ -216,7 +221,7 @@ class DirectoryTree(Tree[DirEntry]): if not dir_entry.loaded: self._load_directory(event.node) else: - self.post_message(self.FileSelected(dir_entry.path)) + self.post_message(self.FileSelected(event.node, dir_entry.path)) def _on_tree_node_selected(self, event: Tree.NodeSelected) -> None: event.stop() @@ -224,4 +229,4 @@ class DirectoryTree(Tree[DirEntry]): if dir_entry is None: return if not dir_entry.path.is_dir(): - self.post_message(self.FileSelected(dir_entry.path)) + self.post_message(self.FileSelected(event.node, dir_entry.path)) From d1d435d68b14786c865e8414e0621829712e9df8 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 3 May 2023 09:02:49 +0100 Subject: [PATCH 10/18] Flesh out the docstring for validate_path --- src/textual/widgets/_directory_tree.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 73e45209c..a05e5b8a6 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -114,7 +114,18 @@ class DirectoryTree(Tree[DirEntry]): ) def validate_path(self, path: str | Path) -> Path: - """Ensure that the path is of the `Path` type.""" + """Ensure that the path is of the `Path` type. + + Args: + path: The path to validate. + + Returns: + The validated Path value. + + Note: + The result will always be a Python `Path` object, regardless of + the value given. + """ return Path(path) def watch_path(self, new_path: str | Path) -> None: From 8311f837b8af6c35ee7e76739ed149f7b2696d90 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 3 May 2023 09:08:21 +0100 Subject: [PATCH 11/18] Add a docstring to render_label Well, okay, borrow the same from Tree. --- src/textual/widgets/_directory_tree.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index a05e5b8a6..a79bbaf3b 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -158,6 +158,16 @@ class DirectoryTree(Tree[DirEntry]): return first_line def render_label(self, node: TreeNode[DirEntry], base_style: Style, style: Style): + """Render a label for the given node. + + Args: + node: A tree node. + base_style: The base style of the widget. + style: The additional style for the label. + + Returns: + A Rich Text object containing the label. + """ node_label = node._label.copy() node_label.stylize(style) From f5097671419b4ec107a95ea73fd794965bb12517 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 3 May 2023 09:13:11 +0100 Subject: [PATCH 12/18] Don't cast a DirEntry.path to a Path any more It's always a Path now. --- src/textual/widgets/_directory_tree.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index a79bbaf3b..aaa528838 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -216,10 +216,9 @@ class DirectoryTree(Tree[DirEntry]): def _load_directory(self, node: TreeNode[DirEntry]) -> None: assert node.data is not None - dir_path = Path(node.data.path) node.data.loaded = True directory = sorted( - self.filter_paths(dir_path.iterdir()), + self.filter_paths(node.data.path.iterdir()), key=lambda path: (not path.is_dir(), path.name.lower()), ) for path in directory: From 3d6fd7ef4ca098ba2d15d7c56bd343cbed3e2bdc Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 3 May 2023 09:13:42 +0100 Subject: [PATCH 13/18] Add a docstring to _load_directory --- src/textual/widgets/_directory_tree.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index aaa528838..f693d8558 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -215,6 +215,11 @@ class DirectoryTree(Tree[DirEntry]): return paths def _load_directory(self, node: TreeNode[DirEntry]) -> None: + """Load the directory contents for a given node. + + Args: + node: The node to load the directory contents for. + """ assert node.data is not None node.data.loaded = True directory = sorted( From 23d6c3611adabdcc10b7d196019f4ad347406722 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 3 May 2023 09:34:55 +0100 Subject: [PATCH 14/18] Add DirectoryTree.reload This is a general reload; reloading the whole tree. --- src/textual/widgets/_directory_tree.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index f693d8558..12f30b931 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -113,6 +113,11 @@ class DirectoryTree(Tree[DirEntry]): disabled=disabled, ) + def reload(self) -> None: + """Reload the `DirectoryTree` contents.""" + self.reset(str(self.path), DirEntry(Path(self.path))) + self._load_directory(self.root) + def validate_path(self, path: str | Path) -> Path: """Ensure that the path is of the `Path` type. @@ -138,8 +143,7 @@ class DirectoryTree(Tree[DirEntry]): the new value as the root. """ self.path = Path(new_path) - self.reset(str(self.path), DirEntry(self.path)) - self._load_directory(self.root) + self.reload() def process_label(self, label: TextType) -> Text: """Process a str or Text into a label. Maybe overridden in a subclass to modify how labels are rendered. From c4e2144207bf200389b9ce4967980016596e2edd Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 3 May 2023 09:59:35 +0100 Subject: [PATCH 15/18] Update the CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c512aa7c..3eff96dc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,10 +22,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Setting attributes with a `compute_` method will now raise an `AttributeError` https://github.com/Textualize/textual/issues/2383 - Unknown psuedo-selectors will now raise a tokenizer error (previously they were silently ignored) https://github.com/Textualize/textual/pull/2445 +- Breaking change: `DirectoryTree.FileSelected.path` is now always a `Path` https://github.com/Textualize/textual/issues/2448 +- Breaking change: `Directorytree.load_directory` renamed to `Directorytree._load_directory` https://github.com/Textualize/textual/issues/2448 ### Added - Watch methods can now optionally be private https://github.com/Textualize/textual/issues/2382 +- Added `DirectoryTree.path` reactive attribute https://github.com/Textualize/textual/issues/2448 +- Added `DirectoryTree.FileSelected.node` +- Added `DirectoryTree.reload` https://github.com/Textualize/textual/issues/2448 ## [0.22.3] - 2023-04-29 From 4250912a280b723da112e2f233b9daa87955d8d6 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 3 May 2023 10:44:45 +0100 Subject: [PATCH 16/18] Don't reassign the path when watching the path Bit of a thinko happening there. --- src/textual/widgets/_directory_tree.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 12f30b931..9b3a77f5e 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -133,16 +133,12 @@ class DirectoryTree(Tree[DirEntry]): """ return Path(path) - def watch_path(self, new_path: str | Path) -> None: + def watch_path(self) -> None: """Watch for changes to the `path` of the directory tree. - Args: - new_path: The new path being set. - If the path is changed the directory tree will be repopulated using the new value as the root. """ - self.path = Path(new_path) self.reload() def process_label(self, label: TextType) -> Text: From 9e15dc45aabf836618a2e24e6654ef18f0cc8b98 Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 3 May 2023 11:30:44 +0100 Subject: [PATCH 17/18] Link the non-issue-related DirectoryTree change back the PR --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de330230a..af9aa228c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Watch methods can now optionally be private https://github.com/Textualize/textual/issues/2382 - Added `DirectoryTree.path` reactive attribute https://github.com/Textualize/textual/issues/2448 -- Added `DirectoryTree.FileSelected.node` +- Added `DirectoryTree.FileSelected.node` https://github.com/Textualize/textual/pull/2463 - Added `DirectoryTree.reload` https://github.com/Textualize/textual/issues/2448 - Added textual.on decorator https://github.com/Textualize/textual/issues/2398 From 2e89cd11bbab5849e3cfaa1965605afd5cb4598c Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 3 May 2023 11:52:23 +0100 Subject: [PATCH 18/18] Add missing type annotation on render_label --- src/textual/widgets/_directory_tree.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 9b3a77f5e..0090717d7 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -157,7 +157,9 @@ class DirectoryTree(Tree[DirEntry]): first_line = text_label.split()[0] return first_line - def render_label(self, node: TreeNode[DirEntry], base_style: Style, style: Style): + def render_label( + self, node: TreeNode[DirEntry], base_style: Style, style: Style + ) -> Text: """Render a label for the given node. Args: