Skip to content

Releases: pavel-zheltiakov/AvaCodeEditor

AvaCodeEditor v12.0.0-preview.2

Choose a tag to compare

Collapsible regions

CodeEditor.Folding takes an IFoldingProvider — asked, per line, where a collapsible region
STARTS, exactly like the other providers, so a host answers out of a syntax tree or an indentation
scan of the visible window rather than by parsing the file. The editor owns which regions are
CLOSED, because that is the user's state and it has to survive a re-parse.

  • Collapse, Expand, ToggleFold, ExpandAll, IsCollapsed, CollapsedRegions.
  • FoldingMargin draws the chevrons and toggles on a click; hand it editor.FoldingState.
  • A collapsed region leaves a boxed placeholder after its header's text — the provider's
    FoldRegion.Placeholder, or an ellipsis.
  • Up and down arrows count ROWS, so a collapsed region is one step and not the fifty lines it
    hides. Collapsing over the caret puts the caret on the header. A fold whose header an edit
    deletes opens; the rest move with the lines they hide.

Screen rows and document lines are no longer the same number. Extent.Height is now the
visible ROW count, and EditorViewport.VisibleLineCount counts rows. A margin that draws every
line from FirstVisibleLine to FirstVisibleLine + VisibleLineCount still compiles and still
works when nothing is folded; past a collapsed region it stops early instead of drawing hidden
lines on top of the header. Iterate MarginContext.VisibleLines instead and neither happens —
it yields exactly the document lines on screen, and allocates nothing.

CodeLens

CodeEditor.CodeLens takes an ICodeLensProvider: a list of anchor lines, and the items to draw
on the row above each. Items are CodeLensItem(Text, Tooltip, Invoke) — drawn on the annotated
line's own indentation, underlined on hover, and invoked on RELEASE only where the press began.

The anchors are a LIST rather than a per-line question, unlike every other provider, and
deliberately: the editor needs the number of lens rows in the whole document to know how tall the
document is, and asking a million lines that question is the one thing this control will not do.

Rectangular selection and multiple carets

  • Alt+drag a rectangle, or Shift+Alt+arrow to open one from the caret (Shift+Alt+Cmd+arrow
    on macOS, where Alt+arrow is already word movement).
  • Ctrl/Cmd+Alt+click adds a caret anywhere. Escape goes back to one.
  • Typing, Backspace and Delete happen at every caret, applied last-to-first so each caret keeps the
    character it was on — including several carets on the SAME line, where the ordinary position
    transform cannot help. A typing run at N carets is one undo step.
  • Copying takes every selection, top to bottom, one per line, so a column pastes as a column.
  • SetBoxSelection, AddCaret, CollapseToPrimaryCaret, AdditionalSelections, BoxSelection,
    HasMultipleCarets.

Context popovers

PopoverRequested fires on a right-click, on the context-menu key and from RequestPopover. It
hands the host the position, the line, the WORD under it, the selection, and the TextMate scopes
— which is what makes a popover context-dependent without the editor learning any languages. Set
Content and the editor anchors it to that character cell and dismisses it on Escape, a click
away, a scroll, or an edit; set Handled and the editor does nothing further.

GetCellRect(TextPosition) is public, so a host that would rather place its own overlay can.

A right-click INSIDE the selection leaves it alone; anywhere else it moves the caret first.

Opening a document no longer reads it

A document used to be measured line by line as it was set: every line walked a grapheme cluster at
a time to find the widest one, so the horizontal scrollbar could be exact. That is the one cost
proportional to the file, and it is gone. Lines are measured when they first come on screen.

What changes for a host: CodeEditor.Extent.Width now covers every line that has been on
screen
, not every line in the document, and it grows as wider lines are scrolled into view — the
way every editor that virtualizes behaves. Extent.Height is unchanged and still exact, because a
line count is known without reading a line. Nothing else moves: the widest visible line is measured
before layout reads the extent, so a scrollbar is never wrong about what is on screen.

A million-line document now opens in under a millisecond, and the demo's scale scenario does
exactly that — see GeneratedBuffer in the demo for a host-supplied ITextBuffer that composes a
line when the control asks for it and stores nothing.

Documentation

