Built from scratch  ·  C++ & Qt

Write code
at the speed
of thought.

A minimal, blazing-fast code editor built entirely from scratch in C++ and Qt. No Electron. No bloat. Just pure engineering.

Download for Windows View source →
main.cpp — Valence
0ms
startup time
~2k
lines of C++
0
dependencies
60fps
render loop
5wks
built in

Built the right way,
from the ground up.

Zero-overhead rendering

Custom QPainter loop draws only visible lines. No layout engine. No DOM. Sub-millisecond frame times even on large files.

Logical cursor model

Cursor tracked as (row, col) and converted to pixels only at paint time. Eliminating an entire class of rendering bugs.

C++ syntax highlighting

Hand-written tokenizer with no regex overhead. Identifies keywords, types, strings, and comments in a single O(n) pass.

Native file I/O

Open and save any file instantly with Qt's native file dialogs. Reads directly into the line buffer — no intermediate copies.

Line number gutter

Rendered with a fixed 50px offset. Current line highlighted. Gutter background separated with a subtle 0.5px border.

Smart auto-indent

On Enter, the editor copies the leading whitespace from the current line. Keeps your indentation intact across the session.

Five clean
subsystems.

Each layer has a single responsibility. The buffer owns the data. The renderer owns the pixels. They never talk directly — only through the cursor.

buf

Text Buffer

std::vector<std::string> — one string per line. Insert, delete, split, merge.

O(1) line access
cur

Cursor System

Struct { int row; int col }. Logical position only — pixels are derived, never stored.

decoupled from render
rnd

Rendering Engine

QPainter in paintEvent. Draws only the visible viewport window, token by token.

viewport culling
inp

Input Handler

keyPressEvent routes chars to buffer, arrows to cursor, and calls update() once per event.

single repaint/event
lex

Tokenizer

Single-pass lexer classifies tokens into keyword, type, string, comment, or plain text.

O(n) per line

Five weekends.
One editor.

1
Weekend 1
Foundation
TextBuffer class. Qt project skeleton. Static text renders to screen via QPainter.
2
Weekend 2
The Typewriter
Cursor renders and blinks. Type characters, move left/right, backspace on a single line.
3
Weekend 3
Full Navigation
Multi-line editing, Enter splits lines, Backspace merges. Viewport scrolling with wheelEvent.
4
Weekend 4
File I/O & Syntax
Open and save real .cpp files. Tokenizer colors keywords, strings, and comments.
5
Weekend 5
Polish & MVP
Line number gutter. Auto-indent. Edge case fixes. Resume-ready MVP ships.

Why every
choice matters.

  • Array of Lines over Rope

    Rope trees are powerful but overkill. For files under 20k lines, vector<string> is faster to implement and reason about — and finishes in the time budget.

  • Logical cursor, not pixel cursor

    Tracking cursor in (row, col) and converting to pixels at render time eliminates an entire class of drift bugs when the font changes or the viewport scrolls.

  • Viewport culling from day one

    Only draw lines between startRow and endRow based on scrollY. A 10,000-line file runs at the same FPS as a 10-line file.

  • Monospace font enforced at init

    QFont initialized with a fixed monospace font at startup. Variable-width fonts break all cursor math — this constraint is non-negotiable.

TextBuffer.cpp
void TextBuffer::insertChar(int row, int col, char ch) { lines[row].insert(col, 1, ch); } void TextBuffer::deleteChar(int row, int col) { if (col == 0) { mergeLines(row); return; } lines[row].erase(col - 1, 1); } void TextBuffer::splitLine(int row, int col) { std::string tail = lines[row].substr(col); lines[row] = lines[row].substr(0, col); lines.insert(lines.begin() + row + 1, tail); } void TextBuffer::mergeLines(int row) { if (row == 0) return; lines[row-1] += lines[row]; lines.erase(lines.begin() + row); }

Open source

Built in the open.
Free forever.

Clone the repo, study the code, build on it. Valence is a proof that systems software doesn't need frameworks.

View on GitHub Download binary →