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.
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.
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 0Accessors
| Method | Returns | Description |
|---|---|---|
text() | &str | Current text content |
cursor() | usize | Cursor 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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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:
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.
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 0Differences from TextEditor
| Behavior | TextInput | TextEditor |
|---|---|---|
| Initial cursor | End of text | Start of text |
| Newlines | Replaced with space on insert | Preserved |
Home / End | Move to start/end of entire string | Move to start/end of current line |
| Vertical movement | Not supported | move_up / move_down |
Additional methods
| Method | Description |
|---|---|
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
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
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:
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:
TextArea {
editor: ctx.state.editor.clone(),
on_edit: ctx.link().callback(|ev: TextEditEvent| Msg::OnEdit(ev)),
}Examples
examples/text_area.rs- TwoTextEditorinstances drivingTextAreawidgetsexamples/text_area_sentinels.rs-TextEditorwith inline sentinels and snapshotsexamples/todo.rs-TextInputfor new item entryexamples/inline.rs-TextInputwith insert modeexamples/opencode_home.rs-TextEditorin a multi-panel layoutexamples/search_lists.rs-TextInputfor filter-as-you-type
Imports
TextEditor, TextEditEvent, and TextEditKind are available from both the prelude and the crate root:
use tui_lipan::TextEditor;
use tui_lipan::TextEditEvent;TextInput is available from the prelude only:
use tui_lipan::prelude::TextInput;
// or
use tui_lipan::prelude::*;