Skip to content

Text Editing

TextEditor and TextInput are the text buffer models that power TextArea and Input widgets respectively. They manage cursor position, selection, undo/redo history, and keystroke handling. Use them in component state to maintain editing context across renders.

rust
use tui_lipan::prelude::*;

TextEditor (multi-line)

A multi-line text editor with selection support. The cursor is stored as a byte index into the UTF-8 string and is always kept on a character boundary. Selection is represented by an optional anchor position.

rust
let mut editor = TextEditor::new("Hello\nWorld");
// Cursor starts at position 0 (beginning of text)

// Also available via Default
let editor = TextEditor::default(); // empty text, cursor at 0

Accessors

MethodReturnsDescription
text()&strCurrent text content
cursor()usizeCursor byte position
anchor()Option<usize>Selection anchor (if active)
selection()Option<(usize, usize)>Ordered (start, end) range if selection exists
selected_text()Option<&str>Text within the selection

Editing

MethodDescription
insert_char(ch)Insert a character at cursor (replaces selection if active)
insert_str(s)Insert a string at cursor (replaces selection if active)
backspace()Delete character before cursor or delete selection
delete()Delete character after cursor or delete selection
delete_word_left()Delete word before cursor
delete_word_right()Delete word after cursor

Cursor movement

MethodDescription
move_left() / move_right()Move cursor by one character, clears selection
move_up() / move_down()Move cursor by one line (grapheme-column aware)
move_word_left() / move_word_right()Move cursor by one word
move_home() / move_end()Move to start/end of current line

Selection

MethodDescription
select_left() / select_right()Extend selection by one character
select_up() / select_down()Extend selection by one line
select_word_left() / select_word_right()Extend selection by one word
select_home() / select_end()Extend selection to start/end of current line
select_all()Select all text
clear_selection()Remove selection (keep cursor position)

Sync and setters

MethodDescription
set_text(s)Replace entire text content (clears history)
set_cursor(pos)Set cursor position (clears selection)
set_cursor_keep_anchor(pos)Set cursor position (preserves anchor for selection)
set_anchor(pos)Set anchor position directly

Undo / Redo

TextEditor maintains an undo history (up to 1000 entries by default). Consecutive edits of the same kind are merged into logical groups for natural undo behavior.

MethodDescription
can_undo()Whether undo is available
can_redo()Whether redo is available
undo()Undo last edit group
redo()Redo last undone edit group
clear_history()Clear all undo/redo history

Keystroke handling

handle_key processes a KeyEvent using the default keymap and returns whether the editor state changed:

rust
let changed = editor.handle_key(key_event);
if changed {
    // editor state was modified
}

Supported keys include character insertion, arrow keys, word movement (Ctrl+Left/Right), Home/End, Backspace/Delete, word deletion (Ctrl+Backspace/Ctrl+Delete), Enter (newline), and undo/redo (Ctrl+Z/Ctrl+Shift+Z).

Clipboard operations (copy/cut/paste) are handled at the widget layer, not by handle_key.


TextInput (single-line)

A single-line text input model. Same selection and undo/redo model as TextEditor, but constrains input to a single line.

rust
let mut input = TextInput::new("initial value");
// Cursor starts at end of text (unlike TextEditor which starts at 0)

let input = TextInput::default(); // empty text, cursor at 0

Differences from TextEditor

BehaviorTextInputTextEditor
Initial cursorEnd of textStart of text
NewlinesReplaced with space on insertPreserved
Home / EndMove to start/end of entire stringMove to start/end of current line
Vertical movementNot supportedmove_up / move_down

Additional methods

MethodDescription
clear()Clear all text content
delete_to_start()Delete from cursor to start of text (or delete selection)
delete_to_end()Delete from cursor to end of text (or delete selection)

All methods from TextEditor (accessors, cursor movement, selection, undo/redo) are available on TextInput as well, except for vertical movement methods.


Integration with widgets

TextEditor and TextInput are typically stored in component state and passed to TextArea and Input widgets:

TextArea + TextEditor

rust
struct State {
    editor: TextEditor,
}

fn create_state(&self, _props: &Self::Properties) -> Self::State {
    State {
        editor: TextEditor::new(""),
    }
}

fn view(&self, ctx: &Context<Self>) -> Element {
    rsx! {
        TextArea {
            editor: ctx.state.editor.clone(),
            on_change: ctx.link().callback(|ev: TextAreaEvent| Msg::EditorChanged(ev)),
        }
    }
}

fn update(&mut self, msg: Msg, ctx: &mut Context<Self>) -> Update {
    match msg {
        Msg::EditorChanged(ev) => {
            ctx.state.editor.set_text(ev.value.to_string());
            ctx.state.editor.set_cursor(ev.cursor);
            ctx.state.editor.set_anchor(ev.anchor);
            Update::full()
        }
    }
}

Input + TextInput

rust
struct State {
    input: TextInput,
}

fn create_state(&self, _props: &Self::Properties) -> Self::State {
    State {
        input: TextInput::new(""),
    }
}

fn view(&self, ctx: &Context<Self>) -> Element {
    rsx! {
        Input {
            value: ctx.state.input.text(),
            on_change: ctx.link().callback(|val: String| Msg::InputChanged(val)),
        }
    }
}

TextEditEvent

Both Input and TextArea emit TextEditEvent through their on_edit callback for structured edit tracking:

rust
pub struct TextEditEvent {
    pub start: usize,                // Byte offset where the edit began
    pub deleted: Arc<str>,           // Text that was removed
    pub inserted: Arc<str>,          // Text that was inserted
    pub cursor_before: usize,        // Cursor position before the edit
    pub anchor_before: Option<usize>,// Anchor position before the edit
    pub cursor_after: usize,         // Cursor position after the edit
    pub anchor_after: Option<usize>, // Anchor position after the edit
    pub kind: TextEditKind,          // Type of edit
}

pub enum TextEditKind {
    Insert,
    DeleteBackspace,
    DeleteForward,
    Replace,
}

Wire it up via on_edit:

rust
TextArea {
    editor: ctx.state.editor.clone(),
    on_edit: ctx.link().callback(|ev: TextEditEvent| Msg::OnEdit(ev)),
}

Examples

  • examples/text_area.rs - Two TextEditor instances driving TextArea widgets
  • examples/text_area_sentinels.rs - TextEditor with inline sentinels and snapshots
  • examples/todo.rs - TextInput for new item entry
  • examples/inline.rs - TextInput with insert mode
  • examples/opencode_home.rs - TextEditor in a multi-panel layout
  • examples/search_lists.rs - TextInput for filter-as-you-type

Imports

TextEditor, TextEditEvent, and TextEditKind are available from both the prelude and the crate root:

rust
use tui_lipan::TextEditor;
use tui_lipan::TextEditEvent;

TextInput is available from the prelude only:

rust
use tui_lipan::prelude::TextInput;
// or
use tui_lipan::prelude::*;

MIT OR Apache-2.0