Skip to content

FileManager ​

The FileManager class is the central controller of Termix, orchestrating all user interactions, state management, and UI coordination. It acts as the main application loop and coordinates between services, UI components, and user input.

Overview ​

csharp
public class FileManager
{
    public enum InputMode
    {
        Normal, Add, Rename, DeleteConfirm, Filter, FilteredNavigation, QuitConfirm
    }
    
    public InputMode CurrentMode { get; private set; } = InputMode.Normal;
    public bool IsViewFiltered => !string.IsNullOrEmpty(_inputText);
    
    public FileManager(bool useIcons);
    public void Run();
}

Core Responsibilities ​

1. State Management ​

  • Current directory and navigation state
  • File selection and view offset
  • Input modes and user interaction state
  • Clipboard contents and operation state
  • Search/filter state and history

2. Event Coordination ​

  • Keyboard input processing
  • UI update orchestration
  • Service operation coordination
  • Progress tracking and reporting

3. User Interface Control ​

  • Double-buffered rendering coordination
  • Status message management
  • Progress indication
  • Mode-specific UI behavior

Input Mode State Machine ​

FileManager operates as a state machine with distinct modes:

Normal ←→ Filter ←→ FilteredNavigation
  ↕        ↕
 Add    Rename
  ↕        ↕
DeleteConfirm  QuitConfirm

Mode Descriptions ​

ModePurposeActive InputExit Conditions
NormalDefault navigation and operationsNavigation keys, operation keysMode change keys
AddCreating new files/directoriesText input for nameEnter (create) or Esc (cancel)
RenameRenaming existing itemsText input for new nameEnter (rename) or Esc (cancel)
FilterSearch input activeText input for search queryEsc (apply filter)
FilteredNavigationNavigating search resultsNavigation keysEsc (clear filter), B (back to results)
DeleteConfirmConfirming deletiony/n keysy (delete), n/Esc (cancel)
QuitConfirmConfirming quit during operationsy/n keysy (force quit), n (continue)

Main Application Loop ​

Run Method ​

The main application loop:

csharp
public void Run()
{
    AnsiConsole.Clear();
    RefreshDirectory(setInitialSelection: true);

    while (!_shouldQuit)
    {
        if (_needsRedraw)
        {
            _needsRedraw = false;
            var footerContent = CreateFooterRenderable();
            var layout = _renderer.GetLayout(/* parameters */);
            _doubleBuffer.Render(layout);
        }

        while (!Console.KeyAvailable && !_needsRedraw && !_shouldQuit)
        {
            Thread.Sleep(50); // 20 FPS polling
        }

        if (Console.KeyAvailable)
        {
            _inputHandler.ProcessKey(Console.ReadKey(true));
        }
    }
}

Key Features:

  • Efficient polling: 50ms sleep for ~20 FPS responsiveness
  • Conditional rendering: Only redraws when needed
  • Input prioritization: Keyboard input processed immediately
  • Clean exit: Graceful shutdown handling

State Management ​

Directory State ​

csharp
private string _currentPath = Directory.GetCurrentDirectory();
private List<FileSystemItem> _currentItems = [];
private List<FileSystemItem> _unfilteredItems = [];
private int _selectedIndex;
private int _viewOffset;
csharp
private readonly Stack<string> _navigationStack = new();
private (string Path, string Filter, List<FileSystemItem> Items, int SelectedIndex)? _savedFilterState;

Operation State ​

csharp
private ClipboardItem? _clipboard;
private bool _isOperationInProgress;
private string? _progressTaskDescription;
private double _progressValue;
private CancellationTokenSource? _operationCts;

Input State ​

csharp
private string _inputText = "";
private string _promptText = "";
private bool _isDeepSearchRunning;
private List<FileSystemItem>? _recursiveSearchCache;

Key Methods ​

Directory Management ​

RefreshDirectory ​

csharp
private void RefreshDirectory(string? findAndSelect = null, bool preserveSelection = false, bool setInitialSelection = false)

Reloads current directory contents and manages selection:

  • findAndSelect: Selects specific item by name after refresh
  • preserveSelection: Maintains current selection index
  • setInitialSelection: Sets selection to first item
csharp
private void NavigateToDirectory(string path, string? findAndSelect = null)

Changes current directory with error handling and state management.

File Operations ​

BeginAdd ​

csharp
public void BeginAdd()

Initiates file/directory creation mode:

  • Determines target directory (selected directory or current)
  • Sets appropriate prompt text
  • Switches to Add mode

BeginRename ​

csharp
public void BeginRename()

Initiates rename mode:

  • Pre-fills input with current name
  • Validates rename capability
  • Switches to Rename mode

BeginDelete ​

csharp
public void BeginDelete()

Initiates deletion confirmation:

  • Shows confirmation prompt
  • Switches to DeleteConfirm mode

Clipboard Operations ​

BeginCopy/BeginMove ​

csharp
public void BeginCopy()
public void BeginMove()

Copies or cuts selected item to clipboard:

  • Creates ClipboardItem with appropriate mode
  • Updates status message
  • Provides visual feedback

BeginPaste ​

csharp
public void BeginPaste()

Initiates paste operation:

  • Validates clipboard contents and destination
  • Handles naming conflicts
  • Starts async operation with progress tracking

Search and Filter ​

BeginFilter ​

csharp
public void BeginFilter()

Initiates search mode:

  • Clears previous search state
  • Switches to Filter mode
  • Prepares for recursive search

