Skip to content

Core Components

This document details the core architectural components that form the foundation of Termix. These components work together to provide the application's functionality while maintaining clean separation of concerns.

Component Overview

┌─────────────────────┐
│     Program.cs      │  Application Entry Point
└─────────────────────┘

┌─────────────────────┐
│    FileManager      │  Central Controller
└─────────────────────┘

    ┌──────┴──────┐
    │             │
┌───▼────┐   ┌────▼────┐
│Services│   │UI Layer │
└────────┘   └─────────┘

Program.cs - Application Entry Point

Responsibilities

  • Command-line argument processing
  • Global exception handling
  • Application initialization and cleanup
  • Terminal environment setup

Implementation

csharp
public static class Program
{
    public static Task Main(string[] args)
    {
        var useIcons = !args.Contains("--no-icons");
        try
        {
            AnsiConsole.Clear();
            var fileManager = new FileManager(useIcons);
            fileManager.Run();
        }
        catch (Exception ex)
        {
            AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks);
        }
        finally
        {
            AnsiConsole.Clear();
        }

        return Task.CompletedTask;
    }
}

Key Features

  • Command-line options: Processes --no-icons flag
  • Exception handling: Global error catching with formatted output
  • Clean terminal: Ensures terminal is cleared before and after execution
  • Simple entry: Minimal startup logic, delegates to FileManager

Models - Data Structures

FileSystemItem

The core data model representing files and directories:

csharp
public class FileSystemItem
{
    public string Path { get; init; }
    public string Name { get; init; }  
    public bool IsDirectory { get; init; }
    public long Size { get; init; }
    public DateTime LastWriteTime { get; init; }
    public bool IsParentDirectory { get; init; }

    public FileSystemItem(string path, string name, bool isDirectory, 
        long size, DateTime lastWriteTime, bool isParentDirectory = false)
    {
        Path = path;
        Name = name;
        IsDirectory = isDirectory;
        Size = size;
        LastWriteTime = lastWriteTime;
        IsParentDirectory = isParentDirectory;
    }
}

Design Decisions:

  • Immutable: Uses init properties for thread safety
  • Complete metadata: Includes all necessary file information
  • Display name: Name may be truncated for UI purposes
  • Parent directory flag: Special handling for ".." entries

ClipboardItem

Represents items in the application clipboard:

csharp
public class ClipboardItem
{
    public FileSystemItem Item { get; }
    public ClipboardMode Mode { get; }

    public ClipboardItem(FileSystemItem item, ClipboardMode mode)
    {
        Item = item;
        Mode = mode;
    }
}

public enum ClipboardMode
{
    Copy,
    Move
}

Features:

  • Mode tracking: Distinguishes between copy and move operations
  • Item reference: Maintains reference to the original file system item
  • State preservation: Clipboard contents persist across navigation

Input Processing

InputHandler

Processes keyboard input and translates it to FileManager operations:

csharp
public class InputHandler
{
    private readonly FileManager _fileManager;

    public InputHandler(FileManager fileManager)
    {
        _fileManager = fileManager;
    }

    public void ProcessKey(ConsoleKeyInfo keyInfo)
    {
        switch (_fileManager.CurrentMode)
        {
            case FileManager.InputMode.Normal:
                ProcessNormalModeKey(keyInfo);
                break;
            case FileManager.InputMode.Filter:
                ProcessFilterModeKey(keyInfo);
                break;
            // ... other modes
        }
    }
}

Key Features:

  • Mode-specific processing: Different behavior for each input mode
  • Key translation: Maps raw keyboard input to logical operations
  • Error isolation: Input errors don't crash the application

Key Processing Strategy

csharp
private void ProcessNormalModeKey(ConsoleKeyInfo keyInfo)
{
    switch (keyInfo.Key)
    {
        case ConsoleKey.UpArrow:
        case ConsoleKey.K when keyInfo.Modifiers == 0:
            _fileManager.MoveSelection(-1);
            break;
            
        case ConsoleKey.DownArrow:
        case ConsoleKey.J when keyInfo.Modifiers == 0:
            _fileManager.MoveSelection(1);
            break;
            
        case ConsoleKey.Enter:
        case ConsoleKey.L when keyInfo.Modifiers == 0:
            _fileManager.OpenSelectedItem();
            break;
            
        // ... more key mappings
    }
}

UI Components

DoubleBufferedRenderer

Provides flicker-free terminal rendering:

csharp
public class DoubleBufferedRenderer
{
    private string _lastRendered = "";

    public void Render(IRenderable content)
    {
        var currentContent = AnsiConsole.ToAnsi(content);
        
        if (currentContent != _lastRendered)
        {
            AnsiConsole.Clear();
            AnsiConsole.Write(content);
            _lastRendered = currentContent;
        }
    }
}

Benefits:

  • Flicker reduction: Only redraws when content changes
  • Performance: Avoids unnecessary terminal operations
  • Smooth experience: Maintains visual continuity

FileManagerRenderer

Creates the visual layout for the file manager:

csharp
public class FileManagerRenderer
{
    private readonly IconProvider _iconProvider;

    public IRenderable GetLayout(string currentPath, List<FileSystemItem> items, 
        int selectedIndex, IRenderable preview, int viewOffset, IRenderable footer)
    {
        var pathPanel = CreatePathPanel(currentPath);
        var contentLayout = CreateTwoPaneLayout(items, selectedIndex, preview, viewOffset);
        
        return new Rows(pathPanel, contentLayout, footer);
    }
}

