Selkie--Widget--MultiLineInput

NAME

Selkie::Widget::MultiLineInput - Multi-line text input with word-wrap and 2D cursor

SYNOPSIS

use Selkie::Widget::MultiLineInput;
use Selkie::Sizing;

my $area = Selkie::Widget::MultiLineInput.new(
    sizing      => Sizing.fixed(1),    # grows up to max-lines as user types
    max-lines   => 6,
    placeholder => 'Type a message... (Ctrl+Enter to send)',
);

$area.on-submit.tap: -> $text { send-message($text); $area.clear };
$area.on-change.tap: -> $text { save-draft($text) };

DESCRIPTION

A multi-line text area with word-wrapping, a 2D cursor, and dynamic height that grows as the user types (up to max-lines). Plain Enter inserts a newline; Ctrl+Enter submits.

The height auto-adjusts via desired-height: if you pass sizing = Sizing.fixed(1)>, the parent layout sees the widget's desired height grow as content is added, bounded by max-lines.

set-text-silent updates the buffer without emitting on-change — use this from store subscriptions to avoid feedback loops. Both set-text and set-text-silent normalise CRLF and lone CR to LF before splitting, so text loaded from Windows-authored files (e.g. CCv3 cards) does not strand a \r inside each line.

Mouse and selection

Click positions the caret. Drag selects across rows; the selection range is rendered with reverse-video and respects word-wrap (the highlight follows the wrapped layout, not raw offsets). Double-click selects the word under the cursor; triple-click selects the entire current logical line. Scroll-wheel moves the cursor up/down. Ctrl+A selects everything; Ctrl+C / Ctrl+X emit on on-copy / on-cut and (for cut) delete the selection. Backspace and Delete consume an active selection if present; typing replaces it.

EXAMPLES

Chat compose area

my $compose = Selkie::Widget::MultiLineInput.new(
    sizing      => Sizing.fixed(1),
    max-lines   => 5,
    placeholder => 'Type a message — Ctrl+Enter to send',
);
$compose.on-submit.tap: -> $text {
    if $text.chars > 0 {
        $app.store.dispatch('chat/send', :$text);
        $compose.clear;
    }
};

SEE ALSO

has Int $!sel-anchor-row

Selection anchor in (logical-row, logical-col). -1 in $!sel-anchor-row means "no selection" — cursor is a bare caret. When >= 0 the selection covers the half-open range from min(anchor, cursor) to max(anchor, cursor), walked across logical lines.

method has-selection

method has-selection() returns Bool

True iff a selection is active (anchor differs from cursor). Bare caret returns False.

method selection-range

method selection-range() returns List

Returns the normalised selection range as a List of two pairs: (:row, :col) for the start and (:row, :col) for the end (half-open at end). Returns () when no selection.

method selected-text

method selected-text() returns Str

The text currently selected, walking line by line. \n joins successive logical lines. Empty string when no selection.

method clear-selection

method clear-selection() returns Mu

Clear the active selection without moving the caret.

method on-copy

method on-copy() returns Supply

Supply emitting the currently-selected text on Ctrl+C. Selkie does not own the system clipboard — apps wire this up themselves via OSC 52 or notcurses paste-buffer. Fires only when there's an active selection.

method on-cut

method on-cut() returns Supply

Supply emitting on Ctrl+X. Like on-copy but the selection is also deleted from the buffer.

method text

method text() returns Str

The full buffer contents joined with \n. (The buffer is stored as an array of logical lines; this assembly is O(N) in total character count — cache the result if calling per frame.)

method set-text

method set-text(
    Str:D $t
) returns Mu

Replace the buffer contents and place the caret at the end. Emits on on-change. Use this for user-driven updates; for programmatic syncs from a store path use set-text-silent instead to avoid feedback loops.

method set-text-silent

method set-text-silent(
    Str:D $t
) returns Mu

Silent variant of set-text — updates the buffer without emitting on on-change. Wire this into store subscriptions that mirror external state into the input, so the input update doesn't dispatch an event that loops back through the store and re-fires the subscription.

method clear

method clear() returns Mu

Empty the buffer. Equivalent to set-text('').

method on-submit

method on-submit() returns Supply

Supply that emits the current buffer when the user presses Ctrl+Enter (Enter inserts a newline). Apps that want plain Enter as submit register their own keybind and call text directly.

method on-change

method on-change() returns Supply

Supply that emits the new buffer contents on every user-driven edit (typing, paste, delete, cut, set-text). Does not fire for set-text-silent.

method set-focused

method set-focused(
    Bool $f
) returns Mu

Set the input's focus state. Called by Selkie::App's focus dispatcher. The caret is only painted while focused.

method is-focused