UpdateFilter ​

csharp
public void UpdateFilter(string newFilterText)

Updates search results in real-time:

  • Debounces rapid input changes
  • Triggers background recursive search if needed
  • Applies filtering to current results

ApplyFilter/ClearFilter ​

csharp
private void ApplyFilter()
public void ClearFilter()

Manages filter application and removal.

UI Integration ​

Rendering Coordination ​

The FileManager coordinates with the UI layer:

csharp
private IRenderable CreateFooterRenderable()
{
    if (_isOperationInProgress)
    {
        // Show progress bar and status
    }
    else if (_statusMessage != null)
    {
        // Show status message
    }
    else if (_clipboard != null)
    {
        // Show clipboard contents
    }
    else
    {
        // Show mode-appropriate shortcuts
    }
}

Double Buffering ​

Uses DoubleBufferedRenderer for flicker-free updates:

  • Only renders when _needsRedraw is true
  • Efficient for terminal applications
  • Smooth user experience

Progress Reporting ​

Async Operation Progress ​

csharp
private void BeginPaste()
{
    var progress = new Progress<(long totalBytes, long completedBytes, string currentFile)>(value =>
    {
        _progressTaskDescription = value.currentFile;
        _progressValue = value.totalBytes > 0 ? (double)value.completedBytes / value.totalBytes * 100 : 0;
        SetNeedsRedraw();
    });

    Task.Run(async () =>
    {
        var result = await ActionService.CopyAsync(source, dest, progress, token);
        // Handle completion
    });
}

Features:

  • Real-time progress: Updates UI during long operations
  • File-level detail: Shows current file being processed
  • Percentage calculation: Provides completion percentage
  • Cancellation support: User can interrupt operations

Error Handling ​

Graceful Error Management ​

csharp
private void NavigateToDirectory(string path, string? findAndSelect = null)
{
    try
    {
        _currentPath = Path.GetFullPath(path);
        RefreshDirectory(findAndSelect, setInitialSelection: true);
    }
    catch (Exception ex)
    {
        _statusMessage = $"[red]Navigation failed: {ex.Message.EscapeMarkup()}[/]";
        SetNeedsRedraw();
    }
}

Error Handling Strategy:

  • Non-fatal errors: Show status messages, continue operation
  • User feedback: Clear, actionable error messages
  • State preservation: Maintain valid state even after errors
  • Graceful degradation: Application remains functional

Performance Optimization ​

Efficient State Updates ​

csharp
private void SetNeedsRedraw()
{
    _needsRedraw = true;
}

Only triggers redraws when necessary, reducing CPU usage.

Debounced Operations ​

Search input is debounced to prevent excessive operations:

csharp
_debounceCts.Cancel();
_debounceCts = new CancellationTokenSource();
var token = _debounceCts.Token;

Background Processing ​

Long-running operations like recursive search run in background:

csharp
ActionService.GetDeepDirectoryContentsAsync(_currentPath, token).ContinueWith(task =>
{
    if (task.IsCompletedSuccessfully)
    {
        _recursiveSearchCache = task.Result;
        ApplyFilter();
    }
    _isDeepSearchRunning = false;
    SetNeedsRedraw();
}, token);

Integration Points ​

Service Coordination ​

FileManager coordinates multiple services:

  • ActionService: File operations
  • FileSystemService: Directory reading
  • FilePreviewService: Content preview
  • IgnoreService: Implicit through ActionService

InputHandler Integration ​

csharp
private readonly InputHandler _inputHandler;

public FileManager(bool useIcons)
{
    _inputHandler = new InputHandler(this);
}

InputHandler processes keyboard input and calls FileManager methods.

Renderer Integration ​

csharp
private readonly FileManagerRenderer _renderer;

var layout = _renderer.GetLayout(_currentPath, _currentItems, _selectedIndex, 
    _currentPreview, _viewOffset, footerContent);

FileManagerRenderer creates the visual layout based on current state.

Testing Considerations ​

State Testing ​

Key state transitions should be tested:

  • Mode changes
  • Directory navigation
  • Selection management
  • Filter state

Integration Testing ​

Test FileManager with real services:

  • File system operations
  • UI rendering
  • Input processing
  • Error scenarios

Mock Integration ​

Mock services for isolated testing:

csharp
var mockActionService = new Mock<ActionService>();
var mockFileSystemService = new Mock<FileSystemService>();

Thread Safety ​

Single-Threaded UI ​

FileManager runs on the main thread:

  • UI updates are not thread-safe
  • Background operations use Progress<T> for thread-safe updates
  • CancellationTokens coordinate async operations

Background Operation Coordination ​

csharp
Task.Run(async () => {
    // Background work
}).ContinueWith(t => {
    // UI updates on main thread
}, CancellationToken.None);

Extension Points ​

Custom Modes ​

New input modes can be added by:

  1. Adding to InputMode enum
  2. Implementing mode logic
  3. Adding input handling
  4. Updating UI rendering

Custom Operations ​

New file operations can be integrated by:

  1. Adding methods to FileManager
  2. Coordinating with ActionService
  3. Adding input bindings
  4. Updating status messages

Architecture Insight

FileManager is intentionally monolithic to maintain tight control over application state and ensure consistent behavior. While this might seem contrary to separation of concerns, it provides the reliability needed for a file manager application.

State Complexity

FileManager manages complex state interactions. When modifying the code, be careful to maintain state consistency, especially during mode transitions and async operations.

Released under the MIT License