diff --git a/CHANGELOG.md b/CHANGELOG.md index 230d547ca7..64060a4bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [8.0.2] - 2026-03-03 + +### Changed + +- Themes are now in alphabetical order in command palette https://github.com/Textualize/textual/pull/6405 + +### Fixed + +- Fixed issues with Directory Tree https://github.com/Textualize/textual/pull/6405 + ## [8.0.1] - 2026-03-01 ### Fixed @@ -3360,6 +3370,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040 - New handler system for messages that doesn't require inheritance - Improved traceback handling +[8.0.2]: https://github.com/Textualize/textual/compare/v8.0.1...v8.0.2 [8.0.1]: https://github.com/Textualize/textual/compare/v8.0.0...v8.0.1 [8.0.0]: https://github.com/Textualize/textual/compare/v7.5.0...v8.0.0 [7.5.0]: https://github.com/Textualize/textual/compare/v7.4.0...v7.5.0 diff --git a/pyproject.toml b/pyproject.toml index a35bc698b6..5ccf7dbcd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual" -version = "8.0.1" +version = "8.0.2" homepage = "https://github.com/Textualize/textual" repository = "https://github.com/Textualize/textual" documentation = "https://textual.textualize.io/" diff --git a/src/textual/theme.py b/src/textual/theme.py index fea6999faa..1adb26a71c 100644 --- a/src/textual/theme.py +++ b/src/textual/theme.py @@ -2,6 +2,7 @@ from dataclasses import dataclass, field from functools import partial +from operator import attrgetter from typing import Callable from textual.command import DiscoveryHit, Hit, Hits, Provider @@ -475,7 +476,7 @@ def set_app_theme(name: str) -> None: return [ (theme.name, partial(set_app_theme, theme.name)) - for theme in themes.values() + for theme in sorted(themes.values(), key=attrgetter("name")) if theme.name != "textual-ansi" ] diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 13be6df16c..7b94d5da8a 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -465,7 +465,7 @@ def _safe_is_dir(path: Path) -> bool: """ try: return path.is_dir() - except PermissionError: + except OSError: # We may or may not have been looking at a directory, but we # don't have the rights or permissions to even know that. Best # 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]: if worker.is_cancelled: break yield entry - except PermissionError: + except OSError: pass @work(thread=True, exit_on_error=False) @@ -526,14 +526,15 @@ def _load_directory(self, node: TreeNode[DirEntry]) -> list[Path]: key=lambda path: (not self._safe_is_dir(path), path.name.lower()), ) - @work() + @work(exclusive=True, group="_loader") async def _loader(self) -> None: """Background loading queue processor.""" worker = get_current_worker() + load_queue = self._load_queue while not worker.is_cancelled: # Get the next node that needs loading off the queue. Note that # this blocks if the queue is empty. - node = await self._load_queue.get() + node = await load_queue.get() content: list[Path] = [] async with self.lock: cursor_node = self.cursor_node @@ -557,8 +558,7 @@ async def _loader(self) -> None: if cursor_node is not None: self.move_cursor(cursor_node, animate=False) finally: - # Mark this iteration as done. - self._load_queue.task_done() + load_queue.task_done() async def _on_tree_node_expanded(self, event: Tree.NodeExpanded[DirEntry]) -> None: event.stop() @@ -566,9 +566,11 @@ async def _on_tree_node_expanded(self, event: Tree.NodeExpanded[DirEntry]) -> No if dir_entry is None: return if await asyncio.to_thread(self._safe_is_dir, dir_entry.path): - await self._add_to_load_queue(event.node) + if event.node.data is not None: + await self._add_to_load_queue(event.node) else: - self.post_message(self.FileSelected(event.node, dir_entry.path)) + if event.node.data is not None: + self.post_message(self.FileSelected(event.node, dir_entry.path)) async def _on_tree_node_selected(self, event: Tree.NodeSelected[DirEntry]) -> None: event.stop() @@ -576,6 +578,8 @@ async def _on_tree_node_selected(self, event: Tree.NodeSelected[DirEntry]) -> No if dir_entry is None: return if await asyncio.to_thread(self._safe_is_dir, dir_entry.path): - self.post_message(self.DirectorySelected(event.node, dir_entry.path)) + if event.node.data is not None: + self.post_message(self.DirectorySelected(event.node, dir_entry.path)) else: - self.post_message(self.FileSelected(event.node, dir_entry.path)) + if event.node.data is not None: + self.post_message(self.FileSelected(event.node, dir_entry.path))