Git Product home page Git Product logo

tui-textarea's Introduction

tui-textarea

crate docs CI coverage

tui-textarea is a simple yet powerful text editor widget like <textarea> in HTML for ratatui and tui-rs. Multi-line text editor can be easily put as part of your TUI application.

Features:

  • Multi-line text editor widget with basic operations (insert/delete characters, auto scrolling, ...)
  • Emacs-like shortcuts (C-n/C-p/C-f/C-b, M-f/M-b, C-a/C-e, C-h/C-d, C-k, M-</M->, ...)
  • Undo/Redo
  • Line number
  • Cursor line highlight
  • Search with regular expressions
  • Text selection
  • Mouse scrolling
  • Yank support. Paste text deleted with C-k, C-j, ...
  • Backend agnostic. crossterm, termion, termwiz, and your own backend are all supported
  • Multiple textarea widgets in the same screen
  • Support both ratatui (the fork by community) and tui-rs (the original)

Documentation

Examples

Running cargo run --example in this repository can demonstrate usage of tui-textarea.

cargo run --example minimal

Minimal usage with crossterm support.

minimal example

cargo run --example editor --features search file.txt

Simple text editor to edit multiple files.

editor example

cargo run --example single_line

Single-line input form with float number validation.

single line example

cargo run --example split

Two split textareas in a screen and switch them. An example for multiple textarea instances.

multiple textareas example

cargo run --example variable

Simple textarea with variable height following the number of lines.

cargo run --example vim

Vim-like modal text editor. Vim emulation is implemented as a state machine.

Vim emulation example

cargo run --example popup_placeholder

Popup textarea with a placeholder text.

popup textarea with placeholder example

cargo run --example password

Password input form with masking text with ●.

password example

cargo run --example termion --no-default-features --features=termion

Minimal usage with termion support.

cargo run --example termwiz --no-default-features --features=termwiz

Minimal usage with termwiz support.

Examples for tui-rs support

All above examples use ratatui, but some examples provide tui-rs version. Try tuirs_ prefix. In these cases, you need to specify features to use tui-rs and --no-default-features flag explicitly.

# tui-rs version of `minimal` example
cargo run --example tuirs_minimal --no-default-features --features=tuirs-crossterm

# tui-rs version of `editor` example
cargo run --example tuirs_editor --no-default-features --features=tuirs-crossterm,search file.txt

# tui-rs version of `termion` example
cargo run --example tuirs_termion --no-default-features --features=tuirs-termion

Installation

Add tui-textarea crate to dependencies in your Cargo.toml. This enables crossterm backend support by default.

[dependencies]
ratatui = "*"
tui-textarea = "*"

If you need text search with regular expressions, enable search feature. It adds regex crate crate as dependency.

[dependencies]
ratatui = "*"
tui-textarea = { version = "*", features = ["search"] }

If you're using ratatui with termion or termwiz, enable respective feature instead of crossterm feature.

[dependencies]

# For termion
ratatui = { version = "*", default-features = false, features = ["termion"] }
tui-textarea = { version = "*", default-features = false, features = ["termion"] }

# For termwiz
ratatui = { version = "*", default-features = false, features = ["termwiz"] }
tui-textarea = { version = "*", default-features = false, features = ["termwiz"] }

If you're using tui-rs instead of ratatui, you need to enable features for using tui-rs crate and to disable default features. The following table shows feature names corresponding to the dependencies.

crossterm termion termwiz Your own backend
ratatui crossterm (enabled by default) termion termwiz no-backend
tui-rs tuirs-crossterm tuirs-termion N/A tuirs-no-backend

For example, when you want to use the combination of tui-rs and crossterm,

[dependencies]
tui = "*"
tui-textarea = { version = "*", features = ["tuirs-crossterm"], default-features = false }

Note that ratatui support and tui-rs support are exclusive. When you use tui-rs support, you must disable ratatui support by default-features = false.

In addition to above dependencies, you also need to install crossterm or termion or termwiz to initialize your application and to receive key inputs. Note that version of crossterm crate is different between ratatui and tui-rs. Please select the correct version.

Minimal Usage

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 = ...;
        // `TextArea::widget` builds a widget to render the editor with tui
        let widget = textarea.widget();
        // Render the widget in terminal screen
        f.render_widget(widget, 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());

TextArea is an instance to manage the editor state. By default, it disables line numbers and highlights cursor line with underline.

TextArea::widget() builds a widget to render the current state of the editor. Create the widget and render it on each tick of event loop.

TextArea::input() receives inputs from tui backends. The method can take key events from backends such as crossterm::event::KeyEvent or termion::event::Key directly if the features are enabled. The method handles default key mappings as well.

Default key mappings are as follows:

