зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-09-03 22:35:50 +03:00
directly implement Widget for &TextArea (fix #78)
Этот коммит содержится в:
16
README.md
16
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 `<input>` in HTML
|
||||
|
||||
To use `TextArea` for single-line input widget like `<input>` in HTML, ignore all key mappings which inserts newline.
|
||||
To use `TextArea` for a single-line input widget like `<input>` 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);
|
||||
}
|
||||
})?;
|
||||
|
||||
|
||||
@@ -116,7 +116,6 @@ pub trait TerminalExt {
|
||||
impl TerminalExt for Terminal<DummyBackend> {
|
||||
#[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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { "" };
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -57,8 +57,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
Event::Tick => {}
|
||||
}
|
||||
term.draw(|f| {
|
||||
let widget = textarea.widget();
|
||||
f.render_widget(widget, f.size());
|
||||
f.render_widget(&textarea, f.size());
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
// 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
|
||||
|
||||
@@ -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 { "" };
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -57,8 +57,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
Event::Tick => {}
|
||||
}
|
||||
term.draw(|f| {
|
||||
let widget = textarea.widget();
|
||||
f.render_widget(widget, f.size());
|
||||
f.render_widget(&textarea, f.size());
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
2
fuzz/Cargo.lock
сгенерированный
2
fuzz/Cargo.lock
сгенерированный
@@ -453,7 +453,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tui-textarea"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crossterm",
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
105
src/textarea.rs
105
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<String>,
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user