Skip to content

Commit 431dc7d

Browse files
authored
Merge pull request #6405 from Textualize/directory-tree-fixes
Directory tree fixes
2 parents 55d1eac + c2ffcb8 commit 431dc7d

4 files changed

Lines changed: 28 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [8.0.2] - 2026-03-03
9+
10+
### Changed
11+
12+
- Themes are now in alphabetical order in command palette https://github.com/Textualize/textual/pull/6405
13+
14+
### Fixed
15+
16+
- Fixed issues with Directory Tree https://github.com/Textualize/textual/pull/6405
17+
818
## [8.0.1] - 2026-03-01
919

1020
### Fixed
@@ -3360,6 +3370,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040
33603370
- New handler system for messages that doesn't require inheritance
33613371
- Improved traceback handling
33623372

3373+
[8.0.2]: https://github.com/Textualize/textual/compare/v8.0.1...v8.0.2
33633374
[8.0.1]: https://github.com/Textualize/textual/compare/v8.0.0...v8.0.1
33643375
[8.0.0]: https://github.com/Textualize/textual/compare/v7.5.0...v8.0.0
33653376
[7.5.0]: https://github.com/Textualize/textual/compare/v7.4.0...v7.5.0

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "textual"
3-
version = "8.0.1"
3+
version = "8.0.2"
44
homepage = "https://github.com/Textualize/textual"
55
repository = "https://github.com/Textualize/textual"
66
documentation = "https://textual.textualize.io/"

src/textual/theme.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from dataclasses import dataclass, field
44
from functools import partial
5+
from operator import attrgetter
56
from typing import Callable
67

78
from textual.command import DiscoveryHit, Hit, Hits, Provider
@@ -475,7 +476,7 @@ def set_app_theme(name: str) -> None:
475476

476477
return [
477478
(theme.name, partial(set_app_theme, theme.name))
478-
for theme in themes.values()
479+
for theme in sorted(themes.values(), key=attrgetter("name"))
479480
if theme.name != "textual-ansi"
480481
]
481482

src/textual/widgets/_directory_tree.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ def _safe_is_dir(path: Path) -> bool:
465465
"""
466466
try:
467467
return path.is_dir()
468-
except PermissionError:
468+
except OSError:
469469
# We may or may not have been looking at a directory, but we
470470
# don't have the rights or permissions to even know that. Best
471471
# we can do, short of letting the error blow up, is assume it's
@@ -505,7 +505,7 @@ def _directory_content(self, location: Path, worker: Worker) -> Iterator[Path]:
505505
if worker.is_cancelled:
506506
break
507507
yield entry
508-
except PermissionError:
508+
except OSError:
509509
pass
510510

511511
@work(thread=True, exit_on_error=False)
@@ -526,14 +526,15 @@ def _load_directory(self, node: TreeNode[DirEntry]) -> list[Path]:
526526
key=lambda path: (not self._safe_is_dir(path), path.name.lower()),
527527
)
528528

529-
@work()
529+
@work(exclusive=True, group="_loader")
530530
async def _loader(self) -> None:
531531
"""Background loading queue processor."""
532532
worker = get_current_worker()
533+
load_queue = self._load_queue
533534
while not worker.is_cancelled:
534535
# Get the next node that needs loading off the queue. Note that
535536
# this blocks if the queue is empty.
536-
node = await self._load_queue.get()
537+
node = await load_queue.get()
537538
content: list[Path] = []
538539
async with self.lock:
539540
cursor_node = self.cursor_node
@@ -557,25 +558,28 @@ async def _loader(self) -> None:
557558
if cursor_node is not None:
558559
self.move_cursor(cursor_node, animate=False)
559560
finally:
560-
# Mark this iteration as done.
561-
self._load_queue.task_done()
561+
load_queue.task_done()
562562

563563
async def _on_tree_node_expanded(self, event: Tree.NodeExpanded[DirEntry]) -> None:
564564
event.stop()
565565
dir_entry = event.node.data
566566
if dir_entry is None:
567567
return
568568
if await asyncio.to_thread(self._safe_is_dir, dir_entry.path):
569-
await self._add_to_load_queue(event.node)
569+
if event.node.data is not None:
570+
await self._add_to_load_queue(event.node)
570571
else:
571-
self.post_message(self.FileSelected(event.node, dir_entry.path))
572+
if event.node.data is not None:
573+
self.post_message(self.FileSelected(event.node, dir_entry.path))
572574

573575
async def _on_tree_node_selected(self, event: Tree.NodeSelected[DirEntry]) -> None:
574576
event.stop()
575577
dir_entry = event.node.data
576578
if dir_entry is None:
577579
return
578580
if await asyncio.to_thread(self._safe_is_dir, dir_entry.path):
579-
self.post_message(self.DirectorySelected(event.node, dir_entry.path))
581+
if event.node.data is not None:
582+
self.post_message(self.DirectorySelected(event.node, dir_entry.path))
580583
else:
581-
self.post_message(self.FileSelected(event.node, dir_entry.path))
584+
if event.node.data is not None:
585+
self.post_message(self.FileSelected(event.node, dir_entry.path))

0 commit comments

Comments
 (0)