diff --git a/src/IO/Input.py b/src/IO/Input.py index bbac296..4bedd7f 100644 --- a/src/IO/Input.py +++ b/src/IO/Input.py @@ -21,7 +21,8 @@ class Input: "enter": self._enter, "back": self._back, "parent": self._parent, - "sort": self._sort, + "sort_direction": self._sort_direction, + "sort_grouping": self._sort_grouping, "new_folder": self._new_folder, "delete_folder": self._delete_folder, "new_file": self._new_file, @@ -96,8 +97,12 @@ class Input: self._maybe_show_error() return True - def _sort(self) -> bool: - self.app.navigator.cycle_sort() + def _sort_direction(self) -> bool: + self.app.navigator.cycle_sort_direction() + return True + + def _sort_grouping(self) -> bool: + self.app.navigator.cycle_sort_grouping() return True def _new_folder(self) -> bool: diff --git a/src/core/Config.py b/src/core/Config.py index d9cd0b1..663dbd5 100644 --- a/src/core/Config.py +++ b/src/core/Config.py @@ -13,7 +13,8 @@ DEFAULT_KEYBINDS: dict[str, list[str]] = { "enter": ["l", "KEY_RIGHT", "\n", "KEY_ENTER"], "back": ["-", "KEY_LEFT", "KEY_BACKSPACE", "\x7f", "\x08"], "parent": ["h"], - "sort": ["i"], + "sort_direction": ["r"], + "sort_grouping": ["i"], "new_folder": ["d"], "delete_folder": ["D"], "new_file": ["%"], @@ -25,10 +26,12 @@ DEFAULT_SCROLL_OFFSET = 4 class Config: def __init__(self, keybinds: dict[str, list[str]], cursor_reset: str, - sort_mode: str, theme: Theme, scroll_offset: int) -> None: + sort_direction: str, sort_grouping: str, theme: Theme, + scroll_offset: int) -> None: self._keybinds = keybinds self._cursor_reset = cursor_reset - self._sort_mode = sort_mode + self._sort_direction = sort_direction + self._sort_grouping = sort_grouping self._theme = theme self._scroll_offset = scroll_offset self._key_to_action: dict[str, str] = { @@ -53,11 +56,19 @@ class Config: keybinds = dict(DEFAULT_KEYBINDS) keybinds.update(data.get("keybinds", {})) cursor_reset = data.get("cursor", {}).get("reset", DEFAULT_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 + + sort_data = data.get("sort", {}) + sort_direction = sort_data.get("direction", Sort.DEFAULT_DIRECTION) + if sort_direction not in Sort.DIRECTIONS: + print(f"pyexplorer: unknown sort direction {sort_direction!r}, " + f"falling back to {Sort.DEFAULT_DIRECTION!r}", file=sys.stderr) + sort_direction = Sort.DEFAULT_DIRECTION + sort_grouping = sort_data.get("grouping", Sort.DEFAULT_GROUPING) + if sort_grouping not in Sort.GROUPINGS: + print(f"pyexplorer: unknown sort grouping {sort_grouping!r}, " + f"falling back to {Sort.DEFAULT_GROUPING!r}", file=sys.stderr) + sort_grouping = Sort.DEFAULT_GROUPING + theme = Theme.load(data) scroll_offset = data.get("scroll", {}).get("offset", DEFAULT_SCROLL_OFFSET) @@ -65,7 +76,8 @@ class Config: print(f"pyexplorer: invalid scroll offset {scroll_offset!r}, " f"falling back to {DEFAULT_SCROLL_OFFSET}", file=sys.stderr) scroll_offset = DEFAULT_SCROLL_OFFSET - return cls(keybinds, cursor_reset, sort_mode, theme, scroll_offset) + return cls(keybinds, cursor_reset, sort_direction, sort_grouping, + theme, scroll_offset) @staticmethod def _default_path() -> Path: @@ -83,8 +95,12 @@ class Config: return self._cursor_reset @property - def sort_mode(self) -> str: - return self._sort_mode + def sort_direction(self) -> str: + return self._sort_direction + + @property + def sort_grouping(self) -> str: + return self._sort_grouping @property def theme(self) -> Theme: diff --git a/src/core/Navigator.py b/src/core/Navigator.py index 508d61c..35c5b3e 100644 --- a/src/core/Navigator.py +++ b/src/core/Navigator.py @@ -12,7 +12,8 @@ class Navigator: def __init__(self, path: Path, config: Config) -> None: self._config = config - self._sort_mode = config.sort_mode + self._sort_direction = config.sort_direction + self._sort_grouping = config.sort_grouping self._history = [path] self._cursor = Cursor() self._current: Folder | None = None @@ -25,7 +26,8 @@ class Navigator: try: self._current = Folder(self._history[-1]) self._items = Sort.sort_items(self._current.children(), - self._sort_mode) + self._sort_direction, + self._sort_grouping) except FileNotFoundError: self._items = [] except PermissionError: @@ -35,9 +37,17 @@ 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 _resort(self) -> None: + self._items = Sort.sort_items(self._items, self._sort_direction, + self._sort_grouping) + + def cycle_sort_direction(self) -> None: + self._sort_direction = Sort.next_direction(self._sort_direction) + self._resort() + + def cycle_sort_grouping(self) -> None: + self._sort_grouping = Sort.next_grouping(self._sort_grouping) + self._resort() def _reset_cursor(self) -> None: if self._config.cursor_reset == "keep": diff --git a/src/core/Sort.py b/src/core/Sort.py index d9f2672..215c906 100644 --- a/src/core/Sort.py +++ b/src/core/Sort.py @@ -1,33 +1,32 @@ from ..items.AItems import AItem -MODES = [ - "name_asc", - "name_desc", - "dirs_first_asc", - "files_first_asc", - "dirs_first_desc", - "files_first_desc", -] +DIRECTIONS = ["asc", "desc"] +GROUPINGS = ["mixed", "dirs_first", "files_first"] -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") +DEFAULT_DIRECTION = "asc" +DEFAULT_GROUPING = "mixed" -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: +def sort_items(items: list[AItem], direction: str, grouping: str) -> list[AItem]: + items = sorted(items, key=lambda item: item.name, reverse=direction == "desc") + if grouping == "dirs_first": items = sorted(items, key=lambda item: not item.is_navigable()) - elif mode in _FILES_FIRST_MODES: + elif grouping == "files_first": items = sorted(items, key=lambda item: item.is_navigable()) return items -def next_mode(mode: str) -> str: +def next_direction(direction: str) -> str: try: - i = MODES.index(mode) + i = DIRECTIONS.index(direction) except ValueError: - return DEFAULT_MODE - return MODES[(i + 1) % len(MODES)] + return DEFAULT_DIRECTION + return DIRECTIONS[(i + 1) % len(DIRECTIONS)] + + +def next_grouping(grouping: str) -> str: + try: + i = GROUPINGS.index(grouping) + except ValueError: + return DEFAULT_GROUPING + return GROUPINGS[(i + 1) % len(GROUPINGS)]