1
0
зеркало из https://github.com/glebtv/tui-textarea.git synced 2026-08-28 11:36:17 +03:00
Gleb Tv 6aa9ec0b87 fix(wrap): drop phantom whitespace rows for inputs that exactly fill a row
Word-wrap previously emitted a phantom whitespace-only row when a word
exactly filled a row and the input continued (e.g. "abcde fghij" at
width 5 wrapped to 3 rows instead of 2). Separator whitespace that does
not fit the remaining space is now handled like ratatui's WordWrapper
(trim: false): whitespace filling the remaining cells is dropped,
whitespace after an exactly full row is dropped one grapheme at a time,
and any leftover whitespace leads the next row.

wrapped_rows now keeps byte-accurate column bookkeeping so screen_map
round-trips hold for all visible positions; positions inside dropped
whitespace have no screen representation and are treated as such.

Verified differentially against ratatui 0.30's WordWrapper (a faithful
reference port cross-checked against the real Paragraph renderer):
row counts now match for single- and multi-space separators, trailing
whitespace, CJK and mixed-width inputs.
2026-08-19 13:36:28 +03:00
2026-07-22 17:51:44 +02:00
2024-08-02 23:07:17 +09:00
2026-02-18 23:34:50 +10:00

tui-textarea-2

crate docs Rust Test Rust Clippy coverage

