📘 CmdTextC Buffer & Display Subsystems – Comprehensive Technical Documentation
1. Executive Summary
The Buffer & Display Subsystems form the foundational data management and presentation layers of the CmdTextC terminal-based text editor. Implemented in pure C99, these modules provide a robust, high-performance architecture for in-memory text storage, dynamic row manipulation, syntax highlighting buffer allocation, coordinate translation, snapshot-based undo/redo, and ANSI terminal rendering. The buffer module handles raw character storage, tab expansion, render string generation, and state persistence. The display module manages viewport scrolling, cursor positioning, syntax-aware color emission, status/messaging bars, and direct terminal I/O via optimized buffer construction. Together, they deliver a deterministic, zero-external-dependency editing core optimized for fast interactive sessions and predictable memory behavior.
2. System Architecture & Design Philosophy
| Design Principle |
Implementation Strategy |
| Dual-Buffer Row Model |
Each EditorRow maintains chars (raw input) and render (tab-expanded display). Separation ensures editing precision while guaranteeing accurate visual alignment. |
| Highlight Array Decoupling |
Syntax highlighting is stored in a parallel unsigned char *hl array matching render length. Rendering loop applies ANSI codes dynamically without mutating text data. |
| Snapshot-Based History |
Undo/Redo captures deep copies of the entire buffer state. Trade-off: higher memory footprint for O(1) rollback complexity and complete state fidelity. |
| Terminal-Optimized Rendering |
Constructs a contiguous ANSI-encoded string buffer (screenbuf[64KB]) in memory before a single termWrite() syscall. Minimizes I/O latency and terminal flicker. |
| Viewport-Centric Scrolling |
Calculates rowoff/coloff dynamically against terminal dimensions. Guarantees cursor visibility while preserving edit position integrity. |
| Strict Lifecycle Management |
Explicit calloc/malloc/free patterns. Zero global mutable state. All resources tied to EditorBuffer or EditorDisplay structs. |
3. Core Data Structures
3.1 EditorRow
typedef struct {
int size; // Raw character count in chars[]
char *chars; // Raw input buffer (includes '\t', raw newlines excluded)
char *render; // Display buffer (tabs expanded to spaces)
int rsize; // Rendered character count
unsigned char *hl; // Parallel highlight array (1 byte per render char)
int hl_open_comment; // Cross-row multiline comment state (0/1)
int idx; // Row index in buffer
TabStop *tabs; // Dynamic array tracking tab stop positions & lengths
int tab_count; // Active tab stops
int tab_cap; // Allocated tab stop capacity
} EditorRow;
- Dual-Storage Rationale:
chars preserves exact user input for serialization. render normalizes whitespace for terminal alignment and syntax token mapping.
- Highlight Mapping:
hl[i] stores an HlColor enum value corresponding to render[i]. Enables per-character ANSI coloring without string duplication.
3.2 EditorBuffer
typedef struct {
EditorRow *rows; // Dynamic array of row structs
int count; // Current active rows
int capacity; // Allocated row capacity
int dirty; // Modification flag (triggers save prompt)
char *filename; // Associated file path
int num_undo; // Active undo states in stack
UndoState *undo_stack; // Fixed-size circular stack (256)
int tab_size; // Tab expansion width (default: 4)
} EditorBuffer;
- Dynamic Growth: Initial capacity
128. Doubles via realloc when count == capacity.
- Undo Stack: Pre-allocated
256 slots. Oldest state discarded when full.
3.3 UndoState
typedef struct {
int last_cx, last_cy, last_action;
EditorRow *rows; // Deep-copied row snapshot
int row_count; // Number of rows in snapshot
char *msg; // Contextual message prefix
char msg_prefix;
long long saved_msgs; // Reserved for message history
} UndoState;
- Deep Copy Semantics: Captures
chars, render, hl, and tabs for every row at time of action.
- Restoration Logic: Frees current buffer state, reallocates from snapshot, restores cursor position.
3.4 EditorDisplay
typedef struct {
int rowoff, coloff; // Viewport scroll offsets (top row, left column)
int cx, cy, rx; // Cursor position: raw index, row index, render index
int mode; // Editor mode (0=NORMAL, 1=INSERT, 2=COMMAND, etc.)
char statusmsg[256]; // Transient status message
time_t statusmsg_time; // Message timestamp for auto-expiry
int search_hit; // Current search match index
int search_dir; // Search direction (1=forward, -1=backward)
int show_line_numbers; // Toggle line number rendering
int wrap_lines; // Line wrapping flag (reserved)
} EditorDisplay;
- Viewport Logic:
cy - rowoff determines visible row range. rx - coloff determines visible column range.
- Message Timeout:
MSG_TIMEOUT = 5 seconds. Automatically clears after expiry.
4. Buffer Management Subsystem (buffer.c / buffer.h)
4.1 Row & Character CRUD
| Function |
Complexity |
Behavior |
bufferInsertRow |
O(N) |
Inserts row at index. Shifts subsequent rows via memmove. Expands capacity if needed. Marks dirty=1. |
bufferDeleteRow |
O(N) |
Frees row resources. Shifts remaining rows. Updates idx fields. |
bufferRowInsertChar |
O(L) |
Inserts character at position. Reallocates chars (+2 bytes). Shifts suffix. |
bufferRowDeleteChar |
O(L) |
Removes character at position. Reallocates chars (-1 byte). Shifts suffix. |
bufferRowAppendString |
O(S) |
Appends string to row end. Reallocates. Copies via memcpy. |
4.2 Tab Expansion & Render Generation
bufferUpdateRow() performs the critical transformation from raw input to display-ready format:
- Counts
\t characters to calculate required buffer size.
- Allocates
render string: size + tabs_count * (tab_size - 1) + 1.
- Iterates
chars:
- On
\t: Calculates spaces to next tab stop (tab_size - (idx % tab_size)). Records TabStop entry. Fills render with spaces.
- On other chars: Direct copy to
render.
- Allocates
hl array of size rsize, initialized to HL_NORMAL.
- Updates
rsize, tab_count, and tab stop array.
4.3 Coordinate Translation
| Function |
Purpose |
Complexity |
bufferRowCxToRx |
Converts raw char index (cx) to visual/render index (rx) |
O(cx) |
bufferRowRxToCx |
Converts visual index (rx) to raw char index (cx) |
O(cx) |
- Handles tab width mapping. If
tabs array is null, defaults to tab_size=4 fallback.
- Essential for accurate cursor placement, scrolling boundaries, and syntax highlight mapping.
4.4 Buffer Serialization
bufferRowsToString():
- Calculates total length including newline separators.
- Allocates contiguous buffer.
- Copies
chars from each row, inserts \n between rows.
- Returns heap-allocated string with length via pointer output. Used for file saving.
5. Display & Rendering Engine (display.c / display.h)
5.1 Viewport & Scrolling Logic
/* Scroll vertical boundaries */
if (d->cy < d->rowoff) d->rowoff = d->cy;
if (d->cy >= d->rowoff + screen_body_rows) d->rowoff = d->cy - screen_body_rows + 1;
if (d->rowoff < 0) d->rowoff = 0;
screen_body_rows = screenrows - 2 (reserves 1 row for status bar, 1 for message bar).
- Guarantees cursor (
cy) remains visible within viewport without manual scroll commands.
5.2 Screen Buffer Construction Pipeline
The displayRefresh() function builds a single ANSI-encoded string in screenbuf[65536]:
- Header:
\x1b[?25l (hide cursor) + \x1b[H (move to home).
- Line Numbers (Optional): Calculates width based on
buf->count. Pads with spaces. Colors via HL_COMMENT.
- Row Rendering Loop:
- Computes visible slice:
coloff to coloff + text_cols.
- Iterates
render chars. Checks hl[i]:
- If
HL_NORMAL: Emits default color.
- If colored: Emits
\x1b[<code>m via syntaxToColor().
- Control chars (
\0-\x1A): Displayed as @+char in inverse video (\x1b[7m).
- Terminates line with
\x1b[K (clear to end of line).
- Status Bar: Reverse video (
\x1b[7m). Displays filename, dirty flag [+], mode, line count, cursor coords, filetype. Pads to screen width.
- Message Bar: Displays
statusmsg if time() - statusmsg_time < MSG_TIMEOUT.
- Cursor Positioning: Calculates absolute terminal coords. Emits
\x1b[y;xH + \x1b[?25h (show cursor).
- Output: Single
termWrite(screenbuf, sb_len) syscall.
5.3 Status & Message Management
displaySetStatusMessage(): Formats string via vsnprintf, records timestamp. Auto-expires after 5 seconds during refresh.
- Mode indicator maps integers to strings:
0=NORMAL, 1=INSERT, 2=COMMAND, 3=SEARCH, 4=VISUAL, 5=REPLACE.
6. Undo/Redo State Management
6.1 State Capture (bufferPushUndo)
- Discards states beyond current
num_undo pointer (trims redo history on new action).
- If stack full (
256), frees oldest state, shifts array left.
- Calls
undoStateCapture():
- Deep copies
EditorRow array: allocates new chars, render, hl, tabs for each row.
- Stores cursor position (
last_cx, last_cy) and action type.
- Increments
num_undo.
6.2 State Restoration (bufferUndo / bufferRedo)
- Decrements/increments
num_undo pointer.
- Frees all current
rows, chars, render, hl, tabs in active buffer.
- Reallocates buffer arrays from snapshot.
- Deep copies snapshot data back into active buffer.
- Restores
cx, cy to captured positions.
- Marks buffer
dirty implicitly via structural replacement.
Complexity: O(R * L) per undo/redo, where R = row count, L = avg line length. High memory cost, instantaneous execution.
7. API Reference
7.1 Buffer API
| Function |
Signature |
Description |
bufferCreate |
EditorBuffer *bufferCreate(void); |
Allocates & initializes buffer. Inserts empty row. |
bufferFree |
void bufferFree(EditorBuffer *buf); |
Frees all rows, undo stack, and buffer struct. |
bufferInsertRow |
int bufferInsertRow(EditorBuffer *buf, int at, const char *s, size_t len); |
Inserts row at index. Returns new index or -1. |
bufferDeleteRow |
void bufferDeleteRow(EditorBuffer *buf, int at); |
Deletes row at index. Updates indices. |
bufferRowInsertChar |
int bufferRowInsertChar(EditorBuffer *buf, EditorRow *row, int at, int c); |
Inserts character at raw index. |
bufferRowDeleteChar |
void bufferRowDeleteChar(EditorBuffer *buf, EditorRow *row, int at); |
Deletes character at raw index. |
bufferUpdateRow |
void bufferUpdateRow(EditorBuffer *buf, EditorRow *row); |
Rebuilds render, hl, and tabs. Call after text mutation. |
bufferRowsToString |
char *bufferRowsToString(EditorBuffer *buf, int *buflen); |
Serializes buffer to heap-allocated string. |
bufferPushUndo |
void bufferPushUndo(EditorBuffer *buf, int action_type, int cx, int cy); |
Captures current state for undo. |
bufferUndo |
void bufferUndo(EditorBuffer *buf, int *cx, int *cy); |
Restores previous state. Updates cursor. |
bufferRedo |
void bufferRedo(EditorBuffer *buf, int *cx, int *cy); |
Re-applies undone state. Updates cursor. |
7.2 Display API
| Function |
Signature |
Description |
displayInit |
void displayInit(EditorDisplay *d); |
Zero-initializes display state. |
displayRefresh |
void displayRefresh(EditorDisplay *d, EditorBuffer *buf, struct EditorSyntax *syn); |
Renders full screen to terminal. |
displaySetStatusMessage |
void displaySetStatusMessage(EditorDisplay *d, const char *fmt, ...); |
Sets formatted status message with 5s timeout. |
8. Rendering Pipeline & Terminal I/O
[Input Event] → bufferMutation() → bufferUpdateRow() → syntaxHighlight()
↓
[Refresh Trigger] → displayRefresh()
↓
[Viewport Calc] → rowoff/coloff boundaries → screenrows/screencols
↓
[Screen Buffer Build] → sbAppend() + ANSI codes + syntaxToColor()
↓
[Status/Message] → Reverse video + timeout check + padding
↓
[Cursor Placement] → \x1b[y;xH + \x1b[?25h
↓
[I/O Flush] → termWrite(screenbuf, sb_len)
↓
[Terminal Emulator] → Renders frame instantly
ANSI Code Mapping:
\x1b[?25l / \x1b[?25h: Hide/Show cursor
\x1b[H: Cursor home
\x1b[K: Clear to end of line
\x1b[7m / \x1b[27m: Enter/Exit inverse video (status bar, control chars)
\x1b[<N>m: Set foreground color (31-37, 90-97)
\x1b[y;xH: Absolute cursor positioning
9. Performance, Memory & Complexity Analysis
| Metric |
Value / Behavior |
Notes |
| Render Time |
O(R * C) per refresh, where R = visible rows, C = visible cols |
Highly optimized via contiguous buffer build |
| Memory Overhead |
~3 * L bytes per row (chars + render + hl) + tab stops |
Linear scaling. Predictable heap usage |
| Undo Memory |
O(R_total * L_avg * UNDO_STACK_SIZE) |
Peak ~256 full buffer copies. Suitable for <5MB files |
| I/O Efficiency |
1 write() syscall per frame |
Eliminates per-char/line terminal latency |
| Tab Expansion |
O(L) per row update |
Cached in tabs[] array. Fast coordinate conversion |
| Thread Safety |
Not thread-safe |
Designed for single-threaded main loop |
10. Integration Guidelines
10.1 Initialization Sequence
EditorBuffer *buf = bufferCreate();
EditorDisplay d;
displayInit(&d);
SyntaxRegistry *syn = syntaxInit();
// Load file, run syntaxSelectHighlight(syn, buf)
10.2 Edit Loop
while (running) {
displayRefresh(&d, buf, syn->current);
int key = termReadKey();
inputProcessKeypress(key, buf, &d, syn, &running);
}
10.3 Best Practices
- Always call
bufferUpdateRow() after modifying row->chars or row->size.
- Push undo states before mutation, not after.
- Use
bufferRowCxToRx for cursor positioning logic; use bufferRowRxToCx for hit-testing.
- Clear
d.statusmsg_time = 0 to force immediate message display.
11. Known Limitations & Engineering Roadmap
| Limitation |
Impact |
Recommended Mitigation |
| Full snapshot undo |
High RAM usage for large files |
Implement incremental delta logging or copy-on-write row pointers |
| Fixed 64KB screen buffer |
Limits rendering on ultra-wide terminals |
Switch to dynamic realloc or chunked I/O writes |
CxToRx/RxToCx tab lookup bug |
row->tabs[0] always accessed instead of matching stop |
Iterate tabs[] array or use binary search for exact match |
| No line wrapping |
Long lines require horizontal scroll |
Implement soft-wrap rendering layer with virtual line mapping |
| Hardcoded tab size |
No per-file or user-configurable tabs |
Load tab_size from config or file shebang/metadata |
| Blocking refresh |
UI freezes during heavy syntax parse |
Decouple highlight computation to background thread or async queue |
🚀 v2.0 Roadmap:
- Incremental undo with operation logs (
InsertChar, DeleteLine, etc.)
- Virtual scrolling & infinite canvas support
- Dynamic screen buffer allocation
- Configurable tab size & soft-wrap engine
- Async syntax highlighting pipeline
- Terminal capability detection (TrueColor, mouse tracking, bracketed paste)
12. Appendix: Constants & Enumerations
12.1 Highlight Color Enum (HlColor)
typedef enum {
HL_NORMAL = 0, // Default text
HL_COMMENT, // Single-line comment
HL_MLCOMMENT, // Multi-line comment
HL_KEYWORD1, // Control flow, loops
HL_KEYWORD2, // Secondary keywords
HL_STRING, // String literals
HL_NUMBER, // Numeric literals
HL_MATCH, // Bracket/quote match
HL_SEARCH_MATCH, // Search highlight
HL_ERROR, // Control chars, syntax errors
HL_PREPROC, // Preprocessor directives (#)
HL_TYPE, // Data types, built-ins
HL_FUNCTION, // Function calls/declarations
HL_CONSTANT, // Constants, enums
HL_OPERATOR, // (){}[]
} HlColor;
12.2 Editor Modes
| Value |
Mode |
Description |
0 |
NORMAL |
Navigation, command execution |
1 |
INSERT |
Text insertion |
2 |
COMMAND |
: prompt for save/quit/search |
3 |
SEARCH |
/ or ? prompt for find |
4 |
VISUAL |
Selection mode |
5 |
REPLACE |
Overwrite mode |
12.3 Key Internal Constants
| Constant |
Value |
Purpose |
INITIAL_ROWS |
128 |
Starting row capacity |
UNDO_STACK_SIZE |
256 |
Max undo history depth |
TAB_SIZE |
4 |
Default tab expansion width |
MSG_TIMEOUT |
5 |
Status message expiry (seconds) |
screenbuf size |
65536 |
Max ANSI render buffer (64KB) |
📄 Document Version: 1.0
📅 Last Updated: May 2026
🔧 Target Platform: POSIX / Windows Terminal Emulators
📦 Source Reference: buffer.h, buffer.c, display.h, display.c
🏷️ Tags: #TextEditorCore #ANSIRendering #BufferManagement #UndoRedo #TerminalUI #C99 #SyntaxHighlighting #ViewportScrolling #ZeroDependency #PerformanceOptimized
Log in or sign up for Devpost to join the conversation.