diff --git a/src/IO/Input.py b/src/IO/Input.py index af696da..8e803b5 100644 --- a/src/IO/Input.py +++ b/src/IO/Input.py @@ -15,6 +15,7 @@ class Input: "enter": self._enter, "back": self._back, "parent": self._parent, + "sort": self._sort, } def handle(self, key: str) -> bool: @@ -49,6 +50,10 @@ class Input: self.app.navigator.up() return True + def _sort(self) -> bool: + self.app.navigator.cycle_sort() + return True + @property def app(self) -> App: return self._app diff --git a/src/core/Config.py b/src/core/Config.py index 621eca1..5282c06 100644 --- a/src/core/Config.py +++ b/src/core/Config.py @@ -3,6 +3,7 @@ import os import sys import tomllib from pathlib import Path +from . import Sort DEFAULT_KEYBINDS: dict[str, list[str]] = { "quit": ["q"], @@ -11,15 +12,18 @@ DEFAULT_KEYBINDS: dict[str, list[str]] = { "enter": ["l", "KEY_RIGHT", "\n", "KEY_ENTER"], "back": ["h", "KEY_LEFT", "KEY_BACKSPACE", "\x7f", "\x08"], "parent": ["-"], + "sort": ["i"], } DEFAULT_CURSOR_RESET = "top" class Config: - def __init__(self, keybinds: dict[str, list[str]], cursor_reset: str) -> None: + def __init__(self, keybinds: dict[str, list[str]], cursor_reset: str, + sort_mode: str) -> None: self._keybinds = keybinds self._cursor_reset = cursor_reset + self._sort_mode = sort_mode self._key_to_action: dict[str, str] = { key: action for action, keys in keybinds.items() @@ -42,7 +46,12 @@ class Config: keybinds = dict(DEFAULT_KEYBINDS) keybinds.update(data.get("keybinds", {})) cursor_reset = data.get("cursor", {}).get("reset", DEFAULT_CURSOR_RESET) - return cls(keybinds, cursor_reset) + sort_mode = data.get("sort", {}).get("mode", Sort.DEFAULT_MODE) + if sort_mode not in Sort.MODES: + print(f"pyexplorer: unknown sort mode {sort_mode!r}, " + f"falling back to {Sort.DEFAULT_MODE!r}", file=sys.stderr) + sort_mode = Sort.DEFAULT_MODE + return cls(keybinds, cursor_reset, sort_mode) @staticmethod def _default_path() -> Path: @@ -58,3 +67,7 @@ class Config: @property def cursor_reset(self) -> str: return self._cursor_reset + + @property + def sort_mode(self) -> str: + return self._sort_mode diff --git a/src/core/Navigator.py b/src/core/Navigator.py index ccde0a1..8d446e3 100644 --- a/src/core/Navigator.py +++ b/src/core/Navigator.py @@ -2,6 +2,7 @@ from pathlib import Path from ..items.AItems import AItem from ..items.Folder import Folder from ..items.Symlink import Symlink +from . import Sort from .Config import Config from .Cursor import Cursor @@ -10,6 +11,7 @@ class Navigator: def __init__(self, path: Path, config: Config) -> None: self._config = config + self._sort_mode = config.sort_mode self._history = [path] self._cursor = Cursor() self._current: Folder | None = None @@ -21,7 +23,8 @@ class Navigator: self._error = None try: self._current = Folder(self._history[-1]) - self._items = self._current.children() + self._items = Sort.sort_items(self._current.children(), + self._sort_mode) except FileNotFoundError: self._items = [] except PermissionError: @@ -31,6 +34,10 @@ class Navigator: def refresh(self) -> None: self._load() + def cycle_sort(self) -> None: + self._sort_mode = Sort.next_mode(self._sort_mode) + self._items = Sort.sort_items(self._items, self._sort_mode) + def _reset_cursor(self) -> None: if self._config.cursor_reset == "keep": self._cursor.clamp(len(self._items)) diff --git a/src/core/Sort.py b/src/core/Sort.py new file mode 100644 index 0000000..d9f2672 --- /dev/null +++ b/src/core/Sort.py @@ -0,0 +1,33 @@ +from ..items.AItems import AItem + +MODES = [ + "name_asc", + "name_desc", + "dirs_first_asc", + "files_first_asc", + "dirs_first_desc", + "files_first_desc", +] + +DEFAULT_MODE = "name_asc" + +_DESC_MODES = ("name_desc", "dirs_first_desc", "files_first_desc") +_DIRS_FIRST_MODES = ("dirs_first_asc", "dirs_first_desc") +_FILES_FIRST_MODES = ("files_first_asc", "files_first_desc") + + +def sort_items(items: list[AItem], mode: str) -> list[AItem]: + items = sorted(items, key=lambda item: item.name, reverse=mode in _DESC_MODES) + if mode in _DIRS_FIRST_MODES: + items = sorted(items, key=lambda item: not item.is_navigable()) + elif mode in _FILES_FIRST_MODES: + items = sorted(items, key=lambda item: item.is_navigable()) + return items + + +def next_mode(mode: str) -> str: + try: + i = MODES.index(mode) + except ValueError: + return DEFAULT_MODE + return MODES[(i + 1) % len(MODES)]