tui-textarea-2 is a simple yet powerful text editor widget like in HTML for <a href="<a href="https://github.com/ratatui/ratatui" data-markdown-generated-content="">https://github.com/ratatui/ratatui</a>">ratatui</a>. Multi-line text editor can be easily put as part of your TUI application.</p> <blockquote> <p dir="auto">Maintained fork notice: this repository is maintained under <code>srothgan/tui-textarea</code> to keep compatibility updates moving (including ratatui 0.30+ support and maintenance fixes).</p> </blockquote> <p dir="auto">Since this fork was created, it has added or integrated:</p> <ul dir="auto"> <li>Compatibility updates for current ratatui releases plus Rust 2024 / <code>rust-version = 1.85.0</code></li> <li><code>TextArea::clear()</code> and custom highlight APIs</li> <li>Unicode-aware soft-wrap modes via <code>WrapMode::{None, Word, Glyph, WordOrGlyph}</code></li> <li>Wrapped-line Up/Down cursor navigation that follows visual rows</li> <li>Row measurement via <code>TextAreaMeasure</code>, <code>TextArea::measure()</code>, <code>set_min_rows()</code>, and <code>set_max_rows()</code></li> <li>Bulk whole-buffer replacement via <code>TextArea::set_lines()</code></li> <li>Opt-in atomic ranges for caller-owned placeholders, mentions, or other indivisible spans</li> <li>Opt-in native terminal cursor integration via <code>CursorRenderMode::Hidden</code> and <code>rendered_cursor_position()</code></li> </ul> <p dir="auto"><strong>Features:</strong></p> <ul dir="auto"> <li>Multi-line text editor widget with basic operations (insert/delete characters, auto scrolling, ...)</li> <li>Emacs-like shortcuts (<code>C-n</code>/<code>C-p</code>/<code>C-f</code>/<code>C-b</code>, <code>M-f</code>/<code>M-b</code>, <code>C-a</code>/<code>C-e</code>, <code>C-h</code>/<code>C-d</code>, <code>C-k</code>, <code>M-<</code>/<code>M-></code>, ...)</li> <li>Undo/Redo</li> <li>Line number</li> <li>Cursor line highlight</li> <li>Unicode-aware soft wrap with visual-line cursor navigation</li> <li>Dynamic row measurement for auto-sizing layouts</li> <li>Bulk content replacement without rebuilding widget configuration</li> <li>Search with regular expressions</li> <li>Text selection</li> <li>Custom highlighted ranges</li> <li>Opt-in atomic ranges for indivisible editing spans</li> <li>Placeholder and masking support</li> <li>Optional native terminal cursor placement while keeping backend-specific cursor shape control outside the widget</li> <li>Mouse scrolling</li> <li>Yank support. Paste text deleted with <code>C-k</code>, <code>C-j</code>, ...</li> <li>Backend agnostic. <a href="<a href="https://docs.rs/crossterm/latest/crossterm/" data-markdown-generated-content="">https://docs.rs/crossterm/latest/crossterm/</a>">crossterm</a>, <a href="<a href="https://docs.rs/termion/latest/termion/" data-markdown-generated-content="">https://docs.rs/termion/latest/termion/</a>">termion</a>, <a href="<a href="https://docs.rs/termwiz/latest/termwiz/" data-markdown-generated-content="">https://docs.rs/termwiz/latest/termwiz/</a>">termwiz</a>, and your own backend are all supported</li> <li>Multiple textarea widgets in the same screen</li> <li>Support <a href="<a href="https://github.com/ratatui/ratatui" data-markdown-generated-content="">https://github.com/ratatui/ratatui</a>">ratatui</a></li> </ul> <p dir="auto"><a href="<a href="https://docs.rs/tui-textarea-2/latest/tui_textarea/" data-markdown-generated-content="">https://docs.rs/tui-textarea-2/latest/tui_textarea/</a>">Documentation</a></p> <h2 id="user-content-examples" dir="auto">Examples</h2> <p dir="auto">Running <code>cargo run --example</code> in this repository can demonstrate usage of tui-textarea.</p> <p dir="auto">For consistent GIF capture across the crossterm examples, set <code>TUI_TEXTAREA_RECORDING=1</code> for the default <code>120x30</code> terminal size or override it with <code>TUI_TEXTAREA_RECORDING_SIZE=<cols>x<rows></code>. See <a href="./examples/RECORDING.md"><code>examples/RECORDING.md</code></a> for the recording workflow.</p> <h3 id="user-content-minimalexamplesminimalrs" dir="auto"><a href="./examples/minimal.rs"><code>minimal</code></a></h3> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-sh display">cargo run --example minimal </code></pre></div><p dir="auto">Minimal usage with <a href="<a href="https://docs.rs/crossterm/latest/crossterm/" data-markdown-generated-content="">https://docs.rs/crossterm/latest/crossterm/</a>">crossterm</a> support.</p> <img src="<a href="https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/minimal.gif" data-markdown-generated-content="">https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/minimal.gif</a>" width=539 height=172 alt="minimal example"> <h3 id="user-content-editorexampleseditorrs" dir="auto"><a href="./examples/editor.rs"><code>editor</code></a></h3> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-sh display">cargo run --example editor --features search file.txt </code></pre></div><p dir="auto">Simple text editor to edit multiple files.</p> <img src="<a href="https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/editor.gif" data-markdown-generated-content="">https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/editor.gif</a>" width=560 height=236 alt="editor example"> <h3 id="user-content-single_lineexamplessingle_liners" dir="auto"><a href="./examples/single_line.rs"><code>single_line</code></a></h3> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-sh display">cargo run --example single_line </code></pre></div><p dir="auto">Single-line input form with float number validation.</p> <img src="<a href="https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/single_line.gif" data-markdown-generated-content="">https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/single_line.gif</a>" width=539 height=92 alt="single line example"> <h3 id="user-content-splitexamplessplitrs" dir="auto"><a href="./examples/split.rs"><code>split</code></a></h3> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-sh display">cargo run --example split </code></pre></div><p dir="auto">Two split textareas in a screen and switch them. An example for multiple textarea instances.</p> <img src="<a href="https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/split.gif" data-markdown-generated-content="">https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/split.gif</a>" width=539 height=124 alt="multiple textareas example"> <h3 id="user-content-variableexamplesvariablers" dir="auto"><a href="./examples/variable.rs"><code>variable</code></a></h3> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-sh display">cargo run --example variable </code></pre></div><p dir="auto">Simple textarea with variable height driven by measured preferred rows.</p> <p dir="auto">This example now sizes itself from <code>textarea.measure(width).preferred_rows</code>.</p> <h3 id="user-content-vimexamplesvimrs" dir="auto"><a href="./examples/vim.rs"><code>vim</code></a></h3> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-sh display">cargo run --example vim </code></pre></div><p dir="auto">Vim-like modal text editor. Vim emulation is implemented as a state machine.</p> <img src="<a href="https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/vim.gif" data-markdown-generated-content="">https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/vim.gif</a>" width=590 height=156 alt="Vim emulation example"> <h3 id="user-content-popup_placeholderexamplespopup_placeholderrs" dir="auto"><a href="./examples/popup_placeholder.rs"><code>popup_placeholder</code></a></h3> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-sh display">cargo run --example popup_placeholder </code></pre></div><p dir="auto">Popup textarea with a placeholder text.</p> <img src="<a href="https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/placepop.gif" data-markdown-generated-content="">https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/placepop.gif</a>" width=446 height=220 alt="popup textarea with placeholder example"> <h3 id="user-content-passwordexamplespasswordrs" dir="auto"><a href="./examples/password.rs"><code>password</code></a></h3> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-sh display">cargo run --example password </code></pre></div><p dir="auto">Password input form with masking text with ●.</p> <img src="<a href="https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/password.gif" data-markdown-generated-content="">https://raw.githubusercontent.com/rhysd/ss/master/tui-textarea/password.gif</a>" width=589 height=92 alt="password example"> <h3 id="user-content-termionexamplestermionrs" dir="auto"><a href="./examples/termion.rs"><code>termion</code></a></h3> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-sh display">cargo run --example termion --no-default-features --features<span class="o">=</span>termion </code></pre></div><p dir="auto">Minimal usage with <a href="<a href="https://docs.rs/termion/latest/termion/" data-markdown-generated-content="">https://docs.rs/termion/latest/termion/</a>">termion</a> support.</p> <h3 id="user-content-termwizexamplestermwizrs" dir="auto"><a href="./examples/termwiz.rs"><code>termwiz</code></a></h3> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-sh display">cargo run --example termwiz --no-default-features --features<span class="o">=</span>termwiz </code></pre></div><p dir="auto">Minimal usage with <a href="<a href="https://docs.rs/termwiz/latest/termwiz/" data-markdown-generated-content="">https://docs.rs/termwiz/latest/termwiz/</a>">termwiz</a> support.</p> <h2 id="user-content-installation" dir="auto">Installation</h2> <p dir="auto">Add <code>tui-textarea</code> crate to dependencies in your <code>Cargo.toml</code>. This enables crossterm backend support by default.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-toml display"><span class="p">[</span><span class="nx">dependencies</span><span class="p">]</span> <span class="nx">ratatui</span> <span class="p">=</span> <span class="s2">"*"</span> <span class="nx">tui-textarea</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">package</span> <span class="p">=</span> <span class="s2">"tui-textarea-2"</span><span class="p">,</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">"*"</span> <span class="p">}</span> </code></pre></div><p dir="auto">If you need text search with regular expressions, enable <code>search</code> feature. It adds <a href="<a href="https://docs.rs/regex/latest/regex/" data-markdown-generated-content="">https://docs.rs/regex/latest/regex/</a>">regex crate</a> as dependency.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-toml display"><span class="p">[</span><span class="nx">dependencies</span><span class="p">]</span> <span class="nx">ratatui</span> <span class="p">=</span> <span class="s2">"*"</span> <span class="nx">tui-textarea</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">package</span> <span class="p">=</span> <span class="s2">"tui-textarea-2"</span><span class="p">,</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">"*"</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"search"</span><span class="p">]</span> <span class="p">}</span> </code></pre></div><p dir="auto">If you're using ratatui with <a href="<a href="https://docs.rs/termion/latest/termion/" data-markdown-generated-content="">https://docs.rs/termion/latest/termion/</a>">termion</a> or <a href="<a href="https://docs.rs/termwiz/latest/termwiz/" data-markdown-generated-content="">https://docs.rs/termwiz/latest/termwiz/</a>">termwiz</a>, enable the <code>termion</code> or <code>termwiz</code> feature instead of <code>crossterm</code> feature.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-toml display"><span class="p">[</span><span class="nx">dependencies</span><span class="p">]</span> <span class="c"># For termion</span> <span class="nx">ratatui</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">"*"</span><span class="p">,</span> <span class="nx">default-features</span> <span class="p">=</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"termion"</span><span class="p">]</span> <span class="p">}</span> <span class="nx">tui-textarea</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">package</span> <span class="p">=</span> <span class="s2">"tui-textarea-2"</span><span class="p">,</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">"*"</span><span class="p">,</span> <span class="nx">default-features</span> <span class="p">=</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"termion"</span><span class="p">]</span> <span class="p">}</span> <span class="c"># For termwiz</span> <span class="nx">ratatui</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">"*"</span><span class="p">,</span> <span class="nx">default-features</span> <span class="p">=</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"termwiz"</span><span class="p">]</span> <span class="p">}</span> <span class="nx">tui-textarea</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">package</span> <span class="p">=</span> <span class="s2">"tui-textarea-2"</span><span class="p">,</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">"*"</span><span class="p">,</span> <span class="nx">default-features</span> <span class="p">=</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"termwiz"</span><span class="p">]</span> <span class="p">}</span> </code></pre></div><p dir="auto">The following table shows feature names corresponding to the supported ratatui backend integrations.</p> <table> <thead> <tr> <th></th> <th>crossterm</th> <th>termion</th> <th>termwiz</th> <th>Your own backend</th> </tr> </thead> <tbody> <tr> <td>ratatui</td> <td><code>crossterm</code> (enabled by default)</td> <td><code>termion</code></td> <td><code>termwiz</code></td> <td><code>no-backend</code></td> </tr> </tbody> </table> <p dir="auto">In addition to above dependencies, you also need to install <a href="<a href="https://docs.rs/crossterm/latest/crossterm/" data-markdown-generated-content="">https://docs.rs/crossterm/latest/crossterm/</a>">crossterm</a> or <a href="<a href="https://docs.rs/termion/latest/termion/" data-markdown-generated-content="">https://docs.rs/termion/latest/termion/</a>">termion</a> or <a href="<a href="https://docs.rs/termwiz/latest/termwiz/" data-markdown-generated-content="">https://docs.rs/termwiz/latest/termwiz/</a>">termwiz</a> to initialize your application and to receive key inputs.</p> <h2 id="user-content-minimal-usage" dir="auto">Minimal Usage</h2> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use tui_textarea::TextArea; use crossterm::event::{Event, read}; let mut term = ratatui::Terminal::new(...); // Create an empty `TextArea` instance which manages the editor state let mut textarea = TextArea::default(); // Event loop loop { term.draw(|f| { // Get `ratatui::layout::Rect` where the editor should be rendered let rect = ...; // Render the textarea in terminal screen f.render_widget(&textarea, rect); })?; if let Event::Key(key) = read()? { // Your own key mapping to break the event loop if key.code == KeyCode::Esc { break; } // `TextArea::input` can directly handle key events from backends and update the editor state textarea.input(key); } } // Get text lines as `&[String]` println!("Lines: {:?}", textarea.lines()); </code></pre></div><p dir="auto"><code>TextArea</code> is an instance to manage the editor state. By default, it disables line numbers and highlights cursor line with underline.</p> <p dir="auto"><code>&TextArea</code> reference implements ratatui's <code>Widget</code> trait. Render it on every tick of event loop.</p> <p dir="auto"><code>TextArea::input()</code> receives inputs from tui backends. The method can take key events from backends such as <code>crossterm::event::KeyEvent</code> or <code>termion::event::Key</code> directly if the features are enabled. The method handles default key mappings as well.</p> <p dir="auto">Default key mappings are as follows:</p> <table> <thead> <tr> <th>Mappings</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>Ctrl+H</code>, <code>Backspace</code></td> <td>Delete one character before cursor</td> </tr> <tr> <td><code>Ctrl+D</code>, <code>Delete</code></td> <td>Delete one character next to cursor</td> </tr> <tr> <td><code>Ctrl+M</code>, <code>Enter</code></td> <td>Insert newline</td> </tr> <tr> <td><code>Ctrl+K</code></td> <td>Delete from cursor until the end of line</td> </tr> <tr> <td><code>Ctrl+J</code></td> <td>Delete from cursor until the head of line</td> </tr> <tr> <td><code>Ctrl+W</code>, <code>Alt+H</code>, <code>Alt+Backspace</code></td> <td>Delete one word before cursor</td> </tr> <tr> <td><code>Alt+D</code>, <code>Alt+Delete</code></td> <td>Delete one word next to cursor</td> </tr> <tr> <td><code>Ctrl+U</code></td> <td>Undo</td> </tr> <tr> <td><code>Ctrl+R</code></td> <td>Redo</td> </tr> <tr> <td><code>Ctrl+C</code>, <code>Copy</code></td> <td>Copy selected text</td> </tr> <tr> <td><code>Ctrl+X</code>, <code>Cut</code></td> <td>Cut selected text</td> </tr> <tr> <td><code>Ctrl+Y</code>, <code>Paste</code></td> <td>Paste yanked text</td> </tr> <tr> <td><code>Ctrl+F</code>, <code>→</code></td> <td>Move cursor forward by one character</td> </tr> <tr> <td><code>Ctrl+B</code>, <code>←</code></td> <td>Move cursor backward by one character</td> </tr> <tr> <td><code>Ctrl+P</code>, <code>↑</code></td> <td>Move cursor up by one line</td> </tr> <tr> <td><code>Ctrl+N</code>, <code>↓</code></td> <td>Move cursor down by one line</td> </tr> <tr> <td><code>Alt+F</code>, <code>Ctrl+→</code></td> <td>Move cursor forward by word</td> </tr> <tr> <td><code>Alt+B</code>, <code>Ctrl+←</code></td> <td>Move cursor backward by word</td> </tr> <tr> <td><code>Alt+]</code>, <code>Alt+P</code>, <code>Ctrl+↑</code></td> <td>Move cursor up by paragraph</td> </tr> <tr> <td><code>Alt+[</code>, <code>Alt+N</code>, <code>Ctrl+↓</code></td> <td>Move cursor down by paragraph</td> </tr> <tr> <td><code>Ctrl+E</code>, <code>End</code>, <code>Ctrl+Alt+F</code>, <code>Ctrl+Alt+→</code></td> <td>Move cursor to the end of line</td> </tr> <tr> <td><code>Ctrl+A</code>, <code>Home</code>, <code>Ctrl+Alt+B</code>, <code>Ctrl+Alt+←</code></td> <td>Move cursor to the head of line</td> </tr> <tr> <td><code>Alt+<</code>, <code>Ctrl+Alt+P</code>, <code>Ctrl+Alt+↑</code></td> <td>Move cursor to top of lines</td> </tr> <tr> <td><code>Alt+></code>, <code>Ctrl+Alt+N</code>, <code>Ctrl+Alt+↓</code></td> <td>Move cursor to bottom of lines</td> </tr> <tr> <td><code>Ctrl+V</code>, <code>PageDown</code></td> <td>Scroll down by page</td> </tr> <tr> <td><code>Alt+V</code>, <code>PageUp</code></td> <td>Scroll up by page</td> </tr> </tbody> </table> <p dir="auto">Deleting multiple characters at once saves the deleted text to yank buffer. It can be pasted with <code>Ctrl+Y</code> later.</p> <p dir="auto">If you don't want to use default key mappings, see the 'Advanced Usage' section.</p> <h2 id="user-content-basic-usage" dir="auto">Basic Usage</h2> <h3 id="user-content-create-textarea-instance-with-text" dir="auto">Create <code>TextArea</code> instance with text</h3> <p dir="auto"><code>TextArea</code> implements <code>Default</code> trait to create an editor instance with an empty text.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">let mut textarea = TextArea::default(); </code></pre></div><p dir="auto"><code>TextArea::new()</code> creates an editor instance with text lines passed as <code>Vec<String></code>.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">let mut lines: Vec<String> = ...; let mut textarea = TextArea::new(lines); </code></pre></div><p dir="auto"><code>TextArea</code> implements <code>From<impl Iterator<Item=impl Into<String>>></code>. <code>TextArea::from()</code> can create an editor instance from any iterators whose elements can be converted to <code>String</code>.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">// Create `TextArea` from from `[&str]` let mut textarea = TextArea::from([ "this is first line", "this is second line", "this is third line", ]); // Create `TextArea` from `String` let mut text: String = ...; let mut textarea = TextArea::from(text.lines()); </code></pre></div><p dir="auto"><code>TextArea</code> also implements <code>FromIterator<impl Into<String>></code>. <code>Iterator::collect()</code> can collect strings as an editor instance. This allows to create <code>TextArea</code> reading lines from file efficiently using <code>io::BufReader</code>.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">let file = fs::File::open(path)?; let mut textarea: TextArea = io::BufReader::new(file).lines().collect::<io::Result<_>>()?; </code></pre></div><h3 id="user-content-get-text-contents-from-textarea" dir="auto">Get text contents from <code>TextArea</code></h3> <p dir="auto"><code>TextArea::lines()</code> returns text lines as <code>&[String]</code>. It borrows text contents temporarily.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">let text: String = textarea.lines().join("\n"); </code></pre></div><p dir="auto"><code>TextArea::into_lines()</code> moves <code>TextArea</code> instance into text lines as <code>Vec<String></code>. This can retrieve the text contents without any copy.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">let lines: Vec<String> = textarea.into_lines(); </code></pre></div><p dir="auto">Note that <code>TextArea</code> always contains at least one line. For example, an empty text means one empty line. This is because any text file must end with newline.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">let textarea = TextArea::default(); assert_eq!(textarea.into_lines(), [""]); </code></pre></div><h3 id="user-content-replace-all-text-while-preserving-configuration" dir="auto">Replace all text while preserving configuration</h3> <p dir="auto">When you want to replace the whole buffer without rebuilding <code>TextArea</code> and reapplying styles/configuration, use <code>TextArea::set_lines()</code>.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">let mut textarea = TextArea::default(); textarea.set_placeholder_text("Type here"); textarea.set_line_number_style(ratatui::style::Style::default()); textarea.set_lines( vec!["hello".to_string(), "world".to_string()], (1, 5), ); </code></pre></div><p dir="auto"><code>set_lines()</code> preserves widget configuration such as styles, wrapping, placeholder, and history capacity, while resetting content-specific state such as undo/redo contents, active selection, custom highlights, viewport scroll, and cached measurement results.</p> <h3 id="user-content-show-line-number" dir="auto">Show line number</h3> <p dir="auto">By default, <code>TextArea</code> does not show line numbers. To enable, set a style for rendering line numbers by <code>TextArea::set_line_number_style()</code>. For example, the following renders line numbers in dark gray background color.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use ratatui::style::{Style, Color}; let style = Style::default().bg(Color::DarkGray); textarea.set_line_number_style(style); </code></pre></div><h3 id="user-content-configure-cursor-line-style" dir="auto">Configure cursor line style</h3> <p dir="auto">By default, <code>TextArea</code> renders the line at cursor with underline so that users can easily notice where the current line is. To change the style of cursor line, use <code>TextArea::set_cursor_line_style()</code>. For example, the following styles the cursor line with bold text.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use ratatui::style::{Style, Modifier}; let style = Style::default().add_modifier(Modifier::BOLD); textarea.set_cursor_line_style(style); </code></pre></div><p dir="auto">To disable cursor line style, set the default style as follows:</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use ratatui::style::{Style, Modifier}; textarea.set_cursor_line_style(Style::default()); </code></pre></div><h3 id="user-content-use-a-native-terminal-cursor" dir="auto">Use a native terminal cursor</h3> <p dir="auto">By default, <code>TextArea</code> draws its cursor as a styled cell in the Ratatui buffer. This keeps existing rendering behavior unchanged. Applications that want a native terminal cursor, such as a blinking bar, can hide the drawn cursor and place the terminal cursor after rendering.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use tui_textarea::{CursorRenderMode, TextArea}; textarea.set_cursor_render_mode(CursorRenderMode::Hidden); frame.render_widget(&textarea, area); if let Some(position) = textarea.rendered_cursor_position() { frame.set_cursor_position(position); } </code></pre></div><p dir="auto"><code>tui-textarea-2</code> does not set backend-specific cursor shapes. Configure those in the application, for example with <code>crossterm::cursor::SetCursorStyle::BlinkingBar</code> during terminal setup and reset the shape during teardown.</p> <h3 id="user-content-configure-tab-width" dir="auto">Configure tab width</h3> <p dir="auto">The default tab width is 4. To change it, use <code>TextArea::set_tab_length()</code> method. The following sets 2 to tab width. Typing tab key inserts 2 spaces.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">textarea.set_tab_length(2); </code></pre></div><h3 id="user-content-configure-soft-wrap-mode" dir="auto">Configure soft wrap mode</h3> <p dir="auto">By default, soft wrapping is disabled and long lines are handled by horizontal scrolling. To enable soft wrapping, set <code>TextArea::set_wrap_mode()</code> with one of the supported modes.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use tui_textarea::WrapMode; textarea.set_wrap_mode(WrapMode::WordOrGlyph); </code></pre></div><p dir="auto">Supported modes:</p> <ul dir="auto"> <li><code>WrapMode::None</code>: Disable soft wrap (default behavior).</li> <li><code>WrapMode::Word</code>: Wrap only at word boundaries.</li> <li><code>WrapMode::Glyph</code>: Wrap at grapheme boundaries.</li> <li><code>WrapMode::WordOrGlyph</code>: Wrap at word boundaries with grapheme fallback for long words.</li> </ul> <p dir="auto">When wrapping is enabled, <code>CursorMove::Up</code> and <code>CursorMove::Down</code> follow visual rows instead of jumping only between logical lines.</p> <h3 id="user-content-measure-preferred-height" dir="auto">Measure preferred height</h3> <p dir="auto"><code>TextArea::measure(width_cols)</code> returns a <code>TextAreaMeasure</code> with row counts for the current content and layout. This is useful when your textarea should grow and shrink with wrapped content.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use tui_textarea::{TextArea, WrapMode}; let mut textarea = TextArea::from(["hello world"]); textarea.set_wrap_mode(WrapMode::WordOrGlyph); textarea.set_min_rows(3); textarea.set_max_rows(10); let measured = textarea.measure(12); let content_rows = measured.content_rows; let height = measured.preferred_rows; </code></pre></div><p dir="auto"><code>content_rows</code> counts the rows needed by the inner content area. <code>preferred_rows</code> includes block chrome such as borders and respects the configured <code>min_rows</code> and <code>max_rows</code>.</p> <h3 id="user-content-add-custom-highlighted-ranges" dir="auto">Add custom highlighted ranges</h3> <p dir="auto">You can draw your own highlighted ranges on top of the content with <code>TextArea::custom_highlight()</code>. This is useful for syntax annotations, diffs, or app-specific match highlighting.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use ratatui::style::{Color, Style}; textarea.custom_highlight( ((0, 0), (0, 5)), Style::default().bg(Color::Yellow), 10, ); </code></pre></div><p dir="auto">Call <code>TextArea::clear_custom_highlight()</code> to remove all custom highlighted ranges.</p> <h3 id="user-content-configure-atomic-ranges" dir="auto">Configure atomic ranges</h3> <p dir="auto">Applications can mark caller-parsed text spans as atomic with <code>TextArea::set_atomic_ranges()</code>. Atomic ranges use row and character-column coordinates, remain separate from rendering, and are cleared after successful content mutations so the application can recompute them from the new text.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use ratatui::style::{Color, Style}; use tui_textarea::{AtomicRange, TextArea}; let mut textarea = TextArea::from(["Send <a href="image:cat.png"><img src="image:cat.png" title="image:cat.png" alt="image:cat.png" loading="lazy"/></a> now"]); textarea.set_atomic_ranges([AtomicRange { row: 0, start_col: 5, end_col: 22, }]); textarea.custom_highlight( ((0, 5), (0, 22)), Style::default().fg(Color::Yellow), 10, ); </code></pre></div><p dir="auto">See <a href="./examples/atomic_ranges.rs"><code>atomic_ranges</code> example</a> for a small caller-owned parsing flow.</p> <h3 id="user-content-configure-max-history-size" dir="auto">Configure max history size</h3> <p dir="auto">By default, past 50 modifications are stored as edit history. The history is used for undo/redo. To change how many past edits are remembered, use <code>TextArea::set_max_histories()</code> method. The following remembers past 1000 changes.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">textarea.set_max_histories(1000); </code></pre></div><p dir="auto">Setting 0 disables undo/redo.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">textarea.set_max_histories(0); </code></pre></div><h3 id="user-content-text-search-with-regular-expressions" dir="auto">Text search with regular expressions</h3> <p dir="auto">To search text in textarea, set a regular expression pattern with <code>TextArea::set_search_pattern()</code> and move cursor with <code>TextArea::search_forward()</code> for forward search or <code>TextArea::search_back()</code> backward search. The regular expression is handled by <a href="<a href="https://docs.rs/regex/latest/regex/" data-markdown-generated-content="">https://docs.rs/regex/latest/regex/</a>"><code>regex</code> crate</a>.</p> <p dir="auto">Text search wraps around the textarea. When searching forward and no match found until the end of textarea, it searches the pattern from start of the file.</p> <p dir="auto">Matches are highlighted in textarea. The text style to highlight matches can be changed with <code>TextArea::set_search_style()</code>. Setting an empty string to <code>TextArea::set_search_pattern()</code> stops the text search.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">// Start text search matching to "hello" or "hi". This highlights matches in textarea but does not move cursor. // `regex::Error` is returned on invalid pattern. textarea.set_search_pattern("(hello|hi)").unwrap(); textarea.search_forward(false); // Move cursor to the next match textarea.search_back(false); // Move cursor to the previous match // Setting empty string stops the search textarea.set_search_pattern("").unwrap(); </code></pre></div><p dir="auto">No UI is provided for text search. You need to provide your own UI to input search query. It is recommended to use another <code>TextArea</code> for search form. To build a single-line input form, see 'Single-line input like <code><input></code> in HTML' in 'Advanced Usage' section below.</p> <p dir="auto"><a href="./examples/editor.rs"><code>editor</code> example</a> implements a text search with search form built on <code>TextArea</code>. See the implementation for working example.</p> <p dir="auto">To use text search, <code>search</code> feature needs to be enabled in your <code>Cargo.toml</code>. It is disabled by default to avoid depending on <code>regex</code> crate until it is necessary.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-toml display"><span class="nx">tui-textarea</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">package</span> <span class="p">=</span> <span class="s2">"tui-textarea-2"</span><span class="p">,</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">"*"</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"search"</span><span class="p">]</span> <span class="p">}</span> </code></pre></div><h2 id="user-content-advanced-usage" dir="auto">Advanced Usage</h2> <h3 id="user-content-single-line-input-like-input-in-html" dir="auto">Single-line input like <code><input></code> in HTML</h3> <p dir="auto">To use <code>TextArea</code> for a single-line input widget like <code><input></code> in HTML, ignore all key mappings which inserts newline.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use crossterm::event::{Event, read}; use tui_textarea::{Input, Key}; let default_text: &str = ...; let default_text = default_text.replace(&['\n', '\r'], " "); // Ensure no new line is contained let mut textarea = TextArea::new(vec![default_text]); // Event loop loop { // ... // Using `Input` is not mandatory, but it's useful for pattern match // Ignore Ctrl+m and Enter. Otherwise handle keys as usual match read()?.into() { Input { key: Key::Char('m'), ctrl: true, alt: false } | Input { key: Key::Enter, .. } => continue, input => { textarea.input(input); } } } let text = textarea.into_lines().remove(0); // Get input text </code></pre></div><p dir="auto">See <a href="./examples/single_line.rs"><code>single_line</code> example</a> for working example.</p> <h3 id="user-content-define-your-own-key-mappings" dir="auto">Define your own key mappings</h3> <p dir="auto">All editor operations are defined as public methods of <code>TextArea</code>. To move cursor, use <code>tui_textarea::CursorMove</code> to notify how to move the cursor.</p> <table> <thead> <tr> <th>Method</th> <th>Operation</th> </tr> </thead> <tbody> <tr> <td><code>textarea.delete_char()</code></td> <td>Delete one character before cursor</td> </tr> <tr> <td><code>textarea.delete_next_char()</code></td> <td>Delete one character next to cursor</td> </tr> <tr> <td><code>textarea.insert_newline()</code></td> <td>Insert newline</td> </tr> <tr> <td><code>textarea.delete_line_by_end()</code></td> <td>Delete from cursor until the end of line</td> </tr> <tr> <td><code>textarea.delete_line_by_head()</code></td> <td>Delete from cursor until the head of line</td> </tr> <tr> <td><code>textarea.delete_word()</code></td> <td>Delete one word before cursor</td> </tr> <tr> <td><code>textarea.delete_next_word()</code></td> <td>Delete one word next to cursor</td> </tr> <tr> <td><code>textarea.clear()</code></td> <td>Clear all text</td> </tr> <tr> <td><code>textarea.undo()</code></td> <td>Undo</td> </tr> <tr> <td><code>textarea.redo()</code></td> <td>Redo</td> </tr> <tr> <td><code>textarea.copy()</code></td> <td>Copy selected text</td> </tr> <tr> <td><code>textarea.cut()</code></td> <td>Cut selected text</td> </tr> <tr> <td><code>textarea.paste()</code></td> <td>Paste yanked text</td> </tr> <tr> <td><code>textarea.insert_char(c)</code></td> <td>Insert one character</td> </tr> <tr> <td><code>textarea.insert_str(text)</code></td> <td>Insert a string</td> </tr> <tr> <td><code>textarea.insert_tab()</code></td> <td>Insert indentation / tab text</td> </tr> <tr> <td><code>textarea.delete_str(chars)</code></td> <td>Delete multiple characters</td> </tr> <tr> <td><code>textarea.start_selection()</code></td> <td>Start text selection</td> </tr> <tr> <td><code>textarea.cancel_selection()</code></td> <td>Cancel text selection</td> </tr> <tr> <td><code>textarea.select_all()</code></td> <td>Select entire text</td> </tr> <tr> <td><code>textarea.custom_highlight(range, style, priority)</code></td> <td>Add a custom highlighted range</td> </tr> <tr> <td><code>textarea.clear_custom_highlight()</code></td> <td>Clear all custom highlights</td> </tr> <tr> <td><code>textarea.set_atomic_ranges(ranges)</code></td> <td>Set caller-owned indivisible text spans</td> </tr> <tr> <td><code>textarea.try_set_atomic_ranges(ranges)</code></td> <td>Validate and set atomic ranges without panics</td> </tr> <tr> <td><code>textarea.clear_atomic_ranges()</code></td> <td>Clear all atomic ranges</td> </tr> <tr> <td><code>textarea.atomic_ranges()</code></td> <td>Get configured atomic ranges</td> </tr> <tr> <td><code>textarea.delete_atomic_range_at_cursor(direction)</code></td> <td>Delete an atom at the cursor as one edit</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::Forward)</code></td> <td>Move cursor forward by one character</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::Back)</code></td> <td>Move cursor backward by one character</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::Up)</code></td> <td>Move cursor up by one line</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::Down)</code></td> <td>Move cursor down by one line</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::WordForward)</code></td> <td>Move cursor forward by word</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::WordEnd)</code></td> <td>Move cursor to next end of word</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::WordBack)</code></td> <td>Move cursor backward by word</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::ParagraphForward)</code></td> <td>Move cursor up by paragraph</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::ParagraphBack)</code></td> <td>Move cursor down by paragraph</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::End)</code></td> <td>Move cursor to the end of line</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::Head)</code></td> <td>Move cursor to the head of line</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::Top)</code></td> <td>Move cursor to top of lines</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::Bottom)</code></td> <td>Move cursor to bottom of lines</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::Jump(row, col))</code></td> <td>Move cursor to (row, col) position</td> </tr> <tr> <td><code>textarea.move_cursor(CursorMove::InViewport)</code></td> <td>Move cursor to stay in the viewport</td> </tr> <tr> <td><code>textarea.set_search_pattern(pattern)</code></td> <td>Set a pattern for text search</td> </tr> <tr> <td><code>textarea.search_forward(match_cursor)</code></td> <td>Move cursor to next match of text search</td> </tr> <tr> <td><code>textarea.search_back(match_cursor)</code></td> <td>Move cursor to previous match of text search</td> </tr> <tr> <td><code>textarea.scroll(Scrolling::PageDown)</code></td> <td>Scroll down the viewport by page</td> </tr> <tr> <td><code>textarea.scroll(Scrolling::PageUp)</code></td> <td>Scroll up the viewport by page</td> </tr> <tr> <td><code>textarea.scroll(Scrolling::HalfPageDown)</code></td> <td>Scroll down the viewport by half-page</td> </tr> <tr> <td><code>textarea.scroll(Scrolling::HalfPageUp)</code></td> <td>Scroll up the viewport by half-page</td> </tr> <tr> <td><code>textarea.scroll((row, col))</code></td> <td>Scroll down the viewport to (row, col) position</td> </tr> </tbody> </table> <p dir="auto">To define your own key mappings, simply call the above methods in your code instead of <code>TextArea::input()</code> method.</p> <p dir="auto">Useful state/configuration helpers:</p> <table> <thead> <tr> <th>Method</th> <th>Purpose</th> </tr> </thead> <tbody> <tr> <td><code>textarea.cursor()</code></td> <td>Get current <code>(row, col)</code> cursor position</td> </tr> <tr> <td><code>textarea.selection_range()</code></td> <td>Get the current selected range if selection is active</td> </tr> <tr> <td><code>textarea.is_selecting()</code></td> <td>Check whether selection is active</td> </tr> <tr> <td><code>textarea.lines()</code></td> <td>Borrow the current text lines</td> </tr> <tr> <td><code>textarea.set_lines(lines, cursor)</code></td> <td>Replace the entire buffer while preserving widget settings</td> </tr> <tr> <td><code>textarea.set_wrap_mode(mode)</code></td> <td>Configure soft wrapping</td> </tr> <tr> <td><code>textarea.wrap_mode()</code></td> <td>Read the current wrap mode</td> </tr> <tr> <td><code>textarea.set_min_rows(rows)</code></td> <td>Set the minimum preferred measured height</td> </tr> <tr> <td><code>textarea.min_rows()</code></td> <td>Read the configured minimum preferred height</td> </tr> <tr> <td><code>textarea.set_max_rows(rows)</code></td> <td>Set the maximum preferred measured height</td> </tr> <tr> <td><code>textarea.max_rows()</code></td> <td>Read the configured maximum preferred height</td> </tr> <tr> <td><code>textarea.measure(width_cols)</code></td> <td>Measure content and preferred outer height</td> </tr> <tr> <td><code>textarea.set_block(block)</code></td> <td>Configure block chrome used for rendering and measurement</td> </tr> <tr> <td><code>textarea.remove_block()</code></td> <td>Remove block chrome</td> </tr> <tr> <td><code>textarea.set_line_number_style(style)</code></td> <td>Enable or restyle line numbers</td> </tr> <tr> <td><code>textarea.remove_line_number()</code></td> <td>Disable line numbers</td> </tr> <tr> <td><code>textarea.set_cursor_render_mode(mode)</code></td> <td>Draw or hide the textarea-owned cursor cell</td> </tr> <tr> <td><code>textarea.cursor_render_mode()</code></td> <td>Read the current cursor render mode</td> </tr> <tr> <td><code>textarea.rendered_cursor_position()</code></td> <td>Get the last rendered terminal cursor position</td> </tr> <tr> <td><code>textarea.set_placeholder_text(text)</code></td> <td>Set or disable placeholder text</td> </tr> <tr> <td><code>textarea.set_placeholder_style(style)</code></td> <td>Change placeholder style</td> </tr> <tr> <td><code>textarea.set_mask_char(ch)</code></td> <td>Enable character masking</td> </tr> <tr> <td><code>textarea.clear_mask_char()</code></td> <td>Disable character masking</td> </tr> <tr> <td><code>textarea.clear()</code></td> <td>Clear the full buffer</td> </tr> </tbody> </table> <p dir="auto">See the <a href="./examples/vim.rs"><code>vim</code> example</a> for working example. It implements more Vim-like key modal mappings.</p> <p dir="auto">If you don't want to use default key mappings, <code>TextArea::input_without_shortcuts()</code> method can be used instead of <code>TextArea::input()</code>. The method only handles very basic operations such as inserting/deleting single characters, tabs, newlines.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">match read()?.into() { // Handle your own key mappings here // ... input => textarea.input_without_shortcuts(input), } </code></pre></div><h3 id="user-content-use-your-own-backend" dir="auto">Use your own backend</h3> <p dir="auto">ratatui allows to make your own backend by implementing <a href="<a href="https://docs.rs/ratatui/latest/ratatui/backend/trait.Backend.html" data-markdown-generated-content="">https://docs.rs/ratatui/latest/ratatui/backend/trait.Backend.html</a>"><code>ratatui::backend::Backend</code></a> trait. tui-textarea supports it as well. Please use the <code>no-backend</code> feature. It avoids adding backend crates (crossterm, termion, or termwiz) since you're using your own backend.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-toml display"><span class="p">[</span><span class="nx">dependencies</span><span class="p">]</span> <span class="nx">tui-textarea</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">package</span> <span class="p">=</span> <span class="s2">"tui-textarea-2"</span><span class="p">,</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">"*"</span><span class="p">,</span> <span class="nx">default-features</span> <span class="p">=</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"no-backend"</span><span class="p">]</span> <span class="p">}</span> </code></pre></div><p dir="auto"><code>tui_textarea::Input</code> is a type for backend-agnostic key input. What you need to do is converting key event in your own backend into the <code>tui_textarea::Input</code> instance. Then <code>TextArea::input()</code> method can handle the input as other backend.</p> <p dir="auto">In the following example, let's say <code>your_backend::KeyDown</code> is a key event type for your backend and <code>your_backend::read_next_key()</code> returns the next key event.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">// In your backend implementation pub enum KeyDown { Char(char), BS, Del, Esc, // ... } // Return tuple of (key, ctrlkey, altkey) pub fn read_next_key() -> (KeyDown, bool, bool) { // ... } </code></pre></div><p dir="auto">Then you can implement the logic to convert <code>your_backend::KeyDown</code> value into <code>tui_textarea::Input</code> value.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use tui_textarea::{Input, Key}; use your_backend::KeyDown; fn keydown_to_input(key: KeyDown, ctrl: bool, alt: bool) -> Input { match key { KeyDown::Char(c) => Input { key: Key::Char(c), ctrl, alt }, KeyDown::BS => Input { key: Key::Backspace, ctrl, alt }, KeyDown::Del => Input { key: Key::Delete, ctrl, alt }, KeyDown::Esc => Input { key: Key::Esc, ctrl, alt }, // ... _ => Input::default(), } } </code></pre></div><p dir="auto">For the keys which are not handled by tui-textarea, <code>tui_textarea::Input::default()</code> is available. It returns 'null' key. An editor will do nothing with the key.</p> <p dir="auto">Finally, convert your own backend's key input type into <code>tui_textarea::Input</code> and pass it to <code>TextArea::input()</code>.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">let mut textarea = ...; // Event loop loop { // ... let (key, ctrl, alt) = your_backend::read_next_key(); if key == your_backend::KeyDown::Esc { break; // For example, quit your app on pressing Esc } textarea.input(keydown_to_input(key, ctrl, alt)); } </code></pre></div><h3 id="user-content-put-multiple-textarea-instances-in-screen" dir="auto">Put multiple <code>TextArea</code> instances in screen</h3> <p dir="auto">You don't need to do anything special. Create multiple <code>TextArea</code> instances and render widgets built from each instances.</p> <p dir="auto">The following is an example to put two textarea widgets in application and manage the focus.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use tui_textarea::{TextArea, Input, Key}; use crossterm::event::{Event, read}; let editors = &mut [ TextArea::default(), TextArea::default(), ]; let mut focused = 0; loop { term.draw(|f| { let rects = ...; for (editor, rect) in editors.iter().zip(rects.into_iter()) { f.render_widget(editor, rect); } })?; match read()?.into() { // Switch focused textarea by Ctrl+S Input { key: Key::Char('s'), ctrl: true, .. } => focused = (focused + 1) % 2; // Handle input by the focused editor input => editors[focused].input(input), } } </code></pre></div><p dir="auto">See <a href="./examples/split.rs"><code>split</code> example</a> and <a href="./examples/editor.rs"><code>editor</code> example</a> for working example.</p> <h3 id="user-content-serializationdeserialization-support" dir="auto">Serialization/Deserialization support</h3> <p dir="auto">This crate optionally supports <a href="<a href="https://crates.io/crates/serde" data-markdown-generated-content="">https://crates.io/crates/serde</a>">serde</a> crate by enabling <code>serde</code> feature.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-toml display"><span class="p">[</span><span class="nx">dependencies</span><span class="p">]</span> <span class="nx">tui-textarea</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">package</span> <span class="p">=</span> <span class="s2">"tui-textarea-2"</span><span class="p">,</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">"*"</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"serde"</span><span class="p">]</span> <span class="p">}</span> </code></pre></div><p dir="auto">Values of the following types can be serialized/deserialized:</p> <ul dir="auto"> <li><code>Key</code></li> <li><code>Input</code></li> <li><code>CursorMove</code></li> <li><code>Scrolling</code></li> <li><code>WrapMode</code></li> <li><code>AtomicRange</code></li> <li><code>AtomicCursorBias</code></li> <li><code>AtomicDeleteDirection</code></li> <li><code>AtomicRangeError</code></li> <li><code>RejectedAtomicRange</code></li> <li><code>AtomicRangeRejectReason</code></li> </ul> <p dir="auto">Here is an example for deserializing key input from JSON using <a href="<a href="https://crates.io/crates/serde_json" data-markdown-generated-content="">https://crates.io/crates/serde_json</a>">serde_json</a>.</p> <div data-attr-class="K72ShDHCsbVbl0Yv:code-block-container code-overflow-scroll"><pre data-attr-class="K72ShDHCsbVbl0Yv:code-block"><code data-attr-class="K72ShDHCsbVbl0Yv:chroma language-rust,ignore display">use tui_textarea::Input; let json = r#" { "key": { "Char": "a" }, "ctrl": true, "alt": false, "shift": true } "#; let input: Input = serde_json::from_str(json).unwrap(); println!("{input:?}"); // Input { // key: Key::Char('a'), // ctrl: true, // alt: false, // shift: true, // } </code></pre></div><h2 id="user-content-minimum-supported-rust-version" dir="auto">Minimum Supported Rust Version</h2> <p dir="auto">MSRV of this crate is Rust 1.85.0 because the crate uses Rust 2024 edition.</p> <p dir="auto">This crate targets Rust 1.85.0 regardless of which supported ratatui backend feature you enable.</p> <h2 id="user-content-versioning" dir="auto">Versioning</h2> <p dir="auto">This crate is not reaching v1.0.0 yet. There is no plan to bump the major version for now. Current versioning policy is as follows:</p> <ul dir="auto"> <li>Major: Fixed to 0</li> <li>Minor: Bump on breaking change</li> <li>Patch: Bump on new feature or bug fix</li> </ul> <h2 id="user-content-contributing-to-tui-textarea-2" dir="auto">Contributing to tui-textarea-2</h2> <p dir="auto">This project is developed <a href="<a href="https://github.com/srothgan/tui-textarea" data-markdown-generated-content="">https://github.com/srothgan/tui-textarea</a>">on GitHub</a>.</p> <p dir="auto">For feature requests or bug reports, please <a href="<a href="https://github.com/srothgan/tui-textarea/issues/new" data-markdown-generated-content="">https://github.com/srothgan/tui-textarea/issues/new</a>">create an issue</a>. For submitting patches, please <a href="<a href="https://github.com/srothgan/tui-textarea/pulls" data-markdown-generated-content="">https://github.com/srothgan/tui-textarea/pulls</a>">create a pull request</a>.</p> <p dir="auto">Please read <a href="./CONTRIBUTING.md">CONTRIBUTING.md</a> before reporting an issue or making a PR.</p> <h2 id="user-content-license" dir="auto">License</h2> <p dir="auto">tui-textarea-2 is distributed under <a href="./LICENSE">The MIT License</a>.</p> </body></html>

Описание
No description provided
Readme MIT 968 KiB
Languages
Rust 100%