From c99108bf453bdc5c975e210b30f1341349f58452 Mon Sep 17 00:00:00 2001 From: rhysd Date: Fri, 2 Aug 2024 15:53:48 +0900 Subject: [PATCH] directly implement `Widget` for `&TextArea` (fix #78) --- README.md | 16 ++---- bench/src/lib.rs | 3 +- examples/editor.rs | 5 +- examples/minimal.rs | 2 +- examples/password.rs | 3 +- examples/popup_placeholder.rs | 2 +- examples/single_line.rs | 3 +- examples/split.rs | 3 +- examples/termion.rs | 3 +- examples/termwiz.rs | 3 +- examples/tuirs_editor.rs | 5 +- examples/tuirs_minimal.rs | 2 +- examples/tuirs_termion.rs | 3 +- examples/variable.rs | 2 +- examples/vim.rs | 2 +- fuzz/Cargo.lock | 2 +- src/cursor.rs | 8 +-- src/scroll.rs | 28 ++++----- src/textarea.rs | 105 +++++++++++++++++++++++----------- src/widget.rs | 50 +++++++--------- 20 files changed, 134 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 94dbca5..1253f23 100644 --- a/README.md +++ b/README.md @@ -211,10 +211,8 @@ 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); + // Render the textarea in terminal screen + f.render_widget(&textarea, rect); })?; if let Event::Key(key) = read()? { @@ -234,8 +232,7 @@ 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` reference implements ratatui's `Widget` trait. Render it on every 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 @@ -440,7 +437,7 @@ tui-textarea = { version = "*", features = ["search"] } ### Single-line input like `` in HTML -To use `TextArea` for single-line input widget like `` in HTML, ignore all key mappings which inserts newline. +To use `TextArea` for a single-line input widget like `` in HTML, ignore all key mappings which inserts newline. ```rust,ignore use crossterm::event::{Event, read}; @@ -628,9 +625,8 @@ 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); + for (editor, rect) in editors.iter().zip(rects.into_iter()) { + f.render_widget(editor, rect); } })?; diff --git a/bench/src/lib.rs b/bench/src/lib.rs index 480fd54..fa90f2e 100644 --- a/bench/src/lib.rs +++ b/bench/src/lib.rs @@ -116,7 +116,6 @@ pub trait TerminalExt { impl TerminalExt for Terminal { #[inline] fn draw_textarea(&mut self, textarea: &TextArea<'_>) { - self.draw(|f| f.render_widget(textarea.widget(), f.size())) - .unwrap(); + self.draw(|f| f.render_widget(textarea, f.size())).unwrap(); } } diff --git a/examples/editor.rs b/examples/editor.rs index 3680348..2eded14 100644 --- a/examples/editor.rs +++ b/examples/editor.rs @@ -188,13 +188,12 @@ impl<'a> Editor<'a> { let chunks = layout.split(f.size()); if search_height > 0 { - f.render_widget(self.search.textarea.widget(), chunks[0]); + f.render_widget(&self.search.textarea, chunks[0]); } let buffer = &self.buffers[self.current]; let textarea = &buffer.textarea; - let widget = textarea.widget(); - f.render_widget(widget, chunks[1]); + f.render_widget(&textarea, chunks[1]); // Render status line let modified = if buffer.modified { " [modified]" } else { "" }; diff --git a/examples/minimal.rs b/examples/minimal.rs index ac37201..9850b1b 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -26,7 +26,7 @@ fn main() -> io::Result<()> { loop { term.draw(|f| { - f.render_widget(textarea.widget(), f.size()); + f.render_widget(&textarea, f.size()); })?; match crossterm::event::read()?.into() { Input { key: Key::Esc, .. } => break, diff --git a/examples/password.rs b/examples/password.rs index dfe6a90..e7518a1 100644 --- a/examples/password.rs +++ b/examples/password.rs @@ -31,8 +31,7 @@ fn main() -> io::Result<()> { loop { term.draw(|f| { let chunks = layout.split(f.size()); - let widget = textarea.widget(); - f.render_widget(widget, chunks[0]); + f.render_widget(&textarea, chunks[0]); })?; match crossterm::event::read()?.into() { diff --git a/examples/popup_placeholder.rs b/examples/popup_placeholder.rs index d705761..136de5c 100644 --- a/examples/popup_placeholder.rs +++ b/examples/popup_placeholder.rs @@ -38,7 +38,7 @@ fn main() -> io::Result<()> { textarea.set_placeholder_text("prompt message"); loop { term.draw(|f| { - f.render_widget(textarea.widget(), area); + f.render_widget(&textarea, area); })?; match crossterm::event::read()?.into() { Input { key: Key::Esc, .. } => break, diff --git a/examples/single_line.rs b/examples/single_line.rs index 1b13f29..1b33df9 100644 --- a/examples/single_line.rs +++ b/examples/single_line.rs @@ -50,8 +50,7 @@ fn main() -> io::Result<()> { loop { term.draw(|f| { let chunks = layout.split(f.size()); - let widget = textarea.widget(); - f.render_widget(widget, chunks[0]); + f.render_widget(&textarea, chunks[0]); })?; match crossterm::event::read()?.into() { diff --git a/examples/split.rs b/examples/split.rs index 6d7afd8..9454651 100644 --- a/examples/split.rs +++ b/examples/split.rs @@ -54,8 +54,7 @@ fn main() -> io::Result<()> { term.draw(|f| { let chunks = layout.split(f.size()); for (textarea, chunk) in textarea.iter().zip(chunks.iter()) { - let widget = textarea.widget(); - f.render_widget(widget, *chunk); + f.render_widget(textarea, *chunk); } })?; match crossterm::event::read()?.into() { diff --git a/examples/termion.rs b/examples/termion.rs index 3a73fe2..95c5662 100644 --- a/examples/termion.rs +++ b/examples/termion.rs @@ -57,8 +57,7 @@ fn main() -> Result<(), Box> { Event::Tick => {} } term.draw(|f| { - let widget = textarea.widget(); - f.render_widget(widget, f.size()); + f.render_widget(&textarea, f.size()); })?; } diff --git a/examples/termwiz.rs b/examples/termwiz.rs index d76349a..a6103f5 100644 --- a/examples/termwiz.rs +++ b/examples/termwiz.rs @@ -22,8 +22,7 @@ fn main() -> Result<(), Box> { // The event loop loop { term.draw(|f| { - let widget = textarea.widget(); - f.render_widget(widget, f.size()); + f.render_widget(&textarea, f.size()); })?; if let Some(input) = term diff --git a/examples/tuirs_editor.rs b/examples/tuirs_editor.rs index c40a74e..ad953fc 100644 --- a/examples/tuirs_editor.rs +++ b/examples/tuirs_editor.rs @@ -190,13 +190,12 @@ impl<'a> Editor<'a> { let chunks = layout.split(f.size()); if search_height > 0 { - f.render_widget(self.search.textarea.widget(), chunks[0]); + f.render_widget(&self.search.textarea, chunks[0]); } let buffer = &self.buffers[self.current]; let textarea = &buffer.textarea; - let widget = textarea.widget(); - f.render_widget(widget, chunks[1]); + f.render_widget(&textarea, chunks[1]); // Render status line let modified = if buffer.modified { " [modified]" } else { "" }; diff --git a/examples/tuirs_minimal.rs b/examples/tuirs_minimal.rs index 2c41cba..743863c 100644 --- a/examples/tuirs_minimal.rs +++ b/examples/tuirs_minimal.rs @@ -28,7 +28,7 @@ fn main() -> io::Result<()> { loop { term.draw(|f| { - f.render_widget(textarea.widget(), f.size()); + f.render_widget(&textarea, f.size()); })?; match crossterm::event::read()?.into() { Input { key: Key::Esc, .. } => break, diff --git a/examples/tuirs_termion.rs b/examples/tuirs_termion.rs index 63ac412..7429b3e 100644 --- a/examples/tuirs_termion.rs +++ b/examples/tuirs_termion.rs @@ -57,8 +57,7 @@ fn main() -> Result<(), Box> { Event::Tick => {} } term.draw(|f| { - let widget = textarea.widget(); - f.render_widget(widget, f.size()); + f.render_widget(&textarea, f.size()); })?; } diff --git a/examples/variable.rs b/examples/variable.rs index bed500e..cb07a01 100644 --- a/examples/variable.rs +++ b/examples/variable.rs @@ -34,7 +34,7 @@ fn main() -> io::Result<()> { .direction(Direction::Vertical) .constraints([Constraint::Length(height), Constraint::Min(0)]) .split(f.size()); - f.render_widget(textarea.widget(), chunks[0]); + f.render_widget(&textarea, chunks[0]); })?; match crossterm::event::read()?.into() { Input { key: Key::Esc, .. } => break, diff --git a/examples/vim.rs b/examples/vim.rs index ce0679c..0e22edb 100644 --- a/examples/vim.rs +++ b/examples/vim.rs @@ -419,7 +419,7 @@ fn main() -> io::Result<()> { let mut vim = Vim::new(Mode::Normal); loop { - term.draw(|f| f.render_widget(textarea.widget(), f.size()))?; + term.draw(|f| f.render_widget(&textarea, f.size()))?; vim = match vim.transition(crossterm::event::read()?.into(), &mut textarea) { Transition::Mode(mode) if vim.mode != mode => { diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index b7fd1e5..ce852f2 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -453,7 +453,7 @@ dependencies = [ [[package]] name = "tui-textarea" -version = "0.5.1" +version = "0.5.2" dependencies = [ "arbitrary", "crossterm", diff --git a/src/cursor.rs b/src/cursor.rs index 4e51449..b54c636 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -233,7 +233,7 @@ pub enum CursorMove { /// ``` /// # use ratatui::buffer::Buffer; /// # use ratatui::layout::Rect; - /// # use ratatui::widgets::Widget; + /// # use ratatui::widgets::Widget as _; /// use tui_textarea::{TextArea, CursorMove}; /// /// // Let's say terminal height is 8. @@ -244,7 +244,7 @@ pub enum CursorMove { /// # // Call `render` at least once to populate terminal size /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 }; /// # let mut b = Buffer::empty(r.clone()); - /// # textarea.widget().render(r, &mut b); + /// # textarea.render(r, &mut b); /// /// // Move cursor to the end of lines (line 20). It is outside the viewport (line 1 to line 8) /// textarea.move_cursor(CursorMove::Bottom); @@ -378,7 +378,7 @@ mod tests { fn in_viewport() { use crate::ratatui::buffer::Buffer; use crate::ratatui::layout::Rect; - use crate::ratatui::widgets::Widget; + use crate::ratatui::widgets::Widget as _; use crate::{CursorMove, TextArea}; let mut textarea: TextArea = (0..20).map(|i| i.to_string()).collect(); @@ -389,7 +389,7 @@ mod tests { height: 8, }; let mut b = Buffer::empty(r); - textarea.widget().render(r, &mut b); + textarea.render(r, &mut b); textarea.move_cursor(CursorMove::Bottom); assert_eq!(textarea.cursor(), (19, 0)); diff --git a/src/scroll.rs b/src/scroll.rs index 875f906..23f301f 100644 --- a/src/scroll.rs +++ b/src/scroll.rs @@ -18,7 +18,7 @@ pub enum Scrolling { /// ``` /// # use ratatui::buffer::Buffer; /// # use ratatui::layout::Rect; - /// # use ratatui::widgets::Widget; + /// # use ratatui::widgets::Widget as _; /// use tui_textarea::{TextArea, Scrolling}; /// /// // Let's say terminal height is 8. @@ -28,7 +28,7 @@ pub enum Scrolling { /// # // Call `render` at least once to populate terminal size /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 }; /// # let mut b = Buffer::empty(r.clone()); - /// # textarea.widget().render(r, &mut b); + /// # textarea.render(r, &mut b); /// /// // Scroll down by 2 lines. /// textarea.scroll(Scrolling::Delta{rows: 2, cols: 0}); @@ -44,7 +44,7 @@ pub enum Scrolling { /// ``` /// # use ratatui::buffer::Buffer; /// # use ratatui::layout::Rect; - /// # use ratatui::widgets::Widget; + /// # use ratatui::widgets::Widget as _; /// use tui_textarea::{TextArea, Scrolling}; /// /// // Let's say terminal height is 8. @@ -54,7 +54,7 @@ pub enum Scrolling { /// # // Call `render` at least once to populate terminal size /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 }; /// # let mut b = Buffer::empty(r.clone()); - /// # textarea.widget().render(r, &mut b); + /// # textarea.render(r, &mut b); /// /// // Scroll down by one page (8 lines) /// textarea.scroll(Scrolling::PageDown); @@ -70,7 +70,7 @@ pub enum Scrolling { /// ``` /// # use ratatui::buffer::Buffer; /// # use ratatui::layout::Rect; - /// # use ratatui::widgets::Widget; + /// # use ratatui::widgets::Widget as _; /// use tui_textarea::{TextArea, Scrolling, CursorMove}; /// /// // Let's say terminal height is 8. @@ -80,13 +80,13 @@ pub enum Scrolling { /// # // Call `render` at least once to populate terminal size /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 }; /// # let mut b = Buffer::empty(r.clone()); - /// # textarea.widget().render(r.clone(), &mut b); + /// # textarea.render(r.clone(), &mut b); /// /// // Go to the last line at first /// textarea.move_cursor(CursorMove::Bottom); /// assert_eq!(textarea.cursor(), (19, 0)); /// # // Call `render` to populate terminal size - /// # textarea.widget().render(r.clone(), &mut b); + /// # textarea.render(r.clone(), &mut b); /// /// // Scroll up by one page (8 lines) /// textarea.scroll(Scrolling::PageUp); @@ -100,7 +100,7 @@ pub enum Scrolling { /// ``` /// # use ratatui::buffer::Buffer; /// # use ratatui::layout::Rect; - /// # use ratatui::widgets::Widget; + /// # use ratatui::widgets::Widget as _; /// use tui_textarea::{TextArea, Scrolling}; /// /// // Let's say terminal height is 8. @@ -110,7 +110,7 @@ pub enum Scrolling { /// # // Call `render` at least once to populate terminal size /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 }; /// # let mut b = Buffer::empty(r.clone()); - /// # textarea.widget().render(r, &mut b); + /// # textarea.render(r, &mut b); /// /// // Scroll down by half-page (4 lines) /// textarea.scroll(Scrolling::HalfPageDown); @@ -126,7 +126,7 @@ pub enum Scrolling { /// ``` /// # use ratatui::buffer::Buffer; /// # use ratatui::layout::Rect; - /// # use ratatui::widgets::Widget; + /// # use ratatui::widgets::Widget as _; /// use tui_textarea::{TextArea, Scrolling, CursorMove}; /// /// // Let's say terminal height is 8. @@ -136,13 +136,13 @@ pub enum Scrolling { /// # // Call `render` at least once to populate terminal size /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 }; /// # let mut b = Buffer::empty(r.clone()); - /// # textarea.widget().render(r.clone(), &mut b); + /// # textarea.render(r.clone(), &mut b); /// /// // Go to the last line at first /// textarea.move_cursor(CursorMove::Bottom); /// assert_eq!(textarea.cursor(), (19, 0)); /// # // Call `render` to populate terminal size - /// # textarea.widget().render(r.clone(), &mut b); + /// # textarea.render(r.clone(), &mut b); /// /// // Scroll up by half-page (4 lines) /// textarea.scroll(Scrolling::HalfPageUp); @@ -193,7 +193,7 @@ mod tests { fn delta() { use crate::ratatui::buffer::Buffer; use crate::ratatui::layout::Rect; - use crate::ratatui::widgets::Widget; + use crate::ratatui::widgets::Widget as _; use crate::TextArea; let mut textarea: TextArea = (0..20).map(|i| i.to_string()).collect(); @@ -204,7 +204,7 @@ mod tests { height: 8, }; let mut b = Buffer::empty(r); - textarea.widget().render(r, &mut b); + textarea.render(r, &mut b); textarea.scroll(Scrolling::Delta { rows: 2, cols: 0 }); assert_eq!(textarea.cursor(), (2, 0)); diff --git a/src/textarea.rs b/src/textarea.rs index 0efd170..6c216f2 100644 --- a/src/textarea.rs +++ b/src/textarea.rs @@ -9,7 +9,7 @@ use crate::scroll::Scrolling; #[cfg(feature = "search")] use crate::search::Search; use crate::util::{spaces, Pos}; -use crate::widget::{Renderer, Viewport}; +use crate::widget::Viewport; use crate::word::{find_word_exclusive_end_forward, find_word_start_backward}; #[cfg(feature = "ratatui")] use ratatui::text::Line; @@ -55,13 +55,18 @@ impl fmt::Display for YankText { } } -/// A type to manage state of textarea. +/// A type to manage state of textarea. These are some important methods: /// -/// [`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. +/// - [`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::lines`] returns line texts. /// ``` /// use tui_textarea::{TextArea, Input, Key}; +/// use ratatui::backend::CrosstermBackend; +/// use ratatui::layout::{Constraint, Direction, Layout}; +/// use ratatui::Terminal; /// /// let mut textarea = TextArea::default(); /// @@ -69,12 +74,35 @@ impl fmt::Display for YankText { /// let input = Input { key: Key::Char('a'), ctrl: false, alt: false, shift: false }; /// textarea.input(input); /// -/// // Get widget to render. -/// let widget = textarea.widget(); -/// /// // Get lines as String. /// println!("Lines: {:?}", textarea.lines()); /// ``` +/// +/// It implements [`ratatui::widgets::Widget`] trait so it can be rendered to a +/// terminal screen via [`ratatui::terminal::Frame::render_widget`] method. +/// ```no_run +/// use ratatui::backend::CrosstermBackend; +/// use ratatui::layout::{Constraint, Direction, Layout}; +/// use ratatui::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()); +/// f.render_widget(&textarea, chunks[0]); +/// }).unwrap(); +/// +/// // ... +/// } +/// ``` #[derive(Clone, Debug)] pub struct TextArea<'a> { lines: Vec, @@ -1589,32 +1617,41 @@ impl<'a> TextArea<'a> { /// Build a ratatui (or tui-rs) widget to render the current state of the textarea. The widget instance returned /// from this method can be rendered with [`ratatui::terminal::Frame::render_widget`]. + /// + /// This method was deprecated at v0.5.3 and is no longer necessary. Instead you can directly pass `&TextArea` + /// reference to the `Frame::render_widget` method call. /// ```no_run - /// use ratatui::backend::CrosstermBackend; - /// use ratatui::layout::{Constraint, Direction, Layout}; - /// use ratatui::Terminal; - /// use tui_textarea::TextArea; + /// # use ratatui::layout::Rect; + /// # use ratatui::Terminal; + /// # use ratatui::widgets::Widget as _; + /// # use ratatui::backend::CrosstermBackend; + /// # use tui_textarea::TextArea; + /// # + /// # let backend = CrosstermBackend::new(std::io::stdout()); + /// # let mut term = Terminal::new(backend).unwrap(); + /// # let textarea = TextArea::default(); + /// # + /// # #[allow(deprecated)] + /// # term.draw(|f| { + /// # let rect = Rect { + /// # x: 0, + /// # y: 0, + /// # width: 24, + /// # height: 8, + /// # }; + /// // v0.5.2 or earlier + /// f.render_widget(textarea.widget(), rect); /// - /// 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(); - /// - /// // ... - /// } + /// // v0.5.3 or later + /// f.render_widget(&textarea, rect); + /// # }).unwrap(); /// ``` + #[deprecated( + since = "0.5.3", + note = "calling this method is no longer neccessary on rendering a textarea. pass &TextArea reference to `Frame::render_widget` method call directly" + )] pub fn widget(&'a self) -> impl Widget + 'a { - Renderer::new(self) + self } /// Set the style of textarea. By default, textarea is not styled. @@ -2277,7 +2314,7 @@ impl<'a> TextArea<'a> { /// ``` /// # use ratatui::buffer::Buffer; /// # use ratatui::layout::Rect; - /// # use ratatui::widgets::Widget; + /// # use ratatui::widgets::Widget as _; /// use tui_textarea::TextArea; /// /// // Let's say terminal height is 8. @@ -2287,7 +2324,7 @@ impl<'a> TextArea<'a> { /// # // Call `render` at least once to populate terminal size /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 }; /// # let mut b = Buffer::empty(r.clone()); - /// # textarea.widget().render(r, &mut b); + /// # textarea.render(r, &mut b); /// /// // Scroll down by 15 lines. Since terminal height is 8, cursor will go out /// // the viewport. @@ -2330,7 +2367,7 @@ mod tests { fn scroll() { use crate::ratatui::buffer::Buffer; use crate::ratatui::layout::Rect; - use crate::ratatui::widgets::Widget; + use crate::ratatui::widgets::Widget as _; let mut textarea: TextArea = (0..20).map(|i| i.to_string()).collect(); let r = Rect { @@ -2340,7 +2377,7 @@ mod tests { height: 8, }; let mut b = Buffer::empty(r); - textarea.widget().render(r, &mut b); + textarea.render(r, &mut b); textarea.scroll((15, 0)); assert_eq!(textarea.cursor(), (15, 0)); diff --git a/src/widget.rs b/src/widget.rs index c0d3862..0901e61 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -11,14 +11,14 @@ use std::sync::atomic::{AtomicU64, Ordering}; #[cfg(feature = "tuirs")] use tui::text::Spans as Line; -// &mut 'a (u16, u16, u16, u16) is not available since Renderer instance totally takes over the ownership of TextArea +// &mut 'a (u16, u16, u16, u16) is not available since `render` method takes immutable reference of TextArea // instance. In the case, the TextArea instance cannot be accessed from any other objects since it is mutablly // borrowed. // -// `tui::terminal::Frame::render_stateful_widget` would be an assumed way to render a stateful widget. But at this -// point we stick with using `tui::terminal::Frame::render_widget` because it is simpler API. Users don't need to +// `ratatui::terminal::Frame::render_stateful_widget` would be an assumed way to render a stateful widget. But at this +// point we stick with using `ratatui::terminal::Frame::render_widget` because it is simpler API. Users don't need to // manage states of textarea instances separately. -// https://docs.rs/tui/latest/tui/terminal/struct.Frame.html#method.render_stateful_widget +// https://docs.rs/ratatui/latest/ratatui/terminal/struct.Frame.html#method.render_stateful_widget #[derive(Default, Debug)] pub struct Viewport(AtomicU64); @@ -80,36 +80,30 @@ impl Viewport { } } -pub struct Renderer<'a>(&'a TextArea<'a>); - -impl<'a> Renderer<'a> { - pub fn new(textarea: &'a TextArea<'a>) -> Self { - Self(textarea) - } - +impl<'a> TextArea<'a> { #[inline] - fn text(&self, top_row: usize, height: usize) -> Text<'a> { - let lines_len = self.0.lines().len(); + fn text_widget(&'a self, top_row: usize, height: usize) -> Text<'a> { + let lines_len = self.lines().len(); let lnum_len = num_digits(lines_len); let bottom_row = cmp::min(top_row + height, lines_len); let mut lines = Vec::with_capacity(bottom_row - top_row); - for (i, line) in self.0.lines()[top_row..bottom_row].iter().enumerate() { - lines.push(self.0.line_spans(line.as_str(), top_row + i, lnum_len)); + for (i, line) in self.lines()[top_row..bottom_row].iter().enumerate() { + lines.push(self.line_spans(line.as_str(), top_row + i, lnum_len)); } Text::from(lines) } #[inline] - fn placeholder_text(&self) -> Text<'a> { - let cursor = Span::styled(" ", self.0.cursor_style); - let text = Span::raw(self.0.placeholder.as_str()); + fn placeholder_widget(&'a self) -> Text<'a> { + let cursor = Span::styled(" ", self.cursor_style); + let text = Span::raw(self.placeholder.as_str()); Text::from(Line::from(vec![cursor, text])) } } -impl<'a> Widget for Renderer<'a> { +impl Widget for &TextArea<'_> { fn render(self, area: Rect, buf: &mut Buffer) { - let Rect { width, height, .. } = if let Some(b) = self.0.block() { + let Rect { width, height, .. } = if let Some(b) = self.block() { b.inner(area) } else { area @@ -125,15 +119,15 @@ impl<'a> Widget for Renderer<'a> { } } - let cursor = self.0.cursor(); - let (top_row, top_col) = self.0.viewport.scroll_top(); + let cursor = self.cursor(); + let (top_row, top_col) = self.viewport.scroll_top(); let top_row = next_scroll_top(top_row, cursor.0 as u16, height); let top_col = next_scroll_top(top_col, cursor.1 as u16, width); - let (text, style) = if !self.0.placeholder.is_empty() && self.0.is_empty() { - (self.placeholder_text(), self.0.placeholder_style) + let (text, style) = if !self.placeholder.is_empty() && self.is_empty() { + (self.placeholder_widget(), self.placeholder_style) } else { - (self.text(top_row as usize, height as usize), self.0.style()) + (self.text_widget(top_row as _, height as _), self.style()) }; // To get fine control over the text color and the surrrounding block they have to be rendered separately @@ -141,8 +135,8 @@ impl<'a> Widget for Renderer<'a> { let mut text_area = area; let mut inner = Paragraph::new(text) .style(style) - .alignment(self.0.alignment()); - if let Some(b) = self.0.block() { + .alignment(self.alignment()); + if let Some(b) = self.block() { text_area = b.inner(area); b.clone().render(area, buf) } @@ -151,7 +145,7 @@ impl<'a> Widget for Renderer<'a> { } // Store scroll top position for rendering on the next tick - self.0.viewport.store(top_row, top_col, width, height); + self.viewport.store(top_row, top_col, width, height); inner.render(text_area, buf); }