# How Flow Control Implements Multi-Cursor Editing: A Deep Dive into the Zig Source Code

> Explore how Flow Control implements multi-cursor editing with its three-layer architecture. Delve into Zig source code for editor core, renderer, and terminal layers.

- Repository: [CJ van den Berg/flow](https://github.com/neurocyte/flow)
- Tags: deep-dive
- Published: 2026-03-08

---

**Flow Control implements multi-cursor editing through a three-layer architecture: the editor core manages arbitrary cursor objects (`CurSel`) in `src/tui/editor.zig`, the renderer detects terminal capabilities in `src/renderer/vaxis/renderer.zig`, and the terminal layer emits Vaxis-driven escape sequences to display hardware cursors.**

Multi-cursor editing in Flow Control, the Zig-based terminal editor from neurocyte/flow, enables simultaneous text manipulation across multiple locations. This feature relies on sophisticated coordination between the editor's data model and the terminal's rendering capabilities to deliver hardware-accelerated cursor display where supported.

## The Three-Layer Architecture of Multi-Cursor Editing in Flow Control

The implementation spans three tightly coupled layers that bridge user input with terminal hardware:

| Layer | Responsibility | Key Implementation |
|-------|----------------|---------------------|
| **Editor core** | Stores cursor objects and logic for adding, merging, and removing them | `src/tui/editor.zig` – `CurSel` struct, `cursels` list, `add_cursor_*` helpers |
| **Renderer capability detection** | Detects terminal support for true multi-cursor control | `src/renderer/vaxis/renderer.zig` – `cap_multi_cursor` parsing |
| **Terminal rendering** | Emits escape sequences to create, move, and clear extra cursors | `src/renderer/vaxis/renderer.zig` – `clear_all_multi_cursors`, `show_multi_cursor_yx` |

## Layer 1: Editor Core and CurSel Management

### The CurSel Data Structure

The foundation of multi-cursor editing in Flow Control rests on the `CurSel` struct defined in `src/tui/editor.zig`. The editor maintains a **list of nullable `CurSel` objects** (`cursels: CurSel.List`) where the first element represents the primary cursor and subsequent items represent secondary cursors.

```zig
// src/tui/editor.zig
pub const CurSel = struct {
    // cursor and selection state
    cursor: Cursor,
    selection: ?Selection,
    // ...
};

```

### Adding Secondary Cursors

Flow Control exposes several public commands for creating secondary cursors, each implemented as a method on the Editor struct:

```zig
// src/tui/editor.zig
pub fn add_cursor_up(self: *Self, ctx: Context) Result { ... }          // line 3984
pub fn add_cursor_down(self: *Self, ctx: Context) Result { ... }        // line 4011
pub fn add_cursor_next_match(self: *Self, ctx: Context) Result { ... } // line 4024
pub fn add_cursor_all_matches(self: *Self, _: Context) Result { ... } // line 4048
pub fn add_cursors_to_line_ends(self: *Self, _: Context) Result { ... } // line 4084

```

These helpers ultimately invoke internal logic such as `add_cursors_to_cursel_line_ends` (line 4067), which duplicates a cursor for every line-end of the current selection.

### Merging and Clearing Cursors

When secondary cursors overlap, `collapse_cursors` (line 1978) deduplicates them by merging selections or discarding exact duplicates. When the editor loses focus or the user disables the feature, `clear_all_cursors` (line 2170) empties the list, triggering the UI layer to request terminal cleanup via `self.rdr_.clear_all_multi_cursors()` (line 662 of `src/tui/tui.zig`).

## Layer 2: Terminal Capability Detection

Before rendering secondary cursors, Flow Control verifies that the terminal supports hardware multi-cursor control. During renderer initialization in `src/renderer/vaxis/renderer.zig`, the code parses the terminal's capability list:

```zig
// src/renderer/vaxis/renderer.zig (lines 414-417)
.cap_multi_cursor => {
    self.logger.print("multi cursor capability detected", .{});
    self.vx.caps.multi_cursor = true;
}

```

Only when `self.vx.caps.multi_cursor` is true does the editor enable secondary-cursor rendering and invoke the display functions.

## Layer 3: Rendering Secondary Cursors via Vaxis

### Clearing Multi-Cursors

The renderer sends "clear all secondary cursors" escape sequences through the underlying Vaxis library:

```zig
// src/renderer/vaxis/renderer.zig (lines 586-588)
pub fn clear_all_multi_cursors(self: *Self) !void {
    try self.vx.resetAllTerminalSecondaryCursors(self.allocator);
}

```

### Positioning Hardware Cursors

For each secondary cursor, the editor calls `show_multi_cursor_yx(y, x)`, which forwards coordinates to Vaxis for translation into terminal-specific escape codes:

```zig
// src/renderer/vaxis/renderer.zig (lines 590-592)
pub fn show_multi_cursor_yx(self: *Self, y: c_int, x: c_int) !void {
    try self.vx.addTerminalSecondaryCursor(self.allocator, @intCast(y), @intCast(x));
}

```

In the UI code (`src/tui/editor.zig`), secondary cursors are drawn in soft-render mode when the terminal cannot draw hardware cursors; otherwise, `render_term_cursor_secondary` forwards the request:

```zig
// src/tui/editor.zig (lines 59-61)
inline fn render_term_cursor_secondary(self: *Self, pos: Cursor) void {
    const y, const x = self.plane.rel_yx_to_abs(@intCast(pos.row), @intCast(pos.col));
    tui.rdr().show_multi_cursor_yx(y, x) catch return;
}

```

### Windows Support Status

The Win32 renderer currently contains only a placeholder stub at lines 500-504 of `src/renderer/win32/renderer.zig`, indicating that native Windows console multi-cursor support remains a work-in-progress.

## Key Bindings and User Commands

Multi-cursor editing in Flow Control is exposed through specific commands mapped in `src/keybind/parse_flow.zig`. Users can bind these to keys in their configuration:

```zig
// src/keybind/parse_flow.zig (line 55 and surrounding)
"Ctrl-d" => command.add_cursor_next_match,   // Add cursor at next match
"Ctrl-Shift-d" => command.add_cursor_all_matches, // Add cursors to all matches
"Ctrl-Shift-l" => command.add_cursors_to_line_ends, // Add cursors to line ends

```

When `enable_terminal_cursor` is set to true in the configuration, Flow Control utilizes hardware cursors where possible:

```toml

# flow/config.toml

[editor]
enable_terminal_cursor = true      # required for hardware multi-cursors

```

## Summary

- **Flow Control** implements multi-cursor editing through a three-tier architecture separating data management from terminal rendering.
- The **editor core** (`src/tui/editor.zig`) maintains a list of `CurSel` objects with methods like `add_cursor_next_match` and `collapse_cursors` to handle cursor lifecycle and deduplication.
- **Capability detection** (`src/renderer/vaxis/renderer.zig`) checks for `cap_multi_cursor` before enabling hardware cursor features.
- The **rendering layer** translates cursor coordinates into terminal escape sequences using Vaxis functions `addTerminalSecondaryCursor` and `resetAllTerminalSecondaryCursors`.
- Windows support remains pending with a TODO stub in `src/renderer/win32/renderer.zig`.

## Frequently Asked Questions

### What is the CurSel struct in Flow Control?

The `CurSel` struct defined in `src/tui/editor.zig` is the fundamental data structure representing both a cursor position and an optional selection range. Flow Control stores these in a list called `cursels` where the first element serves as the primary cursor and subsequent elements function as secondary cursors for multi-cursor editing operations.

### How does Flow Control detect if my terminal supports multi-cursor editing?

During initialization, the Vaxis renderer in `src/renderer/vaxis/renderer.zig` parses the terminal's capability string. When it encounters the `cap_multi_cursor` token, it sets `self.vx.caps.multi_cursor = true`. The editor only attempts to render hardware secondary cursors when this flag is enabled, falling back to software rendering otherwise.

### Can I use multi-cursor editing on Windows?

Currently, multi-cursor editing on Windows is not fully implemented. The Win32 renderer at `src/renderer/win32/renderer.zig` contains only a TODO placeholder (lines 500-504) where the multi-cursor rendering logic will eventually reside. Windows users currently rely on the standard single-cursor mode until this stub is implemented.

### What happens when two cursors overlap in Flow Control?

When secondary cursors land on the same position or selection, the editor invokes `collapse_cursors` (line 1978 in `src/tui/editor.zig`). This function deduplicates overlapping cursors by merging their selections using `a.merge(b_sel)` or discarding exact duplicates, ensuring that only one cursor remains at any given position to prevent conflicting edits.