Skip to content

Editor draws + highlights + selects in OnDrawingContent — needs the spec §6 visual-line pipeline #28

@tig

Description

@tig

Summary

Editor.OnDrawingContent walks the document directly every frame and inlines three concerns that the spec splits cleanly: per-line rendering, syntax highlighting, and selection background. The shape works pre-MVP but several scaling and correctness gaps are baked in. The fix is the planned VisualLineBuilderCellVisualLineIVisualLineTransformer / IBackgroundRenderer pipeline (specs/00-plan.md §6); these symptoms collapse together when it lands.

Symptoms (each will resolve when §6 ships)

1. UpdateContentSize is O(N lines) per edit

Editor.cs:167-178 walks every DocumentLine to recompute maxWidth on every Document.Changed. Fine on small files; on the editing hot path of a 10k-line document this is per-keystroke.

2. Duplicate line lookups per frame

UpdateCursor (Editor.Drawing.cs:213-237) and EnsureCaretVisible (Editor.cs:208-244) each call GetCaretLineIndex() and GetCaretColumn() back-to-back; both walk the line index. Same wasted work in the loop body of OnDrawingContent (rebuilding line text each frame).

3. Char-length, not cell-width

Editor.Drawing.cs:44-50 and Editor.Mouse.cs:91-96 both treat the viewport as character-indexed. Wide CJK / emoji / combining marks misalign rendering AND make mouse clicks hit the wrong offset. Spec §6 calls this out — measurement is grapheme + string.GetColumns().

4. Syntax highlighter re-tokenizes from line 0 every frame

Editor.Drawing.cs:72-86 (PrepareSyntaxHighlighter) calls Highlight() on every line from 0 up to the first visible line on every redraw. Scroll to line 9,000 of a 10k-line file → 9,000 highlight calls per redraw. The highlighter is re-fed the entire above-viewport text every time the cursor blinks (or a single keystroke arrives).

5. Selection rendered inline as 3-way string slicing

DrawLineRuns / DrawRun / DrawRunPart (Editor.Drawing.cs:88-211) split each visible run into pre-selection / in-selection / post-selection sub-runs and switch attributes manually. Per spec §6 this is exactly what an IBackgroundRenderer exists for — paint cell rectangles for the selection, draw text once with the syntax-derived attribute. Today's implementation also can't compose with future renderers (current-line highlight, search-hit highlight) without each one reaching back into the same string-slicing code.

What §6 changes

DocumentLine ──▶ VisualLineBuilder ──▶ CellVisualLine (one or more CellVisualLineElements)

IVisualLineTransformer[] ── set Attribute on element ranges
IBackgroundRenderer[] ── paint cell rectangles (selection, current line, search hits)

A HighlightingColorizer : IVisualLineTransformer replaces today's PrepareSyntaxHighlighter + inline run-splitting. A SelectionBackgroundRenderer : IBackgroundRenderer replaces the 3-way slicing. Visual lines cache; Document.Changed invalidates only the affected range; line-width tracking updates incrementally.

Recommendation

Treat the §6 pipeline as the next big architectural milestone. Until then, add // TODO: cells, not chars — see specs/00-plan.md §6 markers to (3) and tracking-issue links from each touch point so it doesn't become invisible tech debt.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions