Dual-pane terminal file manager
Two panes side-by-side, Midnight Commander style. Tab to swap focus, multi-select, sort cycling, hidden-file toggle, delete-with-confirm. Pure-state transition layer โ filesystem is injected, every transition is unit-testable.
composer require sugarcraft/candy-files
composer install
./bin/candyfiles [LEFT_DIR] [RIGHT_DIR]
# Default: left = cwd, right = $HOME
# Keys
# Tab Swap focus
# โ / k Move up
# โ / j Move down
# Home / g Top
# End / G Bottom
# Enter / โ Open directory
# โ / h Up one directory
# Space Toggle selection + advance
# s Cycle sort order (6 modes)
# . Toggle hidden files
# c Copy selection or cursor โ inactive pane (y confirm)
# m Move selection or cursor โ inactive pane (y confirm)
# R Rename cursor entry with prompt (y confirm)
# d Delete (selection or cursor) โ y to confirm
# r Refresh active pane
# q Quit
# / Search mode (type to filter, Enter to open)
# Escape Exit search mode
# t Duplicate current tab
# Ctrl+w Close current tab
# Ctrl+Tab Cycle to next tab
# u / Ctrl+z Undo last operation
s cycles.. hides / shows dotfiles.c/m/R stage to inactive pane / cursor target; y commits. Undo available for move and rename.d stages, y commits โ protects against fat-finger deletes.FsLister closure โ swap for a fixture lister in unit tests with no tmp dirs./ enters search mode; type to filter, โโ navigate, Enter opens.t duplicates tab, Ctrl+Tab cycles, Ctrl+w closes.u or Ctrl+z restores last delete / move / rename.| Class | Method | Description |
|---|---|---|
| Manager | start(leftCwd, rightCwd, ?lister) | Factory โ initialises two panes + first tab |
| Manager | update(Msg): [Model, ?Cmd] | Main event loop โ key dispatch, confirm gate |
| Manager | view(): string | Renders two panes + tab bar + status line |
| Manager | copy(src, dst), move(src, dst), rename(src, newName) | File-manager ops โ used by confirm-gate handlers |
| Manager | search(query), exitSearch(), openSearchResult() | Live search mode |
| Manager | undo(), canUndo(), canRedo() | Delete / move / rename undo/redo stack |
| Manager | duplicateTab(), closeTab(), switchTab() | Tab management |
| Pane | open(cwd, lister, sort, showHidden): self | Factory โ reads and sorts directory |
| Pane | navigate(lister): self | Enter directory under cursor (no-op on files) |
| Pane | toggleSelection(): self | Add/remove cursor entry from selection set |
| Sort | apply(entries): list<Entry> | Sort entries (parent sentinel stays at index 0) |
| Sort | cycle(): self | Cycles: name-asc โ name-desc โ mtime-asc โ mtime-desc โ size-asc โ size-desc โ name-asc |
| Entry | displaySize(): string | Returns "DIR" / "LINK" / compacted bytes |
VHS-recorded GIFs of every example shipped with the app. Regenerated automatically on every push that touches the source.