How Superfile Handles File Operations (Copy, Cut, Paste, Delete) Under the Hood
Superfile implements file operations through a model‑centric workflow that ties UI commands, a clipboard abstraction, and low‑level file‑system utilities together.
Superfile is a terminal-based file manager built with Go and Bubble Tea. Understanding how it handles file operations under the hood reveals a sophisticated three‑layer architecture that separates UI interactions from actual disk operations while providing real‑time progress feedback.
The Three-Layer Architecture
Superfile’s file operations are organized into distinct layers that handle different responsibilities:
- UI → Model: Hotkeys trigger methods like
copySingleItemandgetDeleteCmdinsrc/internal/handle_file_operations.go - Model → Processor: The model creates a process bar entry and delegates work to a
FileListProcessor - Processor → Utils: Low‑level utilities in
src/internal/file_operations.goperform the actual copy, move, and delete operations
Clipboard Management for Copy and Cut Operations
Superfile maintains an internal clipboard (m.clipboard) that stores absolute paths and a boolean flag indicating whether the operation is a cut (move) or a copy.
Storing Paths and Cut Flags
When you press the copy or cut hotkey, the model resets the clipboard and populates it with the selected items' locations:
// copySingleItem handles copying the currently focused item
func (m *model) copySingleItem(cut bool) {
panel := m.getFocusedFilePanel()
m.clipboard.Reset(cut) // Reset and set cut flag
if panel.Empty() { return }
m.clipboard.Add(panel.GetFocusedItem().Location)
}
// copyMultipleItem handles batch operations on selected items
func (m *model) copyMultipleItem(cut bool) {
panel := m.getFocusedFilePanel()
m.clipboard.Reset(cut)
if panel.SelectedCount() == 0 { return }
items := panel.GetSelectedLocationsSortedAsVisible()
m.clipboard.SetItems(items)
}
Source: src/internal/handle_file_operations.go (lines 44-68)
The cut flag determines whether a subsequent paste operation performs a rename (fast move) or a full copy‑then‑delete cycle.
Paste Operation Implementation
Validation and Safety Checks
When you trigger a paste, getPasteItemCmd validates the operation before execution. It checks for illegal moves—such as pasting a directory into itself or its own sub‑directory—using the isAncestor helper:
func (m *model) getPasteItemCmd() tea.Cmd {
copyItems := m.clipboard.PruneInaccessibleItemsAndGet()
cut := m.clipboard.IsCut()
if len(copyItems) == 0 { return nil }
reqID := m.nextIoReqCnt()
panelLocation := m.getFocusedFilePanel().Location
return func() tea.Msg {
if err := validatePasteOperation(panelLocation, copyItems, cut); err != nil {
return NewNotifyModalMsg(notify.New(true, "Invalid paste location", err.Error(), notify.NoAction), reqID)
}
return m.executePasteOperation(&m.processBarModel, panelLocation, copyItems, cut, reqID)
}
}
Source: src/internal/handle_file_operations.go (lines 70-88)
Processor Creation and Low-Level Execution
executePasteOperation creates a process bar entry and hands control to makePasteProcessor. This processor iterates over source paths and dispatches to specialized handlers:
moveElement: Used for cuts on the same partition (fast rename)pasteDir: Handles recursive copying for copy operations or cross‑partition moves
The pasteDir function in src/internal/file_operations.go attempts a fast rename first, then falls back to walking the source tree and calling actualPasteOperation for each entry:
func actualPasteOperation(info os.FileInfo, path, newPath string, cut, sameDev bool, p *processbar.Process, model *model) error {
if cut && sameDev {
// Fast path: rename for same-device moves
return os.Rename(path, newPath)
}
// Cross-device or copy: perform full copy
if info.IsDir() {
return copyDir(path, newPath, info)
}
return copyFile(path, newPath, info)
}
Source: src/internal/file_operations.go (lines 202-239)
Delete Operations with Trash Support
Deletion follows a similar pattern. getDeleteCmd gathers selected items and creates a command that respects your trash configuration:
- Trash mode: Uses platform‑specific wrappers (
trash.Move) to move files to the system recycle bin - Permanent delete: Calls
os.RemoveAlldirectly
The makeDeleteProcessor iterates over the file list, updates the process bar, and reports errors back to the UI:
// Example: Creating a delete command (false = use trash if available)
deleteCmd := m.getDeleteCmd(false)
if deleteCmd != nil {
// Command returns a tea.Msg for the update loop
msg := deleteCmd()
}
Source: src/internal/handle_file_operations.go (lines 45-57, 85-110)
Low-Level File Utilities
The actual disk operations reside in src/internal/file_operations.go. These functions handle edge cases like partitions, symlinks, and duplicate names:
| Function | Purpose | Location |
|---|---|---|
moveElement(src, dst) |
Fast move with same‑partition check; falls back to copy‑then‑delete | file_operations.go:47-75 |
copyElement(src, dst) |
Dispatches to copyDir or copyFile based on source type |
file_operations.go:77-88 |
copyDir(src, dst, srcInfo) |
Recursive directory creation with preserved permissions | file_operations.go:90-121 |
copyFile(src, dst, srcInfo) |
Streams data via io.Copy |
file_operations.go:136-154 |
isAncestor(src, dst) |
Detects circular paste attempts (including symlink resolution) | file_operations.go:241-288 |
Process Bar Integration
All file operations report progress through a process bar model (src/internal/ui/processbar/model.go). The workflow is:
- Creation:
processBarModel.SendAddProcessMsginitializes a UI entry with the operation name and total item count - Updates: The processor updates
process.CurrentFile,process.Done, andprocess.Stateas items are processed - Completion:
markProcessDonerecords the final status and timestamp
This architecture ensures that long-running operations (copying large directories) display real‑time progress without blocking the main UI thread.
Summary
- Superfile uses a three‑layer architecture separating UI commands (
handle_file_operations.go) from low‑level disk utilities (file_operations.go) - The clipboard stores absolute paths and a cut flag to determine whether to move or copy during paste
- Validation prevents illegal operations like pasting a directory into itself using the
isAncestorcheck - Cross‑partition moves automatically fall back to copy‑then‑delete logic when
isSamePartitionreturns false - All operations integrate with a process bar for real‑time feedback without blocking the UI
Frequently Asked Questions
How does Superfile detect if a move is possible without copying?
Superfile checks partitions using isSamePartition in src/internal/file_operations.go. If the source and destination reside on the same filesystem, it calls os.Rename for an instant move. If they differ, it falls back to copying the data then deleting the source.
What happens if I try to paste a folder into itself?
The validatePasteOperation function calls isAncestor to detect when the destination path is the same as or nested within the source. This returns an error before any files are modified, preventing infinite recursion and data loss.
Does Superfile preserve file permissions during copy operations?
Yes. The copyDir function preserves mode bits when creating destination directories, and copyFile maintains the original file's permissions. This occurs in src/internal/file_operations.go within the respective copy utilities.
How does the trash feature work across different operating systems?
Superfile delegates to platform‑specific implementations in src/internal/trash/trash_* files. On Linux it may use FreeDesktop Trash specifications, while macOS and Windows use their native APIs. When trash is disabled or unavailable, it falls back to os.RemoveAll for permanent deletion.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →