1
0
зеркало из https://github.com/glebtv/tui-textarea.git synced 2026-09-03 14:26:17 +03:00
Этот коммит содержится в:
rhysd
2022-06-13 20:25:44 +09:00
родитель f85822b191
Коммит 77e8bd5f76

Просмотреть файл

@@ -14,6 +14,26 @@ fn spaces(size: usize) -> &'static str {
&SPACES[..size]
}
/// A type to manage state of textarea.
///
/// [`TextArea::default`] creates an empty textarea. [`TextArea::new`] creates a textarea with given text lines.
/// [`TextArea::from`] creates a textarea from an iterator of lines. [`TextArea::input`] handles key input.
/// [`TextArea::widget`] builds a widget to render. And [`TextArea::lines`] returns line texts.
/// ```
/// use tui_textarea::{TextArea, Input, Key};
///
/// let mut textarea = TextArea::default();
///
/// // Input 'a'
/// let input = Input { key: Key::Char('a'), ctrl: false, alt: false };
/// textarea.input(input);
///
/// // Get widget to render.
/// let widget = textarea.widget();
///
/// // Get lines as String.
/// println!("Lines: {:?}", textarea.lines());
/// ```
pub struct TextArea<'a> {
lines: Vec<String>,
block: Option<Block<'a>>,
@@ -27,23 +47,52 @@ pub struct TextArea<'a> {
yank: String,
}
/// Convert any iterator whose elements can be converted into [`String`] into [`TextArea`]. Each [`String`] element is
/// handled as line. Ensure that the strings don't contain any newlines. This method is useful to create [`TextArea`]
/// from [`std::str::Lines`].
/// ```
/// use tui_textarea::TextArea;
///
/// let text = "hello\nworld";
/// let textarea = TextArea::from(text.lines());
/// assert_eq!(textarea.lines(), ["hello", "world"]);
///
/// let textarea = TextArea::from(["hello", "world"]);
/// assert_eq!(textarea.lines(), ["hello", "world"]);
/// ```
impl<'a, I> From<I> for TextArea<'a>
where
I: Iterator,
I: IntoIterator,
I::Item: Into<String>,
{
fn from(i: I) -> Self {
Self::new(i.map(|s| s.into()).collect::<Vec<String>>())
Self::new(i.into_iter().map(|s| s.into()).collect::<Vec<String>>())
}
}
impl<'a> Default for TextArea<'a> {
/// Create [`TextArea`] instance with empty text content.
/// ```
/// use tui_textarea::TextArea;
///
/// let textarea = TextArea::default();
/// assert_eq!(textarea.lines(), [""]);
/// ```
fn default() -> Self {
Self::new(vec![String::new()])
}
}
impl<'a> TextArea<'a> {
/// Create [`TextArea`] instance with given lines. If you have value other than `Vec<String>`, [`TextArea::from`]
/// may be more useful.
/// ```
/// use tui_textarea::TextArea;
///
/// let lines = vec!["hello".to_string(), "...".to_string(), "goodbye".to_string()];
/// let textarea = TextArea::new(lines);
/// assert_eq!(textarea.lines(), ["hello", "...", "goodbye"]);
/// ```
pub fn new(mut lines: Vec<String>) -> Self {
if lines.is_empty() {
lines.push(String::new());
@@ -63,6 +112,33 @@ impl<'a> TextArea<'a> {
}
}
/// Handle a key input with default key mappings. For default key mappings, see the table in
/// [the module document](./index.html).
/// `crossterm` and `termion` features enable conversion from their own key event types into [`Input`] so this
/// method can take the event values directly.
/// ```ignore
/// use tui_textarea::{TextArea, Key, Input};
///
/// let mut textarea = TextArea::default();
///
/// // Handle crossterm key events
/// let event: crossterm::event::Event = ...;
/// textarea.input(event);
/// if let crossterm::event::Event::Key(key) = event {
/// textarea.input(key);
/// }
///
/// // Handle termion key events
/// let event: termion::event::Event = ...;
/// textarea.input(event);
/// if let termion::event::Event::Key(key) = event {
/// textarea.input(key);
/// }
///
/// // Handle backend-agnostic key input
/// let input = Input { key: Key::Char('a'), ctrl: false, alt: false };
/// textarea.input(input);
/// ```
pub fn input(&mut self, input: impl Into<Input>) {
let input = input.into();
match input {
@@ -305,6 +381,15 @@ impl<'a> TextArea<'a> {
);
}
/// Handle a key input without default key mappings. This method handles only
/// - Single character input without modifier keys
/// - Tab
/// - Enter
/// - Backspace
/// - Delete
///
/// This method is useful when you want to define your own key mappings and don't want default key mappings.
/// See 'Define your own key mappings' section in [the module document](./index.html).
pub fn input_without_shortcuts(&mut self, input: impl Into<Input>) {
let input = input.into();
match input {
@@ -337,6 +422,15 @@ impl<'a> TextArea<'a> {
self.history.push(edit);
}
/// Insert a single character at current cursor position.
/// ```
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::default();
///
/// textarea.insert_char('a');
/// assert_eq!(textarea.lines(), ["a"]);
/// ```
pub fn insert_char(&mut self, c: char) {
let (row, col) = self.cursor;
let line = &mut self.lines[row];
@@ -350,6 +444,15 @@ impl<'a> TextArea<'a> {
self.push_history(EditKind::InsertChar(c, i), (row, col));
}
/// Insert a string at current cursor position. Currently the string must not contain any newlines.
/// ```
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::default();
///
/// textarea.insert_str("hello");
/// assert_eq!(textarea.lines(), ["hello"]);
/// ```
pub fn insert_str<S: Into<String>>(&mut self, s: S) {
let s = s.into();
if s.is_empty() {
@@ -375,6 +478,16 @@ impl<'a> TextArea<'a> {
self.push_history(EditKind::Insert(s, i), (row, col));
}
/// Delete a string in current cursor line. The `chars` parameter means number of characters, not a byte length of
/// the string.
/// ```
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::from(["🐱🐶🐰🐮"]);
///
/// textarea.delete_str(1, 2);
/// assert_eq!(textarea.lines(), ["🐱🐮"]);
/// ```
pub fn delete_str(&mut self, col: usize, chars: usize) {
if chars == 0 {
return;
@@ -397,6 +510,15 @@ impl<'a> TextArea<'a> {
}
}
/// Insert a tab at current cursor position.
/// ```
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::from(["hi"]);
///
/// textarea.insert_tab();
/// assert_eq!(textarea.lines(), [" hi"]);
/// ```
pub fn insert_tab(&mut self) {
if self.tab_len > 0 {
let len = self.tab_len as usize - self.cursor.1 % self.tab_len as usize;
@@ -404,6 +526,16 @@ impl<'a> TextArea<'a> {
}
}
/// Insert a newline at current cursor position.
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["hi"]);
///
/// textarea.move_cursor(CursorMove::Forward);
/// textarea.insert_newline();
/// assert_eq!(textarea.lines(), ["h", "i"]);
/// ```
pub fn insert_newline(&mut self) {
let (row, col) = self.cursor;
let line = &mut self.lines[row];
@@ -420,6 +552,16 @@ impl<'a> TextArea<'a> {
self.push_history(EditKind::InsertNewline(idx), (row, col));
}
/// Delete a newline from **head** of current cursor line.
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["hello", "world"]);
///
/// textarea.move_cursor(CursorMove::Down);
/// textarea.delete_newline();
/// assert_eq!(textarea.lines(), ["helloworld"]);
/// ```
pub fn delete_newline(&mut self) {
let (row, col) = self.cursor;
if row == 0 {
@@ -435,6 +577,17 @@ impl<'a> TextArea<'a> {
self.push_history(EditKind::DeleteNewline(prev_line_end), (row, col));
}
/// Delete one character before cursor. When the cursor is at head of line, the newline before the cursor will be
/// removed.
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["abc"]);
///
/// textarea.move_cursor(CursorMove::Forward);
/// textarea.delete_char();
/// assert_eq!(textarea.lines(), ["bc"]);
/// ```
pub fn delete_char(&mut self) {
let (row, col) = self.cursor;
if col == 0 {
@@ -450,6 +603,17 @@ impl<'a> TextArea<'a> {
}
}
/// Delete one character next to cursor. When the cursor is at end of line, the newline next to the cursor will be
/// removed.
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["abc"]);
///
/// textarea.move_cursor(CursorMove::Forward);
/// textarea.delete_next_char();
/// assert_eq!(textarea.lines(), ["ac"]);
/// ```
pub fn delete_next_char(&mut self) {
let before = self.cursor;
self.move_cursor(CursorMove::Forward);
@@ -459,14 +623,57 @@ impl<'a> TextArea<'a> {
self.delete_char();
}
/// Delete string from cursor to end of the line. When the cursor is at end of line, the newline next to the cursor
/// will be removed.
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["abcde"]);
///
/// // Move to 'c'
/// textarea.move_cursor(CursorMove::Forward);
/// textarea.move_cursor(CursorMove::Forward);
///
/// textarea.delete_line_by_end();
/// assert_eq!(textarea.lines(), ["ab"]);
/// ```
pub fn delete_line_by_end(&mut self) {
self.delete_str(self.cursor.1, usize::MAX);
}
/// Delete string from cursor to head of the line. When the cursor is at head of line, the newline before the cursor
/// will be removed.
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["abcde"]);
///
/// // Move to 'c'
/// textarea.move_cursor(CursorMove::Forward);
/// textarea.move_cursor(CursorMove::Forward);
///
/// textarea.delete_line_by_head();
/// assert_eq!(textarea.lines(), ["cde"]);
/// ```
pub fn delete_line_by_head(&mut self) {
self.delete_str(0, self.cursor.1);
}
/// Delete a word before cursor. Word boundary appears at spaces, punctuations, and others. For example `fn foo(a)`
/// consists of words `fn`, `foo`, `(`, `a`, `)`. When the cursor is at head of line, the newline before the cursor
/// will be removed.
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["aaa bbb ccc"]);
///
/// textarea.move_cursor(CursorMove::End);
///
/// textarea.delete_word();
/// assert_eq!(textarea.lines(), ["aaa bbb "]);
/// textarea.delete_word();
/// assert_eq!(textarea.lines(), ["aaa "]);
/// ```
pub fn delete_word(&mut self) {
let (r, c) = self.cursor;
if let Some(col) = find_word_start_backward(&self.lines[r], c) {
@@ -478,6 +685,19 @@ impl<'a> TextArea<'a> {
}
}
/// Delete a word next to cursor. Word boundary appears at spaces, punctuations, and others. For example `fn foo(a)`
/// consists of words `fn`, `foo`, `(`, `a`, `)`. When the cursor is at end of line, the newline next to the cursor
/// will be removed.
/// ```
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::from(["aaa bbb ccc"]);
///
/// textarea.delete_next_word();
/// assert_eq!(textarea.lines(), [" bbb ccc"]);
/// textarea.delete_next_word();
/// assert_eq!(textarea.lines(), [" ccc"]);
/// ```
pub fn delete_next_word(&mut self) {
let (r, c) = self.cursor;
let line = &self.lines[r];
@@ -494,29 +714,103 @@ impl<'a> TextArea<'a> {
}
}
/// Paste a string previously deleted by [`TextArea::delete_line_by_head`], [`TextArea::delete_line_by_end`],
/// [`TextArea::delete_word`], [`TextArea::delete_next_word`].
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["aaa bbb ccc"]);
///
/// textarea.delete_next_word();
/// textarea.move_cursor(CursorMove::End);
/// textarea.paste();
/// assert_eq!(textarea.lines(), [" bbb cccaaa"]);
/// ```
pub fn paste(&mut self) {
let yank = std::mem::take(&mut self.yank);
self.insert_str(yank);
}
/// Move the cursor to the position specified by the [`CursorMove`] parameter. For each kind of cursor moves, see
/// the document of [`CursorMove`].
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["abc", "def"]);
///
/// textarea.move_cursor(CursorMove::Forward);
/// assert_eq!(textarea.cursor(), (0, 1));
/// textarea.move_cursor(CursorMove::Down);
/// assert_eq!(textarea.cursor(), (1, 1));
/// ```
pub fn move_cursor(&mut self, m: CursorMove) {
if let Some(cursor) = m.next_cursor(self.cursor, &self.lines) {
self.cursor = cursor;
}
}
/// Undo the last modification. When no modification is added to the text, this method does nothing.
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["abc def"]);
///
/// textarea.delete_next_word();
/// assert_eq!(textarea.lines(), [" def"]);
/// textarea.undo();
/// assert_eq!(textarea.lines(), ["abc def"]);
/// ```
pub fn undo(&mut self) {
if let Some(cursor) = self.history.undo(&mut self.lines) {
self.cursor = cursor;
}
}
/// Redo the last undo change. When no undo change remain, this method does nothing.
/// ```
/// use tui_textarea::{TextArea, CursorMove};
///
/// let mut textarea = TextArea::from(["abc def"]);
///
/// textarea.delete_next_word();
/// assert_eq!(textarea.lines(), [" def"]);
/// textarea.undo();
/// assert_eq!(textarea.lines(), ["abc def"]);
/// textarea.redo();
/// assert_eq!(textarea.lines(), [" def"]);
/// ```
pub fn redo(&mut self) {
if let Some(cursor) = self.history.redo(&mut self.lines) {
self.cursor = cursor;
}
}
/// Build a tui-rs widget to render the current state of the textarea. The widget instance returned from this
/// method can be rendered with [`tui::terminal::Frame::render_widget`].
/// ```no_run
/// use tui::backend::CrosstermBackend;
/// use tui::layout::{Constraint, Direction, Layout};
/// use tui::Terminal;
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::default();
///
/// let layout = Layout::default()
/// .direction(Direction::Vertical)
/// .constraints([Constraint::Min(1)].as_ref());
/// let backend = CrosstermBackend::new(std::io::stdout());
/// let mut term = Terminal::new(backend).unwrap();
///
/// loop {
/// term.draw(|f| {
/// let chunks = layout.split(f.size());
/// let widget = textarea.widget();
/// f.render_widget(widget, chunks[0]);
/// }).unwrap();
///
/// // ...
/// }
/// ```
pub fn widget(&'a self) -> impl Widget + 'a {
fn num_digits(i: usize) -> usize {
f64::log10(i as f64) as usize + 1
@@ -564,71 +858,198 @@ impl<'a> TextArea<'a> {
}
}
/// Set the style of textarea. By default, textarea is not styled.
/// ```
/// use tui::style::{Style, Color};
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::default();
/// let style = Style::default().fg(Color::Red);
/// textarea.set_style(style);
/// assert_eq!(textarea.style(), style);
/// ```
pub fn set_style(&mut self, style: Style) {
self.style = style;
}
/// Get the current style of textarea.
pub fn style(&self) -> Style {
self.style
}
/// Set the block of textarea. By default, no block is set.
/// ```
/// use tui_textarea::TextArea;
/// use tui::widgets::{Block, Borders};
///
/// let mut textarea = TextArea::default();
/// let block = Block::default().borders(Borders::ALL).title("Block Title");
/// textarea.set_block(block);
/// assert!(textarea.block().is_some());
/// ```
pub fn set_block(&mut self, block: Block<'a>) {
self.block = Some(block);
}
/// Remove the block of textarea which was set by [`TextArea::set_block`].
/// ```
/// use tui_textarea::TextArea;
/// use tui::widgets::{Block, Borders};
///
/// let mut textarea = TextArea::default();
/// let block = Block::default().borders(Borders::ALL).title("Block Title");
/// textarea.set_block(block);
/// textarea.remove_block();
/// assert!(textarea.block().is_none());
/// ```
pub fn remove_block(&mut self) {
self.block = None;
}
/// Get the block of textarea if exists.
pub fn block<'s>(&'s self) -> Option<&'s Block<'a>> {
self.block.as_ref()
}
/// Set the length of tab character. Due to limitation of tui-rs, hard tab is not supported. Setting 0 disables tab
/// inputs.
/// ```
/// use tui_textarea::{TextArea, Input, Key};
///
/// let mut textarea = TextArea::default();
/// let tab_input = Input { key: Key::Tab, ctrl: false, alt: false };
///
/// textarea.set_tab_length(8);
/// textarea.input(tab_input.clone());
/// assert_eq!(textarea.lines(), [" "]);
///
/// textarea.set_tab_length(2);
/// textarea.input(tab_input);
/// assert_eq!(textarea.lines(), [" "]);
/// ```
pub fn set_tab_length(&mut self, len: u8) {
self.tab_len = len;
}
/// Get how many spaces are used for representing tab character. The default value is 4.
pub fn tab_length(&self) -> u8 {
self.tab_len
}
/// Set how many modifications are remembered for undo/redo. Setting 0 disables undo/redo.
pub fn set_max_histories(&mut self, max: usize) {
self.history = History::new(max);
}
/// Get how many modifications are remembered for undo/redo. The default value is 50.
pub fn max_histories(&self) -> usize {
self.history.max_items()
}
/// Set the style of line at cursor. By default, the cursor line is styled with underline. To stop styling the
/// cursor line, set the default style.
/// ```
/// use tui::style::{Style, Color};
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::default();
///
/// let style = Style::default().fg(Color::Red);
/// textarea.set_cursor_line_style(style);
/// assert_eq!(textarea.cursor_line_style(), style);
///
/// // Disable cursor line style
/// textarea.set_cursor_line_style(Style::default());
/// ```
pub fn set_cursor_line_style(&mut self, style: Style) {
self.cursor_line_style = style;
}
/// Get the style of cursor line. By default it is styled with underline.
pub fn cursor_line_style(&self) -> Style {
self.cursor_line_style
}
/// Set the style of line number. By setting the style with this method, line numbers are drawn in textarea, meant
/// that line numbers are disabled by default. If you want to show line numbers but don't want to style them, set
/// the default style.
/// ```
/// use tui::style::{Style, Color};
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::default();
///
/// // Show line numbers in dark gray background
/// let style = Style::default().bg(Color::DarkGray);
/// textarea.set_line_number_style(style);
/// assert_eq!(textarea.line_number_style(), Some(style));
/// ```
pub fn set_line_number_style(&mut self, style: Style) {
self.line_number_style = Some(style);
}
/// Remove the style of line number which was set by [`TextArea::set_line_number_style`]. After calling this
/// method, Line numbers will no longer be shown.
pub fn remove_line_number(&mut self) {
self.line_number_style = None;
}
/// Get the style of line number if set.
pub fn line_number_style(&self) -> Option<Style> {
self.line_number_style
}
/// Get slice of line texts. This method borrows the content, but not moves. Note that the returned slice will
/// never be empty because an empty text means a slice containing one empty line. This is correct since any text
/// file must end with a newline.
/// ```
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::default();
/// assert_eq!(textarea.lines(), [""]);
///
/// textarea.insert_char('a');
/// assert_eq!(textarea.lines(), ["a"]);
///
/// textarea.insert_newline();
/// assert_eq!(textarea.lines(), ["a", ""]);
///
/// textarea.insert_char('b');
/// assert_eq!(textarea.lines(), ["a", "b"]);
/// ```
pub fn lines(&'a self) -> &'a [String] {
&self.lines
}
/// Convert [`TextArea`] instance into line texts.
/// ```
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::default();
///
/// textarea.insert_char('a');
/// textarea.insert_newline();
/// textarea.insert_char('b');
///
/// assert_eq!(textarea.into_lines(), ["a", "b"]);
/// ```
pub fn into_lines(self) -> Vec<String> {
self.lines
}
/// 0-base character-wise (row, col) cursor position.
/// Get the current cursor position. 0-base character-wise (row, col) cursor position.
/// ```
/// use tui_textarea::TextArea;
///
/// let mut textarea = TextArea::default();
/// assert_eq!(textarea.cursor(), (0, 0));
///
/// textarea.insert_char('a');
/// textarea.insert_newline();
/// textarea.insert_char('b');
///
/// assert_eq!(textarea.cursor(), (1, 1));
/// ```
pub fn cursor(&self) -> (usize, usize) {
self.cursor
}