Mappings Description
Ctrl+H, Backspace Delete one character before cursor
Ctrl+D, Delete Delete one character next to cursor
Ctrl+M, Enter Insert newline
Ctrl+K Delete from cursor until the end of line
Ctrl+J Delete from cursor until the head of line
Ctrl+W, Alt+H, Alt+Backspace Delete one word before cursor
Alt+D, Alt+Delete Delete one word next to cursor
Ctrl+U Undo
Ctrl+R Redo
Ctrl+C, Copy Copy selected text
Ctrl+X, Cut Cut selected text
Ctrl+Y, Paste Paste yanked text
Ctrl+F, Move cursor forward by one character
Ctrl+B, Move cursor backward by one character
Ctrl+P, Move cursor up by one line
Ctrl+N, Move cursor down by one line
Alt+F, Ctrl+→ Move cursor forward by word
Atl+B, Ctrl+← Move cursor backward by word
Alt+], Alt+P, Ctrl+↑ Move cursor up by paragraph
Alt+[, Alt+N, Ctrl+↓ Move cursor down by paragraph
Ctrl+E, End, Ctrl+Alt+F, Ctrl+Alt+→ Move cursor to the end of line
Ctrl+A, Home, Ctrl+Alt+B, Ctrl+Alt+← Move cursor to the head of line
Alt+<, Ctrl+Alt+P, Ctrl+Alt+↑ Move cursor to top of lines
Alt+>, Ctrl+Alt+N, Ctrl+Alt+↓ Move cursor to bottom of lines
Ctrl+V, PageDown Scroll down by page
Alt+V, PageUp Scroll up by page

Deleting multiple characters at once saves the deleted text to yank buffer. It can be pasted with Ctrl+Y later.

If you don't want to use default key mappings, see the 'Advanced Usage' section.

Basic Usage

Create TextArea instance with text

TextArea implements Default trait to create an editor instance with an empty text.

let mut textarea = TextArea::default();

TextArea::new() creates an editor instance with text lines passed as Vec<String>.

let mut lines: Vec<String> = ...;
let mut textarea = TextArea::new(lines);

TextArea implements From<impl Iterator<Item=impl Into<String>>>. TextArea::from() can create an editor instance from any iterators whose elements can be converted to String.

// 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());

TextArea also implements FromIterator<impl Into<String>>. Iterator::collect() can collect strings as an editor instance. This allows to create TextArea reading lines from file efficiently using io::BufReader.

let file = fs::File::open(path)?;
let mut textarea: TextArea = io::BufReader::new(file).lines().collect::<io::Result<_>>()?;

Get text contents from TextArea

TextArea::lines() returns text lines as &[String]. It borrows text contents temporarily.

let text: String = textarea.lines().join("\n");

TextArea::into_lines() moves TextArea instance into text lines as Vec<String>. This can retrieve the text contents without any copy.

let lines: Vec<String> = textarea.into_lines();

Note that TextArea 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.

let textarea = TextArea::default();
assert_eq!(textarea.into_lines(), [""]);

Show line number

By default, TextArea does now show line numbers. To enable, set a style for rendering line numbers by TextArea::set_line_number_style(). For example, the following renders line numbers in dark gray background color.

use ratatui::style::{Style, Color};

let style = Style::default().bg(Color::DarkGray);
textarea.set_line_number_style(style);

Configure cursor line style

By default, TextArea 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 TextArea::set_cursor_line_style(). For example, the following styles the cursor line with bold text.

use ratatui::style::{Style, Modifier};

let style = Style::default().add_modifier(Modifier::BOLD);
textarea.set_line_number_style(style);

To disable cursor line style, set the default style as follows:

use ratatui::style::{Style, Modifier};

textarea.set_line_number_style(Style::default());

Configure tab width

The default tab width is 4. To change it, use TextArea::set_tab_length() method. The following sets 2 to tab width. Typing tab key inserts 2 spaces.

textarea.set_tab_length(2);

Configure max history size

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 TextArea::set_max_histories() method. The following remembers past 1000 changes.

textarea.set_max_histories(1000);

Setting 0 disables undo/redo.

textarea.set_max_histories(0);

Text search with regular expressions

To search text in textarea, set a regular expression pattern with TextArea::set_search_pattern() and move cursor with TextArea::search_forward() for forward search or TextArea::search_back() backward search. The regular expression is handled by regex crate.

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.

Matches are highlighted in textarea. The text style to highlight matches can be changed with TextArea::set_search_style(). Setting an empty string to TextArea::set_search_pattern() stops the text search.

// 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();

No UI is provided for text search. You need to provide your own UI to input search query. It is recommended to use another TextArea for search form. To build a single-line input form, see 'Single-line input like <input> in HTML' in 'Advanced Usage' section below.

editor example implements a text search with search form built on TextArea. See the implementation for working example.

To use text search, search feature needs to be enabled in your Cargo.toml. It is disabled by default to avoid depending on regex crate until it is necessary.

tui-textarea = { version = "*", features = ["search"] }

Advanced Usage

Single-line input like <input> in HTML

To use TextArea for single-line input widget like <input> in HTML, ignore all key mappings which inserts newline.

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(key);
        }
    }
}

let text = textarea.into_lines().remove(0); // Get input text

See single_line example for working example.

Define your own key mappings

All editor operations are defined as public methods of TextArea. To move cursor, use tui_textarea::CursorMove to notify how to move the cursor.

Method Operation
textarea.delete_char() Delete one character before cursor
textarea.delete_next_char() Delete one character next to cursor
textarea.insert_newline() Insert newline
textarea.delete_line_by_end() Delete from cursor until the end of line
textarea.delete_line_by_head() Delete from cursor until the head of line
textarea.delete_word() Delete one word before cursor
textarea.delete_next_word() Delete one word next to cursor
textarea.undo() Undo
textarea.redo() Redo
textarea.copy() Copy selected text
textarea.cut() Cut selected text
textarea.paste() Paste yanked text
textarea.start_selection() Start text selection
textarea.cancel_selection() Cancel text selection
textarea.select_all() Select entire text
textarea.move_cursor(CursorMove::Forward) Move cursor forward by one character
textarea.move_cursor(CursorMove::Back) Move cursor backward by one character
textarea.move_cursor(CursorMove::Up) Move cursor up by one line
textarea.move_cursor(CursorMove::Down) Move cursor down by one line
textarea.move_cursor(CursorMove::WordForward) Move cursor forward by word
textarea.move_cursor(CursorMove::WordBack) Move cursor backward by word
textarea.move_cursor(CursorMove::ParagraphForward) Move cursor up by paragraph
textarea.move_cursor(CursorMove::ParagraphBack) Move cursor down by paragraph
textarea.move_cursor(CursorMove::End) Move cursor to the end of line
textarea.move_cursor(CursorMove::Head) Move cursor to the head of line
textarea.move_cursor(CursorMove::Top) Move cursor to top of lines
textarea.move_cursor(CursorMove::Bottom) Move cursor to bottom of lines
textarea.move_cursor(CursorMove::Jump(row, col)) Move cursor to (row, col) position
textarea.move_cursor(CursorMove::InViewport) Move cursor to stay in the viewport
textarea.set_search_pattern(pattern) Set a pattern for text search
textarea.search_forward(match_cursor) Move cursor to next match of text search
textarea.search_back(match_cursor) Move cursor to previous match of text search
textarea.scroll(Scrolling::PageDown) Scroll down the viewport by page
textarea.scroll(Scrolling::PageUp) Scroll up the viewport by page
textarea.scroll(Scrolling::HalfPageDown) Scroll down the viewport by half-page
textarea.scroll(Scrolling::HalfPageUp) Scroll up the viewport by half-page
textarea.scroll((row, col)) Scroll down the viewport to (row, col) position

To define your own key mappings, simply call the above methods in your code instead of TextArea::input() method.

See the vim example for working example. It implements more Vim-like key modal mappings.

If you don't want to use default key mappings, TextArea::input_without_shortcuts() method can be used instead of TextArea::input(). The method only handles very basic operations such as inserting/deleting single characters, tabs, newlines.

match read()?.into() {
    // Handle your own key mappings here
    // ...
    input => textarea.input_without_shortcuts(input),
}

Use your own backend

ratatui and tui-rs allows to make your own backend by implementing ratatui::backend::Backend trait. tui-textarea supports it as well. Please use no-backend feature for ratatui or tuirs-no-backend feature for tui-rs. They avoid adding backend crates (crossterm, termion, or termwiz) since you're using your own backend.

[dependencies]
# For ratatui
tui-textarea = { version = "*", default-features = false, features = ["no-backend"] }
# For tui-rs
tui-textarea = { version = "*", default-features = false, features = ["tuirs-no-backend"] }

tui_textarea::Input is a type for backend-agnostic key input. What you need to do is converting key event in your own backend into the tui_textarea::Input instance. Then TextArea::input() method can handle the input as other backend.

In the following example, let's say your_backend::KeyDown is a key event type for your backend and your_backend::read_next_key() returns the next key event.

// 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) {
    // ...
}

Then you can implement the logic to convert your_backend::KeyDown value into tui_textarea::Input value.

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(),
    }
}

For the keys which are not handled by tui-textarea, tui_textarea::Input::default() is available. It returns 'null' key. An editor will do nothing with the key.

Finally, convert your own backend's key input type into tui_textarea::Input and pass it to TextArea::input().

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));
}

Put multiple TextArea instances in screen

You don't need to do anything special. Create multiple TextArea instances and render widgets built from each instances.

The following is an example to put two textarea widgets in application and manage the focus.

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_mut().zip(rects.into_iter()) {
            let widget = editor.widget();
            f.render_widget(widget, 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),
    }
}

See split example and editor example for working example.

Minimum Supported Rust Version

MSRV of this crate is depending on tui crate. Currently MSRV is 1.56.1. Note that ratatui crate requires more recent Rust version.

Versioning

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:

  • Major: Fixed to 0
  • Minor: Bump on breaking change
  • Patch: Bump on new feature or bug fix

Contributing to tui-textarea

This project is developed on GitHub.

For feature requests or bug reports, please create an issue. For submitting patches, please create a pull request.

Please read CONTRIBUTING.md before reporting an issue or making a PR.

License

tui-textarea is distributed under The MIT License.

tui-textarea's People

Contributors

fritzrehde avatar pm100 avatar rhysd avatar volkalex28 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

tui-textarea's Issues

Change cursor shape

I've been trying to change the cursor style to a blinking bar for an editor program, but it seems like there isn't a way to do this beyond changing cursor color and blink rate.

I've tried doing so through crossterm but it seems like this is ignored in the rendering of the textarea.

//main.rs
...
use crossterm::cursor::SetCursorStyle;
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, EnterAlternateScreen,
    LeaveAlternateScreen,
};
...
    crossterm::execute!(term.backend_mut(), SetCursorStyle::SteadyBar).unwrap();

    // Main render loop
    let mutex = Mutex::new(());
    while running.load(Ordering::SeqCst) {
        term.draw(|f| {
            let buffer = buffer.lock().unwrap();
            let buffer_widget = buffer.textarea.widget();
            let rectangle = Rect::new(0, 0, f.size().width, f.size().height);
            f.render_widget(buffer_widget, rectangle);
        })
        .unwrap();

        // TUI refresh rate
        let guard = mutex.lock().unwrap();
        _ = condvar.wait_timeout(guard, Duration::from_millis(50));
    }
...

And my implementation of Buffer which creates the TextArea

...
impl Buffer {
    pub fn new(config: &Config) -> io::Result<Self> {
        // TODO: Generate path
        let path = config.output_name.clone();

        let file_already_existed = path.exists();
        let mut textarea = if let Ok(md) = path.metadata() {
            if md.is_file() {
                let contents = fs::read_to_string(path.clone())?;
                TextArea::from(contents.lines())
            } else {
                // Path exists but is not a file
                return Err(io::Error::new(
                    io::ErrorKind::Other,
                    format!("{:?} exists but is not a file", path),
                ));
            }
        } else {
            TextArea::default() // File does not exist
        };
        textarea.set_hard_tab_indent(config.use_hard_indent);
        // Remove default underline style from active line
        textarea.set_cursor_line_style(Style::default());
        Ok(Self {
            textarea,
            path,
            modified: false,
            file_already_existed,
        })
    }
    ...

I've tried calling textarea.set_cursor_style from here (https://docs.rs/tui-textarea/latest/tui_textarea/struct.TextArea.html#method.set_cursor_style), but the Style struct from the tui crate just doesn't seem to support a way to change the cursor shape.

Is this a limitation of the tui crate? Is there a way I can change my cursor shape?

Widget not implemented?

Anyone have any idea how to fix this?

error[E0277]: the trait bound `impl ratatui::widgets::Widget + '_: Widget` is not satisfied
  --> src\components\text_entry.rs:43:21
   |
43 |     f.render_widget(tw, rect);
   |       ------------- ^^ the trait `Widget` is not implemented for `impl ratatui::widgets::Widget + '_`
   |       |
   |       required by a bound introduced by this call
   |
   = help: the following other types implement trait `Widget`:
             BarChart<'a>
             Canvas<'a, F>
             Chart<'a>
             Gauge<'a>
             LineGauge<'a>
             List<'a>
             Paragraph<'a>
             Sparkline<'a>
           and 7 others
note: required by a bound in `ratatui::Frame::<'a, B>::render_widget`
  --> C:\Users\haydu\.cargo\registry\src\github.com-1ecc6299db9ec823\ratatui-0.21.0\src\terminal.rs:88:12
   |
86 |     pub fn render_widget<W>(&mut self, widget: W, area: Rect)
   |            ------------- required by a bound in this associated function
87 |     where
88 |         W: Widget,
   |            ^^^^^^ required by this bound in `Frame::<'a, B>::render_widget`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `wd` (lib) due to previous error

Support bracketed-paste terminal mode

Both crossterm and termwiz are supporting bracketed-paste:

  • crossterm opts in bracketed-paste when EnableBracketedPaste is executed. Pasted text is sent via Event::Paste.
  • termwiz automatically enables bracketed-paste if the terminal supports it. And there is no way to disable it. Pasted text is sent via InputEvent::Paste.

However tui-textarea doesn't support bracketed-paste yet. When pasting some text through terminal, the pasted text from terminal is simply ignored.

It's possible to handle the paste event in user side. However it has the following downsides:

  • Those who are not familiar with terminals usually don't know bracketed-paste.
  • Users need to know the details of backend-specific input events. Users should be able to use Input::from without knowing the details of backend-specific input event.
  • Multiple lines are joined with \r. Users need to replace \r with \n before setting the pasted text via TextArea::set_yank_text.

TextArea::input should handle the bracketed-paste events. This feature will require restructuring Input struct into enum because they are not key inputs. Currently tui-textarea assumes all inputs are by keyboard (though mouse virtual keys are already not fitting to this assumption well). The assumption is no longer applicable.

Before:

struct Input {
    key: Key,
    ctrl: bool,
    // ... 
}

After:

struct KeyInput {
    key: Key,
    ctrl: bool,
    // ...
}

struct MouseInput {
    mouse: Mouse,
    ctrl: bool,
    // ...
}

enum Input {
    Key(KeyInput),
    Mouse(MouseInput),
    Paste(String),
}

This means that the input structure is made more complicated. Input can no longer derive Copy.

What's your text buffer storage implement?

Hello, this crate is awesome, but I want to know the performance of it compare to ropey(I want to write an editor and use this crate)
What's your data structure for text storage?

The reason why I opend this issue is that ropey is a useful crate for text editng, and I want to integrate it with your crate, for best editing performance

refactor key input handling

[The reason I am raising these feature and api requests is because I am trying to get a large project , gitui, to replace their own textinput widget with TTA. I want

  • to make my code simpler
  • to make the change as non invasive as possible
  • have compelling features

this will make TTA attractive to other developers too (This is a good thing for TTA). I could of course just fork TTA but that seems counter productive]

This change is a big one. I started it on the original select PR but did not explain it so you rejected it - fine.

First the core of TTA should have nothing to do with crossterm, termion, termwiz, or any other key / mouse input. It is fundamentally an API driven widget. Moving all that code to a set of sub crates would simplify TTA code organization and dependencies (you did some of this with the mod input in recent changes).

Input dispatch:

