How Superfile Handles Trash Across Different Operating Systems

Superfile delegates system-trash operations to a dedicated Go package in src/internal/trash, using build tags to compile OS-specific backends for Linux, macOS, and Windows while falling back to permanent deletion when native recycling is unavailable.

The terminal file manager from yorukot/superfile manages cross-platform file deletion through a unified trash abstraction. How superfile handles trash across different operating systems depends on a compact internal package that leverages Go build tags, native APIs, and the FreeDesktop.org specification. The core integration point lives in src/internal/handle_file_operations.go, where the application decides whether to recycle or permanently delete an item.

Cross-Platform Trash Architecture

The trash layer is intentionally small. A single public API in src/internal/trash/trash.go declares the Result struct, the Backend type, and the sentinel ErrUnsupported. Every platform must implement two functions:

  • Available(path string) bool – Reports whether the backend can trash the given path.
  • Move(path string) (Result, error) – Moves the item into the system trash and returns metadata about the operation.

Go build tags select the correct implementation file at compile time. The package contains trash_linux.go, trash_darwin.go, trash_windows.go, trash_darwin_nocgo.go, and trash_unsupported.go, ensuring only one OS-specific variant is linked into the final binary.

Linux Trash and the FreeDesktop.org Specification

On Linux, src/internal/trash/trash_linux.go implements the FreeDesktop.org Trash specification. It locates an appropriate trash directory—typically ~/.local/share/Trash or a hidden .Trash-<uid> folder on the same filesystem as the file—and then performs three steps:

  1. Generates a unique trash name.
  2. Writes a .trashinfo file containing the original path and deletion timestamp.
  3. Moves the file into the files/ subdirectory.

This approach guarantees that standard Linux file managers can read and restore superfile-trashed items.

// Move implementation for Linux
func Move(path string) (Result, error) {
    srcAbs, _ := filepath.Abs(path)
    td, err := selectTrashDir(srcAbs, true)      // find/create appropriate trash dir
    trashName, infoPath, filesPath, err := reserveTrashInfo(td, srcAbs)
    // write .trashinfo, then move the file
    if err := movePath(srcAbs, filesPath); err != nil { … }
    return Result{
        OriginalPath: srcAbs,
        TrashedPath:  filepath.Join(td.files, trashName),
        Backend:      BackendFreeDesktop,
        StrictlyRecycled: true,
    }, nil
}

macOS Trash via the Foundation Framework

Superfile’s macOS support requires C-go. In src/internal/trash/trash_darwin.go, the Move function calls the native Foundation FileManager.trashItem(at:) API through a C wrapper named spf_trash_item. The wrapper returns the item’s new path inside the macOS Trash and any error emitted by the OS.

If superfile is compiled without CGO, src/internal/trash/trash_darwin_nocgo.go is selected instead. That stub returns ErrUnsupported, forcing the caller to fall back to permanent deletion.

cPath := C.CString(absPath)
defer C.free(unsafe.Pointer(cPath))

result := C.spf_trash_item(cPath)               // Foundation FileManager call
if result.errorMessage != nil {
    return Result{OriginalPath: absPath, Backend: BackendMacOS},
           errors.New(C.GoString(result.errorMessage))
}
trashedPath := ""
if result.trashedPath != nil {
    trashedPath = C.GoString(result.trashedPath)
}
return Result{
    OriginalPath: absPath,
    TrashedPath:  trashedPath,
    Backend:      BackendMacOS,
    StrictlyRecycled: true,
}, nil

Windows Trash Using COM IFileOperation

On Windows, src/internal/trash/trash_windows.go uses the COM IFileOperation interface with the FOF_ALLOWUNDO flag to recycle files. The implementation converts the path to an IShellItem via SHCreateItemFromParsingName, invokes DeleteItem, and then runs PerformOperations.

Because COM requires consistent thread affinity, the operation spawns a goroutine and locks it to the underlying OS thread with runtime.LockOSThread().

absPath, _ := filepath.Abs(path)
result := Result{OriginalPath: absPath, Backend: BackendWindows}
errCh := make(chan error, 1)
go func() {
    runtime.LockOSThread()
    defer runtime.UnlockOSThread()
    errCh <- recycleWithIFileOperation(absPath) // performs the recycle
}()
if err := <-errCh; err != nil {
    return result, err
}
result.StrictlyRecycled = true
return result, nil

Unsupported Platforms and Graceful Fallback

For any operating system not covered above, src/internal/trash/trash_unsupported.go provides a stub where Available always returns false and Move always returns ErrUnsupported. This ensures the codebase compiles on exotic platforms without trash semantics.

The integration logic in src/internal/handle_file_operations.go decides at runtime whether to recycle or permanently delete. Inside makeDeleteProcessor, the code swaps the deletion strategy based on the useTrash setting:

// inside makeDeleteProcessor (src/internal/handle_file_operations.go)
deleteFunc := os.RemoveAll                    // default: permanent delete
if useTrash {
    deleteFunc = func(item string) error {
        // Move the item to the system trash
        _, err := trash.Move(item)
        return err
    }
}
for _, item := range items {
    if err := deleteFunc(item); err != nil {
        // handle error …
    }
}

When the trash backend reports ErrUnsupported or Available is false, superfile falls back to os.RemoveAll, ensuring the deletion still completes.

Summary

  • Superfile centralizes trash logic in the src/internal/trash package and selects OS-specific code via Go build tags.
  • Linux follows the FreeDesktop.org spec, writing .trashinfo files to ~/.local/share/Trash or a per-filesystem .Trash-<uid> directory.
  • macOS delegates to Foundation’s FileManager.trashItem(at:) through a C-go wrapper; a nocgo stub returns ErrUnsupported.
  • Windows recycles items via COM IFileOperation with FOF_ALLOWUNDO, executing on a locked OS thread.
  • If trash is unavailable or unsupported, src/internal/handle_file_operations.go falls back to permanent deletion with os.RemoveAll.

Frequently Asked Questions

What Linux trash specification does Superfile follow?

Superfile follows the FreeDesktop.org Trash specification as implemented in src/internal/trash/trash_linux.go. It generates unique trash names, writes .trashinfo metadata files, and stores the actual content under the files/ subdirectory so standard Linux file managers can restore trashed items.

Why does the Windows trash implementation lock the OS thread?

The Windows backend in src/internal/trash/trash_windows.go calls COM IFileOperation, which requires thread affinity. By spawning a goroutine and invoking runtime.LockOSThread(), superfile guarantees that the COM interface is created, invoked, and destroyed on the same OS thread, preventing runtime errors.

What happens when Superfile is built on macOS without CGO?

When CGO is disabled, Go selects src/internal/trash/trash_darwin_nocgo.go, whose Move returns ErrUnsupported. The deletion logic in src/internal/handle_file_operations.go then falls back to os.RemoveAll, performing a permanent delete instead of moving the item to the macOS Trash.

How does Superfile decide whether to trash or permanently delete a file?

Each platform file implements Available(path string) bool. On Linux, this returns true for any non-empty path; on macOS and Windows it validates the path string; on unsupported platforms it returns false. If Available is false or trash.Move returns an error, the core processor in src/internal/handle_file_operations.go switches the delete function from trash.Move to os.RemoveAll.

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 →