Skip to main content

TextView

A block of text to read: wrapped to the width it is given, scrolled the same way a ScrollPane is — because it is one inside.

_readme = new TextView(options.Keymap) { Text = File.ReadAllText(path) };

_readme.Draw(region);

Widgets are built in the view's constructor, so options is the ArlecchinoOptions the container hands it, and region is the region the view draws the widget into.

MemberMeaning
TextWhat to show
StyleColors it
OffsetThe first wrapped line shown
LineCountHow many rows it takes once wrapped
IsFocusedSet by the focus ring

Wrapping

Line breaks in the text are kept, long lines break on spaces, and a word wider than the pane is split rather than lost — the same rules as TextWidth.Wrap, which is what does it.

The wrap is cached and redone only when the text or the width changed, so resizing the terminal reflows the text instead of cutting it off, and a frame that changed neither costs nothing.

Keys

↑↓ move a row, PgUp/PgDn a page, Home/End go to the ends, and the wheel scrolls while the pointer is over it — all from the keymap, through the pane inside.

Reading rather than editing

TextView shows text; it does not edit it. Editing several lines is the multi-line dialog, which is a modal rather than a widget because it has a caret, a validator and a submit key of its own.

Measuring

LineCount is how many lines the text takes once wrapped to the last width it was drawn at, so it answers questions about the frame that has already been composed rather than the one being composed now. It is what a status bar reports a position against:

new StatusBar
{
Right = [() => $"{_readme.Offset + 1}/{_readme.LineCount}"],
}.Draw(region.Rows(region.Height - 1, 1));

To split a pane between the text and something else, decide the split from the region rather than from the wrap:

private readonly TextView _readme;
private readonly ListBox<string> _files;

var (text, rest) = region.SplitTop(region.Height / 2);

_readme.Draw(text);
_files.Draw(rest);