A full manual ships with the site: docs.html, a contents tree beside the text, every public type
with the problem it solves and the code that uses it, diagrams of the character grid, of screen
rows against document lines, and of the highlighting pipeline, plus screenshots taken from the
demo. The landing page gains a half-minute tour recorded from the browser demo and a screenshot on
every feature card.

AvaCodeEditor v12.0.0-preview.1

Choose a tag to compare

The first published version: the two packages, the site, and a demo application you can run.
A preview because the API surface is still forming — see the notes below for what a host plugs
into, and expect those interfaces to be the ones that settle first.

The demo runs in a browser

demo/AvaCodeEditor.Demo.Browser publishes the same demo to WebAssembly, and the release drops it
into the site — so the landing page runs the editor rather than describing it. Needs the
wasm-tools workload to build.

A demo application ships with the release

demo/AvaCodeEditor.Demo — a themed sample with eight scenarios, one per thing a host plugs in:
editing, syntax highlighting, a gutter of your own, actions anchored to a block of lines, an
annotation column that arrives asynchronously, line and sub-line backgrounds, a hundred thousand
lines, and themes with a host overriding them.

It is published in the public repository alongside the packages, and it references
AvaCodeEditor as a package there — the same way your own project does. One demo source
serves both repositories: here it references the projects, so it exercises the working tree; there
AvaCodeEditorFromPackage switches it to the published NuGets. scripts/release.sh builds that
public copy against the freshly packed packages and runs its --smoke check before anything is
pushed, so a release that would not compile for a consumer fails here first.

--smoke builds every scenario in both appearances and exits — it is what CI and the release
script run, because nothing else finds out that the sixth scenario throws without clicking it.

Sub-line backgrounds — ISegmentBackgroundProvider, CodeEditor.SegmentBackgrounds

Runs of characters painted under the text inside one line — in-line (word-level) diff
highlighting being the case it was built for. The counterpart of ILineBackgroundProvider: that
one paints a whole line, this one paints inside it, and either may be set without the other.

  • A run is (Start, Length, Brush) in character offsets, never visual columns. The editor
    maps them to cells itself, through the same code the caret and the selection use — a host
    cannot do it, because a tab is one character and up to TabSize cells and a grapheme cluster
    is several characters and one cell.
  • Painting order is band → runs → selection → glyphs. A run stays visible against the band it
    sits in; a selection stays visible over everything.
  • Offsets are clamped to the line: they describe the document as you last read it, and the
    buffer moves on under them. A run of no width paints nothing. Nothing is sorted, merged or
    de-overlapped — two runs over one character paint twice.
  • Changed may be raised from any thread, like ILineBackgroundProvider.Changed; the editor
    marshals and repaints. Subscriptions are dropped when the control leaves the visual tree.

Line annotations — ILineAnnotationProvider, LineAnnotationMargin

Per-line metadata in a gutter column of its own — git blame being the case it was built for.
The host implements the provider; the editor knows nothing about git.

  • Annotations may arrive late: raise AnnotationsChanged when a lookup completes and the
    column repaints. Nothing blocks the render pass waiting for data.
  • The column's width is declared, not measuredMaxCharacters (clamped 1..64), in character
    cells. A column measured from whatever annotations happen to be visible would slide the whole
    text area sideways as the user scrolled onto a longer author name.
  • What does not fit is ellipsised, one cell short of the edge, rather than clipped: a hard cut
    hides that anything was removed, and pzh 2026-07-2 reads as a date rather than as a truncation.
  • A run of consecutive lines carrying the same annotation is labelled once (CollapseRepeats,
    on by default), at the run's first visible line — so a commit taller than the viewport is still
    labelled while you are reading the middle of it. That row is nudged into view when the scroll
    offset lands part-way down it, which a trackpad does constantly, unless the row below carries a
    label of its own and the nudge would print one over the other.
  • The caret's line is answered at full contrast by its run's label, wherever inside the run the
    caret is — including from above the top of the viewport, which is where a caret ends up when you
    scroll down through a long commit. Which label that is costs the viewport, not the document: it
    is chosen from the labels on screen, so the provider is asked about the visible rows and the
    caret's line, and nothing else.
  • GetTooltip answers per line, including on rows whose label was collapsed away or cut short,
    so the full text is always reachable. An empty Tooltip falls back to the label rather than
    popping an empty balloon.
  • The column consumes no press: clicks and drags pass through to the caret.

