mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-14 00:47:14 +00:00
aed60b9839
Reimplement Terminal::insert_before. The previous implementation would insert the new lines in chunks into the area between the top of the screen and the top of the (new) viewport. If the viewport filled the screen, there would be no area in which to insert lines, and the function would crash. The new implementation uses as much of the screen as it needs to, all the way up to using the whole screen. This commit: - adds a scrollback buffer to the `TestBackend` so that tests can inspect and assert the state of the scrollback buffer in addition to the screen - adds functions to `TestBackend` to assert the state of the scrollback - adds and updates `TestBackend` tests to test the behavior of the scrollback and the new asserting functions - reimplements `Terminal::insert_before`, including adding two new helper functions `Terminal::draw_lines` and `Terminal::scroll_up`. - updates the documentation for `Terminal::insert_before` to clarify some of the edge cases - updates terminal tests to assert the state of the scrollback buffer - adds a new test for the condition that causes the bug - adds a conversion constructor `Cell::from(char)` Fixes: https://github.com/ratatui/ratatui/issues/999
284 lines
8.9 KiB
Rust
284 lines
8.9 KiB
Rust
use std::error::Error;
|
|
|
|
use ratatui::{
|
|
backend::{Backend, TestBackend},
|
|
layout::Rect,
|
|
widgets::{Block, Paragraph, Widget},
|
|
Terminal, TerminalOptions, Viewport,
|
|
};
|
|
|
|
#[test]
|
|
fn terminal_buffer_size_should_be_limited() {
|
|
let backend = TestBackend::new(400, 400);
|
|
let terminal = Terminal::new(backend).unwrap();
|
|
let size = terminal.backend().size().unwrap();
|
|
assert_eq!(size.width, 255);
|
|
assert_eq!(size.height, 255);
|
|
}
|
|
|
|
#[test]
|
|
fn swap_buffer_clears_prev_buffer() {
|
|
let backend = TestBackend::new(100, 50);
|
|
let mut terminal = Terminal::new(backend).unwrap();
|
|
terminal
|
|
.current_buffer_mut()
|
|
.set_string(0, 0, "Hello", ratatui::style::Style::reset());
|
|
assert_eq!(terminal.current_buffer_mut().content()[0].symbol(), "H");
|
|
terminal.swap_buffers();
|
|
assert_eq!(terminal.current_buffer_mut().content()[0].symbol(), " ");
|
|
}
|
|
|
|
#[test]
|
|
fn terminal_draw_returns_the_completed_frame() -> Result<(), Box<dyn Error>> {
|
|
let backend = TestBackend::new(10, 10);
|
|
let mut terminal = Terminal::new(backend)?;
|
|
let frame = terminal.draw(|f| {
|
|
let paragraph = Paragraph::new("Test");
|
|
f.render_widget(paragraph, f.area());
|
|
})?;
|
|
assert_eq!(frame.buffer[(0, 0)].symbol(), "T");
|
|
assert_eq!(frame.area, Rect::new(0, 0, 10, 10));
|
|
terminal.backend_mut().resize(8, 8);
|
|
let frame = terminal.draw(|f| {
|
|
let paragraph = Paragraph::new("test");
|
|
f.render_widget(paragraph, f.area());
|
|
})?;
|
|
assert_eq!(frame.buffer[(0, 0)].symbol(), "t");
|
|
assert_eq!(frame.area, Rect::new(0, 0, 8, 8));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn terminal_draw_increments_frame_count() -> Result<(), Box<dyn Error>> {
|
|
let backend = TestBackend::new(10, 10);
|
|
let mut terminal = Terminal::new(backend)?;
|
|
let frame = terminal.draw(|f| {
|
|
assert_eq!(f.count(), 0);
|
|
let paragraph = Paragraph::new("Test");
|
|
f.render_widget(paragraph, f.area());
|
|
})?;
|
|
assert_eq!(frame.count, 0);
|
|
let frame = terminal.draw(|f| {
|
|
assert_eq!(f.count(), 1);
|
|
let paragraph = Paragraph::new("test");
|
|
f.render_widget(paragraph, f.area());
|
|
})?;
|
|
assert_eq!(frame.count, 1);
|
|
let frame = terminal.draw(|f| {
|
|
assert_eq!(f.count(), 2);
|
|
let paragraph = Paragraph::new("test");
|
|
f.render_widget(paragraph, f.area());
|
|
})?;
|
|
assert_eq!(frame.count, 2);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn terminal_insert_before_moves_viewport() -> Result<(), Box<dyn Error>> {
|
|
// When we have a terminal with 5 lines, and a single line viewport, if we insert a
|
|
// number of lines less than the `terminal height - viewport height` it should move
|
|
// viewport down to accommodate the new lines.
|
|
|
|
let backend = TestBackend::new(20, 5);
|
|
let mut terminal = Terminal::with_options(
|
|
backend,
|
|
TerminalOptions {
|
|
viewport: Viewport::Inline(1),
|
|
},
|
|
)?;
|
|
|
|
// insert_before cannot guarantee the contents of the viewport remain unharmed
|
|
// by potential scrolling as such it is necessary to call draw afterwards to
|
|
// redraw the contents of the viewport over the newly designated area.
|
|
terminal.insert_before(2, |buf| {
|
|
Paragraph::new(vec![
|
|
"------ Line 1 ------".into(),
|
|
"------ Line 2 ------".into(),
|
|
])
|
|
.render(buf.area, buf);
|
|
})?;
|
|
|
|
terminal.draw(|f| {
|
|
let paragraph = Paragraph::new("[---- Viewport ----]");
|
|
f.render_widget(paragraph, f.area());
|
|
})?;
|
|
|
|
terminal.backend().assert_buffer_lines([
|
|
"------ Line 1 ------",
|
|
"------ Line 2 ------",
|
|
"[---- Viewport ----]",
|
|
" ",
|
|
" ",
|
|
]);
|
|
terminal.backend().assert_scrollback_empty();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn terminal_insert_before_scrolls_on_large_input() -> Result<(), Box<dyn Error>> {
|
|
// When we have a terminal with 5 lines, and a single line viewport, if we insert many
|
|
// lines before the viewport (greater than `terminal height - viewport height`) it should
|
|
// move the viewport down to the bottom of the terminal and scroll all lines above the viewport
|
|
// until all have been added to the buffer.
|
|
|
|
let backend = TestBackend::new(20, 5);
|
|
let mut terminal = Terminal::with_options(
|
|
backend,
|
|
TerminalOptions {
|
|
viewport: Viewport::Inline(1),
|
|
},
|
|
)?;
|
|
|
|
terminal.insert_before(5, |buf| {
|
|
Paragraph::new(vec![
|
|
"------ Line 1 ------".into(),
|
|
"------ Line 2 ------".into(),
|
|
"------ Line 3 ------".into(),
|
|
"------ Line 4 ------".into(),
|
|
"------ Line 5 ------".into(),
|
|
])
|
|
.render(buf.area, buf);
|
|
})?;
|
|
|
|
terminal.draw(|f| {
|
|
let paragraph = Paragraph::new("[---- Viewport ----]");
|
|
f.render_widget(paragraph, f.area());
|
|
})?;
|
|
|
|
terminal.backend().assert_buffer_lines([
|
|
"------ Line 2 ------",
|
|
"------ Line 3 ------",
|
|
"------ Line 4 ------",
|
|
"------ Line 5 ------",
|
|
"[---- Viewport ----]",
|
|
]);
|
|
terminal
|
|
.backend()
|
|
.assert_scrollback_lines(["------ Line 1 ------"]);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn terminal_insert_before_scrolls_on_many_inserts() -> Result<(), Box<dyn Error>> {
|
|
// This test ensures similar behaviour to `terminal_insert_before_scrolls_on_large_input`
|
|
// but covers a bug previously present whereby multiple small insertions
|
|
// (less than `terminal height - viewport height`) would have disparate behaviour to one large
|
|
// insertion. This was caused by an undocumented cap on the height to be inserted, which has now
|
|
// been removed.
|
|
|
|
let backend = TestBackend::new(20, 5);
|
|
let mut terminal = Terminal::with_options(
|
|
backend,
|
|
TerminalOptions {
|
|
viewport: Viewport::Inline(1),
|
|
},
|
|
)?;
|
|
|
|
terminal.insert_before(1, |buf| {
|
|
Paragraph::new(vec!["------ Line 1 ------".into()]).render(buf.area, buf);
|
|
})?;
|
|
|
|
terminal.insert_before(1, |buf| {
|
|
Paragraph::new(vec!["------ Line 2 ------".into()]).render(buf.area, buf);
|
|
})?;
|
|
|
|
terminal.insert_before(1, |buf| {
|
|
Paragraph::new(vec!["------ Line 3 ------".into()]).render(buf.area, buf);
|
|
})?;
|
|
|
|
terminal.insert_before(1, |buf| {
|
|
Paragraph::new(vec!["------ Line 4 ------".into()]).render(buf.area, buf);
|
|
})?;
|
|
|
|
terminal.insert_before(1, |buf| {
|
|
Paragraph::new(vec!["------ Line 5 ------".into()]).render(buf.area, buf);
|
|
})?;
|
|
|
|
terminal.draw(|f| {
|
|
let paragraph = Paragraph::new("[---- Viewport ----]");
|
|
f.render_widget(paragraph, f.area());
|
|
})?;
|
|
|
|
terminal.backend().assert_buffer_lines([
|
|
"------ Line 2 ------",
|
|
"------ Line 3 ------",
|
|
"------ Line 4 ------",
|
|
"------ Line 5 ------",
|
|
"[---- Viewport ----]",
|
|
]);
|
|
terminal
|
|
.backend()
|
|
.assert_scrollback_lines(["------ Line 1 ------"]);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn terminal_insert_before_large_viewport() -> Result<(), Box<dyn Error>> {
|
|
// This test covers a bug previously present whereby doing an insert_before when the
|
|
// viewport covered the entire screen would cause a panic.
|
|
|
|
let backend = TestBackend::new(20, 3);
|
|
let mut terminal = Terminal::with_options(
|
|
backend,
|
|
TerminalOptions {
|
|
viewport: Viewport::Inline(3),
|
|
},
|
|
)?;
|
|
|
|
terminal.insert_before(1, |buf| {
|
|
Paragraph::new(vec!["------ Line 1 ------".into()]).render(buf.area, buf);
|
|
})?;
|
|
|
|
terminal.insert_before(3, |buf| {
|
|
Paragraph::new(vec![
|
|
"------ Line 2 ------".into(),
|
|
"------ Line 3 ------".into(),
|
|
"------ Line 4 ------".into(),
|
|
])
|
|
.render(buf.area, buf);
|
|
})?;
|
|
|
|
terminal.insert_before(7, |buf| {
|
|
Paragraph::new(vec![
|
|
"------ Line 5 ------".into(),
|
|
"------ Line 6 ------".into(),
|
|
"------ Line 7 ------".into(),
|
|
"------ Line 8 ------".into(),
|
|
"------ Line 9 ------".into(),
|
|
"----- Line 10 ------".into(),
|
|
"----- Line 11 ------".into(),
|
|
])
|
|
.render(buf.area, buf);
|
|
})?;
|
|
|
|
terminal.draw(|f| {
|
|
let paragraph = Paragraph::new("Viewport")
|
|
.centered()
|
|
.block(Block::bordered());
|
|
f.render_widget(paragraph, f.area());
|
|
})?;
|
|
|
|
terminal.backend().assert_buffer_lines([
|
|
"┌──────────────────┐",
|
|
"│ Viewport │",
|
|
"└──────────────────┘",
|
|
]);
|
|
terminal.backend().assert_scrollback_lines([
|
|
"------ Line 1 ------",
|
|
"------ Line 2 ------",
|
|
"------ Line 3 ------",
|
|
"------ Line 4 ------",
|
|
"------ Line 5 ------",
|
|
"------ Line 6 ------",
|
|
"------ Line 7 ------",
|
|
"------ Line 8 ------",
|
|
"------ Line 9 ------",
|
|
"----- Line 10 ------",
|
|
"----- Line 11 ------",
|
|
]);
|
|
|
|
Ok(())
|
|
}
|