Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

sugarcraft/candy-files

Repository files navigation

candy-files

CandyFiles

CI codecov Packagist Version License PHP

demo

Dual-pane terminal file manager built on the SugarCraft stack β€” port of yorukot/superfile, with the Midnight Commander look.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ /home/alice [name-asc] β”‚ β”‚ /var/log [mtime-desc] β”‚
β”‚ ────────────────── β”‚ β”‚ ────────────────── β”‚
β”‚ β–Έ ../ DIR β”‚ β”‚ ../ DIR β”‚
β”‚ Documents/ DIR β”‚ β”‚ β–Έβœ“ syslog 240KB β”‚
β”‚ Downloads/ DIR β”‚ β”‚ auth.log 12KB β”‚
β”‚ .config/ DIR β”‚ β”‚ Xorg.0.log 4.0KB β”‚
β”‚ notes.md 12KB β”‚ β”‚ β”‚
β”‚ todo.txt 512B β”‚ β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 Tab swap Β· ↑↓ jk move Β· Enter open Β· ← h up Β· space select Β· s sort Β· . hidden Β· d delete Β· r refresh Β· q quit Β· / search Β· t tabs Β· u undo

Run it

composer install
./bin/candyfiles [LEFT_DIR] [RIGHT_DIR]

Default: left pane = current directory, right pane = $HOME.

Keys

Key Action
Tab Swap focus between panes
↑ / k Move cursor up
↓ / j Move cursor down
Home / g Top of listing
End / G Bottom of listing
Enter / β†’ Open directory (or no-op on a file)
← / h Go up one directory
Space Toggle selection on the entry under cursor + advance
s Cycle sort order (name β†’ mtime β†’ size, asc / desc)
. Toggle hidden-file visibility
c Copy (selection or cursor) to inactive pane; y confirm
m Move (selection or cursor) to inactive pane; y confirm
R Rename (cursor entry); new name prompted; y confirm
d Delete (selection or cursor); requires y confirm
r Refresh active pane
q Quit
/ Start 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
Ctrl+Shift+Tab Cycle to previous tab
u / Ctrl+z Undo last operation

Architecture

The whole transition layer is pure β€” filesystem I/O is injected as a Closure(string $path): list<Entry> so every transition is unit-testable without tmp dirs or stat fixtures.

File Role
Entry Value object: name, isDir, size, mtime, isLink, isHidden
Sort Enum (NameAsc/NameDesc/MtimeAsc/MtimeDesc/SizeAsc/SizeDesc) + comparator
Pane One pane: cwd, entries, cursor, selection set, sort, showHidden
ConfirmState Pending-confirmation enum (None / DeleteSelected)
Manager SugarCraft Model β€” orchestrates two panes, handles all keys + confirm gate
FsLister Default lister: scandir + lstat against the live filesystem
Renderer Pure view function β€” two pane boxes side-by-side + status line
BulkRename Bulk rename engine: regex template + sequential {n}/{name}/{ext} placeholders
PreviewPane File preview: ANSI image render via Mosaic for images, metadata block otherwise
AsyncOps Async copy/move/rename via React\Promise β€” keeps TUI responsive during I/O

Test plan

  • 36 tests / 65 assertions
  • Pure-state coverage: Entry (size formatting, parent sentinel), Sort (every order ×ば぀ dirs-first ×ば぀ cycle), Pane (open / navigate / move / select / sort / hidden toggle / parent-path / join)
  • Manager integration: Tab swap, key dispatch per pane, confirm gate (d arms, y confirms, anything else cancels), refresh status

Demos

Navigation

navigate

Search

search

Multi-select and delete

multi-select

Tab management

tabs

Sort cycling

sort-cycle

Undo delete

undo

Hidden-file toggle

hidden-files

Constructing Manager

Manager has 15 constructor parameters. Use the fluent builder for readability and to avoid parameter-order mistakes:

$manager = Manager::builder()
 ->withLeft($leftPane)
 ->withRight($rightPane)
 ->withActiveIdx(0)
 ->withStatus('')
 ->withConfirm(ConfirmState::None)
 ->withLister(fn(string $path): list<Entry> => FsLister::lister()($path))
 ->withSearchQuery(null)
 ->withSearchResults([])
 ->withSearchCursor(0)
 ->withTabs([])
 ->withTabIndex(0)
 ->withShowTabBar(false)
 ->withUndoStack([])
 ->withRedoStack([])
 ->withPendingOpDest(null)
 ->withPendingOpType(null)
 ->build();

The direct constructor is kept for backward compatibility only β€” new code should use Manager::builder().

Status

Phase 10 entry β€” copy / move / rename / undo are wired. Three-phase confirm gate (c/m/R arms, y confirms, anything else cancels). Undo restores delete/move/rename; copy undo is informational (original preserved). Everything underneath (the pure-state transition layer) is already in place.

Releases

No releases published

Packages

Contributors

Languages

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /