# How Superfile Handles File Operations (Copy, Cut, Paste, Delete) Under the Hood

> Discover how Superfile manages file operations like copy, cut, paste, and delete. Explore its model-centric workflow combining UI commands, clipboard abstraction, and file-system utilities.

- Repository: [Yorukot/superfile](https://github.com/yorukot/superfile)
- Tags: internals
- Published: 2026-07-30

---

**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 `copySingleItem` and `getDeleteCmd` in [`src/internal/handle_file_operations.go`](https://github.com/yorukot/superfile/blob/main/src/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.go`](https://github.com/yorukot/superfile/blob/main/src/internal/file_operations.go) perform 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:

```go
// 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`](https://github.com/yorukot/superfile/blob/main/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:

```go
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`](https://github.com/yorukot/superfile/blob/main/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`](https://github.com/yorukot/superfile/blob/main/src/internal/file_operations.go) attempts a fast rename first, then falls back to walking the source tree and calling `actualPasteOperation` for each entry:

```go
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`](https://github.com/yorukot/superfile/blob/main/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:

1. **Trash mode**: Uses platform‑specific wrappers (`trash.Move`) to move files to the system recycle bin
2. **Permanent delete**: Calls `os.RemoveAll` directly

The `makeDeleteProcessor` iterates over the file list, updates the process bar, and reports errors back to the UI:

```go
// 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`](https://github.com/yorukot/superfile/blob/main/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`](https://github.com/yorukot/superfile/blob/main/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`](https://github.com/yorukot/superfile/blob/main/src/internal/ui/processbar/model.go)). The workflow is:

1. **Creation**: `processBarModel.SendAddProcessMsg` initializes a UI entry with the operation name and total item count
2. **Updates**: The processor updates `process.CurrentFile`, `process.Done`, and `process.State` as items are processed
3. **Completion**: `markProcessDone` records 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`](https://github.com/yorukot/superfile/blob/main/handle_file_operations.go)) from low‑level disk utilities ([`file_operations.go`](https://github.com/yorukot/superfile/blob/main/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 `isAncestor` check
- **Cross‑partition** moves automatically fall back to copy‑then‑delete logic when `isSamePartition` returns 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`](https://github.com/yorukot/superfile/blob/main/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`](https://github.com/yorukot/superfile/blob/main/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.