method is-focused() returns Bool

Whether the widget currently has focus.

method desired-height

method desired-height() returns UInt

The natural visual height for the buffer in cells, accounting for soft-wrap at the current width. Clamped to max-lines. Used by autosize containers (e.g. a chat compose area) to grow the input with its content.

method line-count

method line-count() returns UInt

Number of logical lines in the buffer (counts hard newlines, not soft-wraps). Always at least 1 — an empty buffer counts as one empty line.

method cursor-row

method cursor-row() returns UInt

Caret row in logical-line coordinates (0-based; counts hard newlines, not soft-wraps).

method cursor-col

method cursor-col() returns UInt

Caret column on the current logical line (0-based; counts characters, not visual cells).

method visual-rows

method visual-rows() returns Array

Same shape as !visual-lines, but each entry is a hash with logical-row, logical-col-start, text. Used by the selection overlay to map visual rows back to logical (row, col) spans for highlighting.

method insert-text

method insert-text(
    Str:D $text
) returns Nil

Insert $text at the current cursor position in one operation, splitting on \n so multi-line pasted content lays across multiple buffer lines. Equivalent to typing each character in turn but with one buffer rebuild instead of one per char — O(n) total instead of O(n²). Used by the App's paste-batching drain loop.

method do-word-backspace

method do-word-backspace() returns Mu

Shift-Backspace: delete from the cursor back to the previous word boundary. At column 0, falls through to the regular backspace semantics so the line above is joined — matches what users expect from "delete previous word" in editors that also support multi-line.

Selkie v0.10.0

High-level TUI framework built on Notcurses

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

Notcurses::Native:ver<0.4.0+>:auth<zef:apogee>

Test Dependencies

Provides

  • Selkie
  • Selkie::App
  • Selkie::App::Internal::Dispatch
  • Selkie::App::Internal::ErrorLog
  • Selkie::App::Internal::FocusTree
  • Selkie::App::Internal::HitTest
  • Selkie::App::Internal::IdleBudget
  • Selkie::App::Internal::OverlayTree
  • Selkie::App::Internal::RenderLoop
  • Selkie::App::Internal::ScreenModalLifecycle
  • Selkie::App::Internal::Terminal
  • Selkie::App::Internal::TerminalSequences
  • Selkie::Container
  • Selkie::EffectiveBounds
  • Selkie::Event
  • Selkie::Layout::Allocate
  • Selkie::Layout::HBox
  • Selkie::Layout::Split
  • Selkie::Layout::VBox
  • Selkie::Plot::Palette
  • Selkie::Plot::Scaler
  • Selkie::Plot::Ticks
  • Selkie::ScreenManager
  • Selkie::Sizing
  • Selkie::Store
  • Selkie::Style
  • Selkie::Test::Focus
  • Selkie::Test::Keys
  • Selkie::Test::Snapshot
  • Selkie::Test::Snapshot::Harness
  • Selkie::Test::Store
  • Selkie::Test::Supply
  • Selkie::Test::Tree
  • Selkie::Theme
  • Selkie::Trace
  • Selkie::Tree
  • Selkie::Widget
  • Selkie::Widget::Axis
  • Selkie::Widget::BarChart
  • Selkie::Widget::Border
  • Selkie::Widget::Button
  • Selkie::Widget::CardList
  • Selkie::Widget::Checkbox
  • Selkie::Widget::CommandPalette
  • Selkie::Widget::ConfirmModal
  • Selkie::Widget::FileBrowser
  • Selkie::Widget::FocusableByDefault
  • Selkie::Widget::Heatmap
  • Selkie::Widget::HelpOverlay
  • Selkie::Widget::Histogram
  • Selkie::Widget::Image
  • Selkie::Widget::Legend
  • Selkie::Widget::LineChart
  • Selkie::Widget::ListView
  • Selkie::Widget::Modal
  • Selkie::Widget::MultiLineInput
  • Selkie::Widget::PasswordStrength
  • Selkie::Widget::Plot
  • Selkie::Widget::ProgressBar
  • Selkie::Widget::RadioGroup
  • Selkie::Widget::RichText
  • Selkie::Widget::RichText::Span
  • Selkie::Widget::ScatterPlot
  • Selkie::Widget::ScrollView
  • Selkie::Widget::Select
  • Selkie::Widget::Sparkline
  • Selkie::Widget::Spinner
  • Selkie::Widget::TabBar
  • Selkie::Widget::Table
  • Selkie::Widget::Text
  • Selkie::Widget::TextInput
  • Selkie::Widget::TextInput::HighlightSpan
  • Selkie::Widget::TextStream
  • Selkie::Widget::Toast
  • Selkie::Widget::ViewportedCardList

Documentation

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.