  • at the moment the if I want to change key mappings I have to basically copy the whole of TTA input function. This is what I have done in my (as yet to be accepted) PR for gitui, thats a lot of code. It would be simpler if it was table driven and I could make calls to change default entries in the table
  • I dont not think having the key table loaded from a file would be a good idea. Thats not transparent enough for the caller. But the calling app can load from a file and call the table edit functions
  • maybe have several different starter key tables. You recognize this need with input_without_shortcuts

I can certainly make a table driven key dispatcher layered on top of the current TTA architecture and make a PR of that, as a sort of demo (like my tick change)

drop tui support

since tui is now basically dead, textarea should drop support for it and only support ratatui. Why? because new features are being added to ratatui and textarea cannot take advantage of them without getting very messy.

See #29

Feature Request: syntax highlighting

Hi,

Do you have any plans for supporting syntax highlighting in the widget?

There are some crates that could help, but I don't know if there is interest in having syntax highlighting as a feature.

`insert_str` should accept newlines

       debug_assert!(
            !line.contains('\n'),
            "string given to insert_str must not contain newline: {:?}",
            line,
        );

it should be looking at the input string not the line that is being inserted into

Scrolling by mouse?

Hello! Thanks for the great Open Source!
Is scrolling by mouse implemented?
Can I do it without editing your code?

Samples on windows are inefficient

On windows every key stroke is doubled due to the key press and release. TTA maps the release to Null but that null is still sent through the main input and draw loop. No harm is done but the key lookup and , more importantly, the render is repeated, uselessly.

I know these are samples but people will copy them.

There is no simple one liner, this isnt enough