Margins that fail no longer take the editor with them

Margins are host code called from inside the editor's render pass, which made a host bug a crash
that loses the unsaved document.

  • IEditorMargin.Render and GetWidth are wrapped per margin, per call. One faulting margin
    costs one column; the text, selection and caret are already on the frame when gutters draw, and
    the remaining columns still draw.
  • A throwing GetWidth keeps the width it last reported. A margin that has never measured
    successfully has no width to keep and gets zero — that includes one added back to Margins after
    being removed, since what the editor remembers about a margin goes with it. A width that is not
    finite counts as a fault: NaN would otherwise take the text area's origin with it.
  • Faults surface on CodeEditor.MarginFailed, raised once per fault rather than once per
    frame, and again when a margin starts working. It arrives after the frame the fault happened
    in, so handling it by removing the margin, replacing it, or re-laying out is safe — and a handler
    that throws costs neither the frame nor the other handlers. Margin is always a margin still in
    Margins, on an editor still attached: a report whose margin was removed, or whose editor was
    detached, in the moment between the frame and the delivery is dropped rather than handed to a
    handler with nothing left to act on. It is still logged.
  • Pointer callbacks (OnPointerPressed and friends) are left to propagate, as a change
    action's Execute is: they run once, because of something the user did, so swallowing them
    hides a real bug rather than surviving a frame. The exception is the hover refresh something
    other than the pointer drives — a scroll, or a re-measure that moved the columns under a
    motionless pointer: nobody touched the gutter, and the throw would land in a layout pass, so that
    path reports like a margin that cannot draw, naming the margin whose call actually threw.
  • A change to Margins cancels a held press, and the margin is told. Any change: the press is
    aimed at a COLUMN, and an add, a move or a removal anywhere renumbers the columns under a button
    already down. Sliding off is a cancel; so is the gutter being rebuilt underneath.

Gutter text

  • MarginContext.GutterText(scale) is the shared arithmetic for text drawn at a fraction of the
    row — the size, the per-cell advance and the baseline. The row it scales is bounded at 1.32 em,
    because past that a row is leading rather than text: sizing from all of it gives a cell more room
    than the column reserved, and the label runs past its own edge where the clip removes the ellipsis
    that said it was truncated. Gutter text therefore stays a notch below the code whatever the
    typeface's line height is — a CJK monospace fallback included, which is the case this is for.

Change actions — IChangeActionProvider, ChangeActionMargin

  • Actions run on pointer release, and only when the release lands on the same action, by Id,
    that the press began on — a push is destructive and a click begun by accident must be takeable
    back. A Changed from the provider cancels a held press, so a re-diff cannot slide a different
    block's identically-named action under the button.
  • ChangeAction.Id now carries a written contract: it identifies the action and its target.
    Run collapsing and the press/release match both depend on it.
  • A block taller than the viewport keeps its button (drawn at the first visible row of each run);
    the column reserves the width the provider declares (MaxActionsPerLine, clamped 1..4); only
    buttons that were actually drawn are hit-test targets.

Highlighting

  • Tokenization runs on a time budget off the render pass, per line, with equatable end-states,
    so a large file stays interactive while it colours in.
  • A line that exceeds the budget is retried a bounded number of times
    (MaxTimeoutRetries) and then left un-tokenized rather than re-attempted forever. Those attempts
    belong to the LINE and travel with it over an edit that inserts or deletes lines above it, so a
    line never inherits another's exhausted budget (which left it plain for good) and a pathological
    one never gets a fresh budget out of a paste somewhere else.

Also in this release

The rest of the 12.0.0 surface, unchanged in shape since it was built: canvas glyph-grid rendering
on a fixed character cell; the document model with undo; IEditorMargin composition with
LineNumberMargin; TextMate grammars and VS Code-style JSON themes, both drop-in
(AvaCodeEditor.Highlighting); light/dark theming that tracks ActualThemeVariant and
never re-tokenizes on a variant switch.

Requires Avalonia 12 and .NET 10. The package major tracks the Avalonia major.