What is ECP?

The Editor Command Protocol (ECP) is Ultra's internal architecture that separates the editor into distinct services and clients communicating via JSON-RPC 2.0. This design enables multiple UI implementations (terminal, future GUI, headless testing) to interact with shared backend services through a unified API.

Three-Layer Architecture

The system comprises three distinct layers:

1

Clients

User interfaces including the TUI (Terminal UI), future GUI clients, and headless testing clients. All communicate through the same protocol.

2

ECP Server

Central JSON-RPC router that directs requests to appropriate service adapters based on method prefixes.

3

Services

Stateful backend components handling core functionality like document editing, file operations, git, LSP, and more.

Method Routing

ECP methods follow a namespace/action format, enabling organized routing:

document/*  → DocumentServiceAdapter
file/*      → FileServiceAdapter
git/*       → GitServiceAdapter
lsp/*       → LSPServiceAdapter
terminal/*  → TerminalServiceAdapter
database/*  → DatabaseServiceAdapter

Data Flow

Every user interaction flows through a consistent pipeline:

  1. Input Capture — Terminal captures keystroke
  2. TUI Processing — Client determines action from keybinding
  3. ECP Request — JSON-RPC request sent to service
  4. Service Execution — Service modifies state, emits events
  5. UI Update — Render scheduler batches and displays changes

Core Services

Ten specialized services handle different aspects of editor functionality, each following a consistent implementation pattern.

📄

Document Service

Text editing with piece table buffers, multi-cursor support, undo/redo stacks, and selection management.

document/*
📁

File Service

File system operations including read, write, watch for changes, and directory listing with glob support.

file/*
📦

Git Service

Version control features: status, diff, stage, unstage, commit, branch management, and timeline history.

git/*
🔧

LSP Service

Language intelligence via completion, hover, go-to-definition, find references, and real-time diagnostics.

lsp/*
💻

Terminal Service

PTY management for integrated terminal sessions with full shell support and 24-bit color rendering.

terminal/*
🗄️

Database Service

PostgreSQL/Supabase query execution, schema browsing, and result set management.

database/*
🎨

Syntax Service

Shiki-powered syntax highlighting with VS Code theme compatibility and tree-sitter parsing.

syntax/*
⚙️

Session Service

Configuration management for settings, keybindings, themes, and workspace state persistence.

session/*
🔍

Search Service

File and content search with regex support, glob filtering, and result streaming.

search/*
🔐

Secret Service

Secure credential storage via system keychain encryption for API keys and tokens.

secret/*

Service Implementation Pattern

Each service follows a standardized three-layer architecture that ensures consistency, testability, and clean separation of concerns:

Service Structure
src/services/[name]/
├── interface.ts      # Contract definition
├── types.ts          # Type definitions
├── [name]Service.ts  # Local implementation
├── adapter.ts        # ECP request mapping
└── index.ts          # Public exports

Pattern Benefits

This design enables switching between local and remote implementations, testing services in isolation, and adding new services without modifying existing code. Services operate as singletons with both named and default exports for flexible access throughout the application.

Event System

Services emit events through callback registration. Components subscribe to changes and receive unsubscribe functions for proper cleanup:

const unsubscribe = documentService.onChange((event) => {
  // Handle document changes
  scheduleRender('editor', renderEditor);
});

// Later: cleanup
unsubscribe();

Piece Table Buffer

Ultra uses a piece table data structure for text buffers, the same approach used by VS Code. This provides efficient editing operations regardless of file size.

How It Works

Instead of modifying the original text directly, a piece table maintains a list of "pieces" that reference either the original file or an append-only buffer of additions.

Original:
Hello World
After edit:
Hello Beautiful World
Undo:
Hello Beautiful World

Benefits

  • Fast inserts/deletes — O(log n) operations via balanced tree
  • Efficient undo/redo — Just adjust piece references
  • Low memory overhead — Original file never copied
  • Safe editing — Original preserved until explicit save

UI Component System

Ultra's terminal UI is built on a component-based architecture with intelligent rendering that minimizes redraws and terminal I/O.

Component Hierarchy

App
├── Navbar
├── Sidebar
│   ├── FileTree
│   └── GitPanel
├── EditorPane
│   ├── TabBar
│   ├── Editor
│   │   ├── LineNumbers
│   │   ├── TextBuffer
│   │   └── Minimap
│   └── Scrollbar
├── Panel
│   ├── Terminal
│   ├── Problems
│   └── AIChat
└── StatusBar

Render Lifecycle

Components follow a simple lifecycle:

  1. State Change — Component data updates
  2. Schedule Render — Component queued with priority
  3. Layout — Calculate bounds based on parent
  4. Paint — Write cells to screen buffer
  5. Flush — Only dirty cells sent to terminal

Input Handling

Keyboard and mouse input flows through a focus-based system:

// Input is routed to the focused component
inputManager.onKeyPress((key) => {
  const focused = focusManager.current;

  // Check keybindings first
  if (keybindings.handle(key, focused.context)) {
    return;
  }

  // Otherwise let the component handle it
  focused.onKeyPress(key);
});

Terminal Rendering Pipeline

📋

Render Scheduler

Batches rapid state changes, deduplicates by ID, orders by priority

🎨

Component Render

Each component paints to ScreenBuffer, tracking changed cells

📤

Buffer Flush

Only dirty cells written out with optimized cursor movement

🖥️

Terminal Output

ANSI escape sequences sent to stdout with 24-bit color

Dirty Cell Tracking

Rather than redrawing everything, Ultra maintains a dirty boolean grid. When a cell changes, it's marked for output. During flush, only changed cells generate ANSI sequences — preventing the performance drain of full-screen rewrites.

Ready to explore?

Get started with Ultra and experience the power of ECP-driven editing.