Layout Structure:

┌─────────────────────────────────────┐
│          Path Panel                 │  Current directory path
├─────────────────────────────────────┤
│ File List    │    Preview Panel     │  Two-pane layout
│              │                      │
│              │                      │
├─────────────────────────────────────┤
│          Footer Panel               │  Status and shortcuts
└─────────────────────────────────────┘

IconProvider

Maps file types to appropriate icons:

csharp
public class IconProvider
{
    private readonly bool _useIcons;
    private readonly Dictionary<string, string> _iconMap;

    public IconProvider(bool useIcons)
    {
        _useIcons = useIcons;
        _iconMap = BuildIconMap();
    }

    public string GetIcon(FileSystemItem item)
    {
        if (!_useIcons) return GetAsciiIcon(item);
        
        if (item.IsDirectory) return "📁";
        
        var extension = Path.GetExtension(item.Name).ToLower();
        return _iconMap.TryGetValue(extension, out var icon) ? icon : "📄";
    }
}

Icon Categories:

  • Directories: Folder icons
  • Code files: Language-specific icons
  • Documents: Document type icons
  • Images: Image format icons
  • Configuration: Settings/config icons

Service Integration

Service Coordination

Core components coordinate with the services layer:

csharp
// FileManager coordinates services
private void RefreshDirectory()
{
    // FileSystemService reads directory
    _unfilteredItems = FileSystemService.GetDirectoryContents(_currentPath);
    
    // IgnoreService filters results (implicit)
    _currentItems = ApplyIgnoreFilters(_unfilteredItems);
    
    // Update UI
    UpdatePreview();
    SetNeedsRedraw();
}

private void ExecuteFileOperation()
{
    // ActionService performs operation
    var result = await ActionService.CopyAsync(source, dest, progress, token);
    
    // Update UI based on result
    _statusMessage = result.Message;
    if (result.Success) RefreshDirectory();
}

Component Communication

Event Flow

User Input → InputHandler → FileManager → Services → UI Update
    ↑                                                      ↓
    └──────────── Rendered Display ←────────────────────────┘

Data Flow

  1. Input Processing: InputHandler receives keystrokes
  2. Command Translation: Keys mapped to FileManager operations
  3. Service Coordination: FileManager calls appropriate services
  4. State Update: Application state modified based on results
  5. UI Refresh: Renderer updates display with new state

Error Handling Architecture

Error Boundaries

Each component handles errors at its level:

csharp
// Program.cs - Global handler
catch (Exception ex)
{
    AnsiConsole.WriteException(ex);
}

// FileManager - Operation handler  
catch (Exception ex)
{
    _statusMessage = $"[red]Operation failed: {ex.Message}[/]";
    SetNeedsRedraw();
}

// Services - Detailed handler
catch (FileNotFoundException ex)
{
    return new ActionResponse(false, $"[red]File not found: {ex.FileName}[/]");
}

Error Handling Principles:

  • Graceful degradation: Application continues running
  • User feedback: Clear, actionable error messages
  • State preservation: Errors don't corrupt application state
  • Logging context: Preserve error details for debugging

Performance Architecture

Efficient Updates

Components are designed for minimal work:

csharp
// Only redraw when necessary
private void SetNeedsRedraw()
{
    _needsRedraw = true;
}

// Debounced operations
_debounceCts.Cancel();
_debounceCts = new CancellationTokenSource();

// Background processing
Task.Run(async () => {
    var result = await LongRunningOperation();
    // Update UI on main thread
}, token);

Memory Management

  • Minimal object creation: Reuse objects where possible
  • Efficient collections: Use appropriate data structures
  • Proper disposal: Clean up resources promptly
  • Garbage collection: Minimize GC pressure

Testing Architecture

Component Testing Strategy

csharp
// Unit test individual components
[Test]
public void FileSystemItem_Constructor_SetsProperties()
{
    var item = new FileSystemItem("/path", "name", true, 1024, DateTime.Now);
    Assert.That(item.Path, Is.EqualTo("/path"));
    Assert.That(item.IsDirectory, Is.True);
}

// Integration test component interactions
[Test] 
public void FileManager_Navigation_UpdatesState()
{
    var fileManager = new FileManager(false);
    fileManager.NavigateToDirectory("/test");
    Assert.That(fileManager.CurrentPath, Is.EqualTo("/test"));
}

Mock Strategies

Components are designed for testability:

  • Dependency injection: Services injected into components
  • Interface abstraction: Mock implementations for testing
  • State verification: Internal state can be inspected
  • Behavior testing: Operations produce expected results

Extension Points

Adding New Components

  1. Define interfaces: Create clear contracts
  2. Implement component: Follow existing patterns
  3. Integrate with FileManager: Add coordination logic
  4. Update UI: Modify renderer if needed
  5. Add input handling: Map keys to new operations

Component Guidelines

  • Single responsibility: Each component has one clear purpose
  • Clear interfaces: Well-defined public API
  • Error handling: Robust error management
  • Performance conscious: Efficient implementation
  • Testable design: Easy to unit test

Component Design

Termix components follow the principle of "high cohesion, loose coupling." Each component has a clear, focused responsibility but can work independently of others.

State Management

While components are loosely coupled, state management is centralized in FileManager. Be careful when modifying state-related code to maintain consistency.

Released under the MIT License