        match crossterm::event::read()?.into() {
            Input { key: Key::Esc, .. } => break,
            Input { key: Key::Null, .. } => continue,

as all the samples do render then input, so the render gets done again

Hard tabs are not rendered

Repro

Save the following code as foo.go.

package main

func main() {
	println("hello")
	if true {
		println("true")
	}
}

Then run cargo run --example editor foo.go

Expected behavior

Hard tabs are rendered as tabs.

package main

func main() {
    println("hello")
    if true {
        println("true")
    }
}

Actual behavior

Hard tabs are not rendered.

package main

func main() {
println("hello")
if true {
println("true")
}
}

Environment

  • iTerm2
  • tui-textarea: v0.1.0 with default features only
  • tui: v0.18.0
  • Backend: crossterm
  • Rust: 1.60.0

Derive debug for Textarea

Hello.
I think it will be awesome if you can derive the debug trait. It makes it impossible to embed the textarea in any structure that has the debug trait, which is not nice for debugging purposes.
Regards

Update code in README to compile to enable unit testing

The code in the README does not compile, which is fine generally, but is imported into lib.rs, which causes cargo test not to run (and which precludes writing unit tests easily).

Some ideas on fixing this:

  1. Update all the readme code to be complete (including the various imports necessary to run the code).
  2. Tag each code sample in the readme as ignore to avoid including it in the tests run by cargo test
  3. Move most of the readme to lib.rs and use cargo-rdme to keep things in sync instead of including the readme in lib.rs (and then fix the code samples there instead)

Personally I like option 3, but option 2 might be a quick short term solution.

Remove Emacs-like shortcuts from `TextArea::input`

The problem

Currently TextArea::input supports Emacs-like keyboard shortcuts. This was a good idea when tui-textarea had less features. However, now tui-textarea supports more features and adds more default key shortcuts and running out key combinations. And now it causes some conflicts.

  • C-a is used for moving to head of line in Emacs, but usually it is used for selecting all text in normal textarea
  • C-v is used for moving to the next page in Emacs, but usually it is used for pasting yanked text

Current default key shortcuts can be found here: https://github.com/rhysd/tui-textarea#minimal-usage

Emacs solves the problem by key sequence (like C-x b) but tui-textarea doesn't have the functionality (and should not have for avoiding complexity).

tui-textarea aims to be something like <textarea> element in HTML as described in the first sentence of README.md. So the default shortcuts should be intuitive and kept simple.

The solution

Remove Emacs-like shortcuts from TextArea::input and add TextArea::input_with_emacs_shortcuts instead.

Supported key shortcuts are as follows.

TextArea::input

Mappings Description
Ctrl+H, Backspace Delete one character before cursor
Delete Delete one character next to cursor
Enter Insert newline
Alt+Backspace Delete one word before cursor
Alt+Delete Delete one word next to cursor
Ctrl+Z Undo
Alt+Z Redo
Ctrl+C Copy selected text
Ctrl+X Cut selected text
Ctrl+V Paste yanked text
Ctrl+A Select all text
Ctrl+F, Move cursor forward by one character
Ctrl+B, Move cursor backward by one character
Ctrl+P, Move cursor up by one line
Ctrl+N, Move cursor down by one line
Ctrl+→ Move cursor forward by word
Ctrl+← Move cursor backward by word
Ctrl+↑ Move cursor up by paragraph
Ctrl+↓ Move cursor down by paragraph
End, Ctrl+Alt+→ Move cursor to the end of line
Home, Ctrl+Alt+← Move cursor to the head of line
Ctrl+Alt+↑ Move cursor to top of lines
Ctrl+Alt+↓ Move cursor to bottom of lines
PageDown Scroll down by page
PageUp Scroll up by page

Note: Alt+Z is unusual, but Ctrl+Shift+Z cannot be detected due to limitation of the terminal escape sequence.

Note: Ctrl+N, Ctrl+P, Ctrl+F, Ctrl+B, Ctrl+H remain because they are supported on macOS by default key shortcuts.

TextArea::input_with_emacs_shortcuts

Mappings Description
Ctrl+H, Backspace Delete one character before cursor
Ctrl+D, Delete Delete one character next to cursor
Ctrl+M, Enter Insert newline
Ctrl+K Delete from cursor until the end of line
Ctrl+J Delete from cursor until the head of line
Ctrl+W, Alt+H Delete one word before cursor
Alt+D Delete one word next to cursor
Ctrl+U Undo
Ctrl+R Redo
Ctrl+Y Paste yanked text
Ctrl+Space Start selection
Ctrl+W Cut selected text
Alt+W Copy selected text
Ctrl+G Cancel selection
Ctrl+F, Move cursor forward by one character
Ctrl+B, Move cursor backward by one character
Ctrl+P, Move cursor up by one line
Ctrl+N, Move cursor down by one line
Alt+F Move cursor forward by word
Atl+B Move cursor backward by word
Alt+], Alt+P Move cursor up by paragraph
Alt+[, Alt+N Move cursor down by paragraph
Ctrl+E, End, Ctrl+Alt+F Move cursor to the end of line
Ctrl+A, Home, Ctrl+Alt+B Move cursor to the head of line
Alt+<, Ctrl+Alt+P, Move cursor to top of lines
Alt+>, Ctrl+Alt+N, Move cursor to bottom of lines
Ctrl+V, PageDown Scroll down by page
Alt+V, PageUp Scroll up by page

Selecting text

👋🏽 I have very little experience with Rust and working with Terminal UI.

Is there a recommended way of selecting tui-textarea text with either mouse or keyboard?
For example moving the cursor with S+Right to move the cursor and select the text below the cursor range at the same time?

selection is really hard to use for the non `input` caller

I have finally got the go ahead to put tui-textarea into gitui. I see that tta is now up to v0.4 and that now includes the keyboard based selection. I now see how you modified my original code.

The problem is that its very hard to use in the case where I am not using the keyboard dispatched via input. The new code there uses move_cursor_with_shift, this what I would have done except that it would have changed the existing API which I wanted to avoid. Which is why I came up with other stuff. Having a shift aware API makes it really easy (as your code shows), but that function is private.

Instead the non input using caller as to use start_selection and stop_selection. This means that I now have to reimplement the logic I already had in my original version that turns the selection on and off as the shift key is toggled, thats really tricky , even trickier outside TTA not having access to its insides (like is a selection already in progress?)

A simple solution would be to make the move_cursor_with_shift public, which would in fact make start_selection redundant (I think)

I think I can do the tricky thing for now but its not at all obvious what a caller needs to do (in the doc) and no samples show it either.

hard tab now adds extra padding character on display

on current head

run minimal sample with hard tab set on (I set width to 10),

enter a\tb\na\b

display is

image

now cursor left into the gap

image

the tab space grew by one character

I have tried an initial debug but its not obvious

Suport for crossterm 0.26

Hello,

crossterm latest verion is 0.26 since last week, could you pleas add support for it ?

Thanks

the trait `From<KeyEvent>` is not implemented for `tui_textarea::Input`

Hi, after update the crossterm crate to version 0.26.0, I started getting this error message at text_area.input():

the trait `From<KeyEvent>` is not implemented for `tui_textarea::Input`

My code:

  pub fn handle(&mut self, key_event: KeyEvent) {
        match key_event {
            KeyEvent {
                code: KeyCode::Esc | KeyCode::Enter | KeyCode::Down | KeyCode::Up,
                ..
            } => self.on_focus = false,
            input => {
                self.text_area.input(input);
                self.buffer = self.text_area.lines()[0].clone()
            }
        }
    }

the KeyEvent is from crossterm::event::KeyEvent
It
Cargo.toml:

[dependencies]
# another non-TUI related deps
tui = "0.19.0" # uses crossterm as default
crossterm = "0.26.0"
tui-textarea = { version = "0.2.0", features = ["crossterm"] }
# another non-TUI related deps

It's like the code can't read the impl From<KeyEvent> for Input int the input.rs class

Feature request: Changing cursor shape

First let me tell, this crate is amazing!
It completely suits me as it is for now, but in the future it might be helpful to be able to change cursor shape (i want to make a vim-like text area in my project at some point, and changing cursor shape between normal mode (block) and insert mode (line) would make it way more user friendly)

Thanks again for making this crate!

Add a non-regex search

I try to avoid using regex in my application due to the increase in binary size. It would be helpful to have a non-regex search that just highlights direct matches of the input text.

Alternatively, or alongside this, exposing a means of highlighting text (like how text selection currently is) would be helpful.

current code doesnt work with ratatui

git clone and then run ratatui_minimal


PS C:\Users\paulm\work\tui-textarea> cargo run --example ratatui_minimal --features="ratatui-crossterm"
    Updating crates.io index
   Compiling windows_x86_64_msvc v0.48.5
   Compiling autocfg v1.1.0
   Compiling winapi v0.3.9
   Compiling parking_lot_core v0.9.8
   Compiling proc-macro2 v1.0.69
   Compiling scopeguard v1.2.0
   Compiling cfg-if v1.0.0
   Compiling smallvec v1.11.1
   Compiling unicode-ident v1.0.12
   Compiling rustversion v1.0.14
   Compiling heck v0.4.1
   Compiling paste v1.0.14
   Compiling lock_api v0.4.10
   Compiling unicode-segmentation v1.10.1
   Compiling windows-targets v0.48.5
   Compiling either v1.9.0
   Compiling cassowary v0.3.0
   Compiling bitflags v2.4.0
   Compiling parking_lot v0.12.1
   Compiling quote v1.0.33
   Compiling bitflags v1.3.2
   Compiling unicode-width v0.1.11
   Compiling itertools v0.11.0
   Compiling syn v2.0.38
   Compiling indoc v2.0.4
   Compiling crossterm_winapi v0.9.1
   Compiling crossterm v0.25.0
   Compiling crossterm v0.27.0
   Compiling strum_macros v0.25.2
   Compiling tui v0.19.0
   Compiling strum v0.25.0
   Compiling ratatui v0.23.0
   Compiling tui-textarea v0.2.2 (C:\Users\paulm\work\tui-textarea)
error[E0252]: the name `crossterm` is defined multiple times
  --> src\lib.rs:47:5
   |
45 | use crossterm;
   |     --------- previous import of the module `crossterm` here
46 | #[cfg(feature = "ratatui-crossterm")]
47 | use crossterm_027 as crossterm;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ `crossterm` reimported here
   |
   = note: `crossterm` must be defined only once in the type namespace of this module
help: you can use `as` to change the binding name of the import
   |
47 | use crossterm_027 as other_crossterm;
   |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

error: tui-rs support and ratatui support are exclussive. only one of them can be enabled at the same time. see https://github.com/rhysd/tui-textarea#installation
  --> src\lib.rs:15:1
   |
15 | compile_error!("tui-rs support and ratatui support are exclussive. only one of them can be enabled at the same time. see https://github.com/rhysd/tui-textarea#installation...
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: unused import: `crossterm`
  --> src\lib.rs:45:5
   |
45 | use crossterm;
   |     ^^^^^^^^^
   |
   = note: `#[warn(unused_imports)]` on by default

For more information about this error, try `rustc --explain E0252`.
warning: `tui-textarea` (lib) generated 1 warning
error: could not compile `tui-textarea` (lib) due to 2 previous errors; 1 warning emitted
warning: build failed, waiting for other jobs to finish...

I suspect its to do with the combinatorial logic of the cfgs, I think you mean not(all.... not not(any...

editor sample crashes on windows every other invokation

it seems to be something to do with enabling and detecting raw mode

If i reset my terminal and run it, its fine, then if I close and run again the screen is not cleared, the edit windows open and can be used but is odd

image

on ^q it crashes

image

run again all is fine

I bet this is a windows issue, I will drill into it (I am ex MSFT guy, windows is my env) once I finish the select work

API improvement to allow fluent calls

A few improvements having used it myself in a non-trivial project

make all the setters return &Self. This allows fluent use

ie instead of

    textarea.set_block(
        Block::default()
            .borders(Borders::ALL)
            .title("Crossterm Minimal Example"),
    );
    textarea.set_hard_tab_indent(true);

have

    textarea.set_block(
        Block::default()
            .borders(Borders::ALL)
            .title("Crossterm Minimal Example"),
    ).set_hard_tab_indent(true)
     .set_mask_char('*');

this is common practice in rust crates with many setters

Feature request: Word wrap

This is super cool!

I want to make a little note editor using tui-textarea. But my notes will often use long lines - and ideally rendered with word wrap.

How can I do that? Would it be an easy change to make to tui-textarea? (I'm happy to get my hands dirty)

Crate fails to compile with Ratatui, even after following docs

This is my Cargo.toml file

[dependencies]
ratatui = { version = "0.22.0", features = ["all-widgets"] }
tui-textarea = { version = "*", features = [
    "ratatui-crossterm",
], default-features = false }

Added Ratatui using cargo add.
Compiling using cargo build leads to the following error -

error: failed to select a version for `tui-textarea`.
    ... required by package `spectrumize v0.1.0 (C:\code-projects\rs-spectrumize\spectrumize)`
versions that meet the requirements `*` (locked to 0.2.0) are: 0.2.0

the package `spectrumize` depends on `tui-textarea`, with features: `ratatui-crossterm` but `tui-textarea` does not have these features.


failed to select a version for `tui-textarea` which could resolve this conflict

This is the solution that the docs mention to get things working with Ratatui. This build seems to fail, why?

New minor version for ratatui dependencies

Hi, at first I want to thank you on this great library. I used it in my app tui-journal and it was a charm!

I came across an issue while trying updating my app to use ratatui instead of tui-rs.

The problem is that cargo didn't recognize the new features for ratatui since they are in save version as before, and it seems that cargo cache this info somewhere in my machine. I've tried to clear my caches but this didn't solve the problem.

I believe the version should be updated to 0.2.1 since there are new features has been added.

Here is the error message I got when I try to add the features with cargo cli

$ cargo add tui-textarea --no-default-features -F ratatui-crossterm

    Updating crates.io index
      Adding tui-textarea v0.2.0 to dependencies.
error: unrecognized feature for crate tui-textarea: ratatui-crossterm
disabled features:
    arbitrary, crossterm, search, termion

And here is the error message when I try to add inside cargo.toml

[dependencies]
crossterm = "0.26.1"
ratatui = "*"
tui-textarea = { version = "*", features = ["ratatui-crossterm"], default-features=false }
error: failed to select a version for `tui-textarea`.
versions that meet the requirements `*` (locked to 0.2.0) are: 0.2.0

the package `test_ratatui` depends on `tui-textarea`, with features: `ratatui-crossterm` but `tui-textarea` does not have these features.

failed to select a version for `tui-textarea` which could resolve this conflict

trying to use tui-textarea in another project - version conflict

I am trying to make gitui use tui-textarea (its why I came here in the first place)

My problem is that I need to use the same version of ratatui in gitui and tta. This because arguments from ratatui (Style) are passed between the gitui code and the code I have written using tta.

gitui says

ratatui = { version = "0.23", default-features = false, features = ['crossterm', 'serde'] }

(It used to say 0.21 but I changed it in my PR to I am putting together). I cannot change gitui to 0.24 because there are breaking changes between 0.23 and 0.24. I need both to use 0.23

tta says

ratatui = { version = ">=0.23.0 , <1", default-features = false, optional = true }

If I build that I end up with gitui using 0.23 and tta using 0.24, does not compile

You would think that cargo would work this out ,but it doesnt.

The only way I have found to make that work is by changing ttas cargo.toml to specifically say

ratatui = { version = "0.23.0", default-features = false, optional = true }

this then works, but seems like a backwards step (that I would have to persuade you to make, which seems wrong anyway)

Any advice ?

Cargo test fails

It tries to compile src/lib.rs, which I includes the readme that has invalid code marked as rust.

I looked at ci.yaml and that seems to not exclude it, but it gets excluded anyway. I don't understand why. But really plain n 'cargo test' should work.

Support text "hints"

I'd like to offer suggestions for auto-complete-like functionality and display a suggestion/hint in the text area using the "placeholder text" styling.

I want to reuse the styling from the placeholder, but allow for it to show up even if the text area isn't empty.

add os clipboard support

Today the copy and paste functions only work within TTA to and from its private paste buffer.

It would be very useful to copy to and from the system clipboard.

Maybe should be a feature

I intend to do a PR for this

Update to ratatui 0.26

Hi, tui-textarea currently targets ratatui 0.24.0, but the latest version is 0.26.1. Could you update the dependency and publish a new release?

'placeholder' feature request

Can I ask you to look at this PR. I want to plug this library into gitui, but I am blocked waiting for this

It adds support for hint text when the text box is empty - like placeholder in the html textbox widget

Allow input widgets to appear wherever the are in a terminal session

In your demos, the input widgets clear the screen and appear at the top. But for many applications, it's better to be able to show the terminal session up to the point where the input occurs, and have the input area on the next line.

I'm not sure whether there's some way to accomplish that already with tui-textarea, or whether this is a feature request. If there's a way to do it I suggest adding it to your examples!

remove the <'a> requirement on TextArea

Dropping TTA into an existing project can be very disruptive since it requires lifetime annotation. This can ripple up through an app. This makes it a barrier to adoption by other projects (speaking from experience here)

The solution would be to have a private Block equivalent that owns the title string (s) rather than using references.

make lines field public

i want some lover-level control over the textarea, and currently there seems to no method to set or modify the text directly.

Feature request: prompt for single line TextArea

I'd like to create a fuzzy finder UI similar to fzf, with a > prompt showing that the user can type the match phrase there, instead of surrounding the input area with a block.

What is missing in TextArea is some kind of way or mode to add such a prompt on the left of a line.

I'm aware this is stretching the already wide number of uses for TextArea, and that it clashes a bit with the already existing, more editor-oriented API like line numbers, but tui-textarea seems to be the best-tailored solution otherwise.

ratatui-minimal has strange behavior

  • sometimes when started there is an extra blank line and the cursor is positioned on the second line
  • if I type a single character it is repeated in the widget

image

Here I simply ran it and typed hello world

It only fails on windows, works on linux (WSL unbuntu 20)

BTW - minimal (with tui) works fine

Panic when max history is set to 0

If set the maximum amount of history to 0, then according to the documentation, undo / redo should be disabled. Actually there is a panic at change of the text in TextArea. This is because the value in history.rs:115 is out of range. I consider it necessary to add an additional condition for checking compliance with the boundaries

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.