How superfile Implements File Operations (Copy, Paste, Cut, Delete): A Deep Dive into the Go Source Code

Superfile implements file operations through a model-centric workflow that combines clipboard management, process bar progress tracking, and low-level filesystem utilities to handle copy, paste, cut, and delete actions across the TUI.

The open-source terminal file manager superfile (by yorukot/superfile) orchestrates file operations through a sophisticated three-layer architecture. This design separates UI interactions from filesystem execution, enabling progress tracking and safe error handling while supporting advanced features like cross-partition moves and trash integration. Understanding this implementation reveals how modern Go-based TUI applications manage complex I/O operations without blocking the interface.

The Three-Layer Architecture of superfile File Operations

The implementation divides responsibilities across distinct layers that communicate through Bubble Tea commands (tea.Cmd) and message passing.

Layer Responsibility Key Files
UI → Model Translates hotkeys into model methods that manipulate the clipboard and create commands src/internal/handle_file_operations.go
Model → Processor Creates process bar entries and delegates work to background processors src/internal/handle_file_operations.go
Processor → Utils Executes actual filesystem calls with partition awareness and error handling src/internal/file_operations.go

This separation allows superfile to display real-time progress bars while safely handling symlinks, duplicate names, and permission errors in the background.

Copy and Cut Operations

Clipboard Management in the Model

Superfile maintains an internal clipboard structure (m.clipboard) that stores absolute paths alongside a boolean flag indicating whether the operation is a cut (move) or copy. When a user triggers a copy or cut action, the model resets the clipboard and populates it with the selected items.

In src/internal/handle_file_operations.go, the copySingleItem method handles individual file operations:

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)
}

The cut parameter determines the later behavior during paste operations—true triggers a move, while false triggers a copy.

Single vs Multiple Item Selection

For batch operations, copyMultipleItem iterates over selected entries and stores them sorted by visible order:

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)
}

Both methods ensure the clipboard is primed before any paste command executes, with the cut flag preserved for the subsequent operation.

Paste Operations and Validation

Validating Paste Destinations

When the user initiates a paste, getPasteItemCmd validates the operation before creating the background processor. This prevents dangerous operations like moving a directory into itself or its subdirectories.

The validation logic in src/internal/handle_file_operations.go uses isAncestor to detect circular references:

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)
    }
}

The Paste Processor Workflow

The executePasteOperation function creates a processbar.Process entry and selects the appropriate operation type (processbar.OpCopy or processbar.OpCut). It then delegates to makePasteProcessor, which iterates over source paths and chooses the optimal transfer method:

  • moveElement – Used for cuts where source and destination reside on the same partition (fast rename)
  • pasteDir – Handles cross-partition moves and all copy operations via recursive file walking

This optimization avoids unnecessary data duplication when a simple rename suffices.

Delete Operations and Trash Handling

Deletion follows a similar processor pattern. The getDeleteCmd method gathers selected items and determines whether to use the system trash or permanent deletion based on user configuration.

The processor created by makeDeleteProcessor iterates over the target list and calls:

  • trash.Move – For safe deletion (moves to recycle bin)
  • os.RemoveAll – For permanent deletion when trash is disabled or unavailable

Errors abort the operation and propagate back to the UI through the process bar system, ensuring users receive immediate feedback on permission failures or locked files.

Low-Level Filesystem Utilities

The core logic resides in src/internal/file_operations.go, which provides partition-aware utilities that handle edge cases like symlinks and duplicate filenames.

moveElement and Partition Optimization

moveElement checks whether source and destination exist on the same partition using isSamePartition. If they match, it attempts a fast rename; if the rename fails or partitions differ, it falls back to copy-then-delete:

func moveElement(src, dst string) error {
    // Check same partition for optimization
    if isSamePartition(src, dst) {
        return os.Rename(src, dst)
    }
    // Fallback to copy-then-delete for cross-partition moves
    if err := copyElement(src, dst); err != nil {
        return err
    }
    return os.RemoveAll(src)
}

copyElement and Recursive Directory Handling

copyElement dispatches to type-specific handlers:

  • copyDir – Recursively creates destination directories while preserving mode bits, iterating through entries and calling copyElement for nested items
  • copyFile – Streams data via io.Copy between source and destination file handles

For complex paste operations, pasteDir coordinates the recursive walk and delegates single-file operations to actualPasteOperation, which updates the process bar state after each file transfer.

Safety Checks with isAncestor

The isAncestor function provides critical protection against filesystem corruption by resolving symlinks and checking if the destination path is contained within the source path. This prevents users from accidentally moving a parent directory into its own child, which would destroy data.

Progress Tracking with the Process Bar

All file operations integrate with superfile's process bar system (processbar.Model) to provide visual feedback:

  1. Initialization – processBarModel.SendAddProcessMsg creates a UI entry with the total item count and operation name
  2. Progress Updates – The processor updates process.CurrentFile and process.Done fields, pushing changes via TrySendingUpdateProcessMsg
  3. Completion – markProcessDone records the final status and timestamp, updating the UI with success or failure indicators

This architecture ensures the TUI remains responsive even during large file transfers, as the heavy I/O runs in background goroutines while the main thread handles rendering.

Summary

  • superfile implements file operations through a three-layer architecture separating UI commands, clipboard management, and filesystem execution.
  • The clipboard stores paths and a cut flag in handle_file_operations.go, determining whether subsequent pastes perform moves or copies.
  • Validation via isAncestor and validatePasteOperation prevents dangerous operations like moving directories into themselves.
  • Partition awareness in moveElement optimizes same-device moves using fast renames instead of copy-then-delete.
  • Process bar integration provides real-time progress tracking for all I/O operations through background processors that report to the UI model.

Frequently Asked Questions

How does superfile handle cut operations differently from copy operations?

Superfile stores a boolean cut flag in the clipboard when users trigger copySingleItem(true) or copyMultipleItem(true). During paste execution, this flag determines whether the processor calls moveElement (for cuts) or pasteDir (for copies). For cuts on the same partition, superfile attempts a fast rename via os.Rename rather than duplicating data.

What prevents users from accidentally moving a folder into its own subdirectory?

The validatePasteOperation function in src/internal/handle_file_operations.go calls isAncestor to check if the destination path is equal to or contained within any source path. This includes symlink resolution, ensuring the operation aborts before any filesystem changes occur if a circular reference is detected.

Does superfile support trash/recycle bin functionality?

Yes. When the getDeleteCmd receives a false parameter (indicating non-permanent deletion), the delete processor calls trash.Move from platform-specific wrappers in src/internal/trash/. This moves files to the OS recycle bin instead of permanently deleting them. If trash functionality is unavailable, it falls back to os.RemoveAll.

How does superfile optimize file moves across different partitions?

The moveElement function in src/internal/file_operations.go first checks isSamePartition to compare device IDs. If source and destination share the same partition, it uses os.Rename for an instantaneous metadata update. For cross-partition moves, it falls back to copyElement followed by os.RemoveAll, ensuring data integrity while optimizing for the fast path when possible.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →