MultiLineInput

termflow.tui.widgets.MultiLineInput

Multi-line text editor widget.

Maintains a Vector[String] of lines plus a (row, col) cursor. Editing keys (CharKey, Enter, Backspace, Delete, arrow keys, Home/End) compose to a small fixed-pitch text editor — enough for a comments box, a description field, or the body of a chat message. For single-line input use termflow.tui.Prompt instead.

Cursor navigation is grapheme-aware within a line: arrow keys step over surrogate pairs and combining marks as one unit, mirroring the single-line Prompt. The cursor is stored in UTF-16 code-unit offsets to match the underlying String model — apps composing with other text APIs can pass it straight through.

The widget renders one TextNode per visible row. The cursor row uses a reverse-video cell so the caret is visible without having to commandeer the hardware cursor — same approach as termflow.tui.widgets.TextField. Lines too wide for the viewport are clipped with a trailing ; vertical scrolling lifts the top row off-screen when the cursor moves below the visible window.

The widget is purely presentational. State lives in the calling app:

given Theme = Theme.dark
MultiLineInput.render(
 state = m.editor,
 width = 60,
 height = 8,
 at = Coord(2.x, 5.y)
)

Apps wire the keyboard with MultiLineInput.handleKey(state, key) which returns (newState, Option[Cmd[Msg]]) — the optional command is only ever None today (no submit semantics are baked in), but the shape matches Prompt.handleKey so swapping the two is a small change.

Attributes

Graph
Supertypes
class Object
trait Matchable
class Any
Self type

Members list

Type members

Classlikes

final case class State(lines: Vector[String], cursorRow: Int, cursorCol: Int, scrollTop: Int)

Editor state.

Editor state.

Invariant: lines always contains at least one entry — an empty editor is lines = Vector("") with cursor (0, 0). The cursor row is clamped to [0, lines.size - 1] and the column to [0, currentLine.length] after every transition.

Value parameters

cursorCol

Zero-based UTF-16 column on the cursor's row.

cursorRow

Zero-based row of the cursor.

lines

The editor's lines, top-to-bottom. UTF-16 code-unit strings, no trailing newline.

scrollTop

First visible row (vertical scroll offset). Internal-state-ish; apps generally don't manage it directly — handleKey keeps it consistent with the cursor.

Attributes

Companion
object
Supertypes
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all
object State

Attributes

Companion
class
Supertypes
trait Product
trait Mirror
class Object
trait Matchable
class Any
Self type
State.type

Value members

Concrete methods

def handleKey[Msg](state: State, key: InputKey): (State, Option[Cmd[Msg]])

Feed a key event into the editor and return the updated state.

Feed a key event into the editor and return the updated state.

Returns (state, None) for handled keys; (state, None) for unhandled keys (no-op) — the Option[Cmd] shape is reserved for future submit semantics so callers don't have to refactor when those land.

Attributes

def render(state: State, width: Int, height: Int, at: Coord)(using theme: Theme): List[VNode]

Render the editor at (at, width, height). Returns a list of VNodes — one per visible row plus an InputNode for the cursor row. Apps typically embed this in a BoxNode border for a nicer visual; the widget doesn't draw the border itself.

Render the editor at (at, width, height). Returns a list of VNodes — one per visible row plus an InputNode for the cursor row. Apps typically embed this in a BoxNode border for a nicer visual; the widget doesn't draw the border itself.

Value parameters

at

Top-left of the viewport.

height

Visible height in rows.

state

Current editor state.

width

Visible width in cells (rows are clipped past this).

Attributes

def scrollFor(state: State, height: Int): State

Adjust scrollTop so the cursor row is visible in height rows.

Adjust scrollTop so the cursor row is visible in height rows.

Attributes