CodeEditor is a code-oriented multi-line editor built on top of the shared TextEditorBase / TextEditorCore stack.
It keeps the core text-editing behavior of TextArea-selection, clipboard, undo/redo, scrolling, and Find/Replace-then adds:

var editor = new CodeEditor("public sealed class Demo { }")
.MinHeight(10)
.MaxHeight(10);
To bind the editor text to a State<string?>:
var code = new State<string?>("return 42;");
new CodeEditor()
.Text(code);
CodeEditor inherits the shared editor behavior from TextEditorBase:
Ctrl+C, Ctrl+X, Ctrl+V),Ctrl+Z, Ctrl+R),Ctrl+F, Ctrl+H),IScrollable / ScrollModel.This means CodeEditor behaves like a specialized TextArea, not a separate editing subsystem.
CodeEditor exposes programmatic navigation helpers:
editor.GoToLine(42); // one-based line, column 1
editor.GoToColumn(8); // one-based column on the current line
editor.GoToLine(42, 8); // one-based line + column
editor.GoToPosition(128); // zero-based UTF-16 document position
editor.GoToPosition(new TextPosition(128));
Line and column navigation use one-based values because they are intended to match the numbers users typically see in editor gutters and status bars. Requests are clamped to the current document bounds.
For status bars and other bindings, CodeEditor also exposes readable bindable caret-location properties:
new Footer()
.Left(new TextBlock(() => $"Ln {editor.Line}, Col {editor.Column}"));
Line and Column are one-based and update automatically as the caret moves.
Line numbers are enabled by default:
new CodeEditor(text)
.ShowLineNumbers(true)
.MinLineNumberDigits(2);
The default gutter is adaptive:
Like TextArea, CodeEditor hosts the reusable SearchReplacePopup:
Ctrl+F: FindCtrl+H: ReplaceYou can also open the popup programmatically:
editor.OpenFind("TODO");
editor.OpenReplace("var");
Search matches are rendered as overlays on top of the syntax-highlighted text.
For small languages, demos, or lightweight classification, use the Highlighter delegate:
new CodeEditor(source)
.Highlighter((in CodeEditorLineHighlightRequest request, List<StyledRun> runs) =>
{
var line = request.Snapshot.GetLine(request.LineIndex);
if (line.Length >= 6)
{
runs.Add(new StyledRun(0, 6, Style.None.WithForeground(Colors.DeepSkyBlue)));
}
});
Runs are line-relative, not wrap-relative. CodeEditor intersects them with only the visible wrapped segments.
For large files or incremental tokenization, provide a CodeEditorSyntaxHighlighter:
public sealed class DemoSyntaxHighlighter : CodeEditorSyntaxHighlighter
{
public override CodeEditorSyntaxState Build(in CodeEditorSyntaxBuildContext context)
=> new DemoState(context.Snapshot.Version);
public override CodeEditorSyntaxState Update(CodeEditorSyntaxState previousState, in CodeEditorSyntaxUpdateContext context)
=> new DemoState(context.Snapshot.Version);
public override void GetLineRuns(CodeEditorSyntaxState state, in CodeEditorLineSyntaxRequest request, List<StyledRun> runs)
{
// Return StyledRun values relative to request.LineStart/request.LineLength.
}
}
new CodeEditor(source)
.SyntaxHighlighter(new DemoSyntaxHighlighter());
Key points:
If your highlighter can work off the UI thread, also implement IAsyncCodeEditorSyntaxHighlighter.
For full grammar-based highlighting, use the companion package:
dotnet add package XenoAtom.Terminal.UI.Extensions.CodeEditor.TextMateSharp
Then attach the bundled TextMateSharp-backed highlighter:
using XenoAtom.Terminal.UI.Extensions.CodeEditor.TextMateSharp;
var editor = new CodeEditor(source)
{
SyntaxHighlighter = new TextMateCodeEditorSyntaxHighlighter(
new TextMateCodeEditorOptions
{
LanguageId = "csharp",
}),
};
You can also resolve grammars from a file name or extension:
var highlighter = new TextMateCodeEditorSyntaxHighlighter(
new TextMateCodeEditorOptions
{
FileName = "program.cs",
});
The TextMateSharp integration keeps incremental per-line tokenizer state so edits only need to recompute the affected suffix of the document, while rendering colors are resolved from bundled light and dark TextMate themes according to the active terminal UI theme.
Margins are non-visual extension points that render beside the text surface:
var diffMargin = CodeEditor.CreateDiffIndicatorMargin(
lineIndex => lineIndex switch
{
3 => new Rune('+'),
4 => new Rune('+'),
10 => new Rune('~'),
_ => null,
},
lineIndex => lineIndex < 10
? Style.None.WithForeground(Colors.LimeGreen) | TextStyle.Bold
: Style.None.WithForeground(Colors.Gold) | TextStyle.Bold);
var editor = new CodeEditor(source);
editor.LeftMargins.Insert(0, diffMargin);
Margins receive:
This allows features such as line numbers, diff markers, diagnostics, breakpoints, or custom annotations without making margins into Visuals.
Use CodeEditorStyle to customize the editor surface, gutter, current-line highlight, and search overlays:
new CodeEditor(source)
.Style(CodeEditorStyle.Default with
{
CurrentLineBackground = Colors.DeepSkyBlue.WithAlpha(0x22),
SearchMatchBackground = Colors.Gold.WithAlpha(0x30),
ActiveSearchMatchBackground = Colors.Orange,
ShowMarginSeparators = true,
});
CodeEditor implements IScrollable, so it integrates naturally with ScrollViewer:
new ScrollViewer(new CodeEditor(longSource).MinHeight(12).MaxHeight(12));
The gutter does not horizontally scroll with the text surface.