This is a 20% performance improvement on buffer diff on my M2 MBP.
```
buffer/diff time: [100.26 µs 100.69 µs 101.15 µs]
change: [-18.007% -17.489% -16.929%] (p = 0.00 < 0.05)
Performance has improved.
```
The current implementation of Terminal::insert_before causes the
viewport to flicker. This is described in #584 .
This PR removes that flickering by using terminal scrolling regions
(sometimes called "scroll regions"). A terminal can have its scrolling
region set to something other than the whole screen. When a scroll ANSI
sequence is sent to the terminal and it has a non-default scrolling
region, the terminal will scroll just inside of that region.
We use scrolling regions to implement insert_before. We create a region
on the screen above the viewport, scroll that up to make room for the
newly inserted lines, and then draw the new lines. We may need to repeat
this process depending on how much space there is and how many lines we
need to draw.
When the viewport takes up the entire screen, we take a modified
approach. We create a scrolling region of just the top line (could be
more) of the viewport, then use that to draw the lines we want to
output. When we're done, we scroll it up by one line, into the
scrollback history, and then redraw the top line from the viewport.
A final edge case is when the viewport hasn't yet reached the bottom of
the screen. This case, we set up a different scrolling region, where the
top is the top of the viewport, and the bottom is the viewport's bottom
plus the number of lines we want to scroll by. We then scroll this
region down to open up space above the viewport for drawing the inserted
lines.
Regardless of what we do, we need to reset the scrolling region. This PR
takes the approach of always resetting the scrolling region after every
operation. So the Backend gets new scroll_region_up and
scroll_region_down methods instead of set_scrolling_region, scroll_up,
scroll_down, and reset_scrolling_region methods. We chose that approach
for two reasons. First, we don't want Ratatui to have to remember that
state and then reset the scrolling region when tearing down. Second, the
pre-Windows-10 console code doesn't support scrolling regio
This PR:
- Adds a new scrolling-regions feature.
- Adds two new Backend methods: scroll_region_up and scroll_region_down.
- Implements those Backend methods on all backends in the codebase.
- The crossterm and termion implementations use raw ANSI escape
sequences. I'm trying to merge changes into those two projects
separately to support these functions.
- Adds code to Terminal::insert_before to choose between
insert_before_scrolling_regions and insert_before_no_scrolling_regions.
The latter is the old implementation.
- Adds lots of tests to the TestBackend to for the
scrolling-region-related Backend methods.
- Adds versions of terminal tests that show that insert_before doesn't
clobber the viewport. This is a change in behavior from before.
BREAKING-CHANGE: The `pub` modifier has been removed from fields on the
`layout::rect::Columns` and `layout::rect::Rows` iterators. These fields
were not intended to be public and should not have been accessed
directly.
Fixes: #1357
This change fixes the unexpected behavior of the Rect::new() function to
be more intuitive. The Rect::new() function now clamps the width and
height of the rectangle to keep each bound within u16::MAX. The
Rect::area() function now returns a u32 instead of a u16 to allow for
larger areas to be calculated.
Previously, the Rect::new() function would clamp the total area of the
rectangle to u16::MAX, by preserving the aspect ratio of the rectangle.
BREAKING CHANGE: Rect::area() now returns a u32 instead of a u16.
Fixes: <https://github.com/ratatui/ratatui/issues/1375>
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
BREAKING-CHANGES: `Line` now implements `From<Cow<str>`
As this adds an extra conversion, ambiguous infered values may no longer
compile.
```rust
// given:
struct Foo { ... }
impl From<Foo> for String { ... }
impl From<Foo> for Cow<str> { ... }
let foo = Foo { ... };
let line = Line::from(foo); // now fails due to ambiguous type inference
// replace with
let line = Line::from(String::from(foo));
```
Fixes: https://github.com/ratatui/ratatui/issues/1367
---------
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
`Tabs::select()` now accepts `Into<Option<usize>>` instead of `usize`.
This allows tabs to be deselected by passing `None`.
`Tabs::default()` is now also implemented manually instead of deriving
`Default`, and a new method `Tabs::titles()` is added to set the titles
of the tabs.
Fixes: <https://github.com/ratatui/ratatui/pull/1412>
BREAKING CHANGE: `Tabs::select()` now accepts `Into<Option<usize>>`
which breaks any code already using parameter type inference:
```diff
let selected = 1u8;
- let tabs = Tabs::new(["A", "B"]).select(selected.into())
+ let tabs = Tabs::new(["A", "B"]).select(selected as usize)
```
Fixes https://github.com/ratatui-org/ratatui/issues/1250
Adds support for selecting a column and cell in `TableState`. The
selected column, and cells style can be set by
`Table::column_highlight_style` and `Table::cell_highlight_style`
respectively.
The table example has also been updated to display the new
functionality:
https://github.com/user-attachments/assets/e5fd2858-4931-4ce1-a2f6-a5ea1eacbecc
BREAKING CHANGE: The Serialized output of the state will now include the
"selected_column" field. Software that manually parse the serialized the
output (with anything other than the `Serialize` implementation on
`TableState`) may have to be refactored if the "selected_column" field
is not accounted for. This does not affect users who rely on the
`Deserialize`, or `Serialize` implementation on the state.
BREAKING CHANGE: The `Table::highlight_style` is now deprecated in favor
of `Table::row_highlight_style`.
---------
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
Improves https://github.com/ratatui/ratatui/pull/1383
The following now round trips when formatted for debug.
This will make it easier to use insta when testing text related views of
widgets.
```rust
Text::from_iter([
Line::from("Hello, world!"),
Line::from("How are you?").bold().left_aligned(),
Line::from_iter([
Span::from("I'm "),
Span::from("doing ").italic(),
Span::from("great!").bold(),
]),
]).on_blue().italic().centered()
```
Fixes#1399
I've looked through all the `assert_eq` and made sure that they follow
the `expected, result` pattern. I wasn't sure if it was desired to
actually pass result and expected as variables to the assert_eq
statements, so I've left everything that seems to have followed the
pattern as is.
This is a simple logo widget that can be used to render the Ratatui logo
in the terminal. It is used in the `examples/ratatui-logo.rs` example,
and may be used in your applications' help or about screens.
```rust
use ratatui::{Frame, widgets::RatatuiLogo};
fn draw(frame: &mut Frame) {
frame.render_widget(RatatuiLogo::tiny(), frame.area());
}
```
This PR renames `element` to `segment` in a couple of functions in the
layout calculations for clarity. `element` can refer to `segment`s or
`spacer`s and functions that take only `segment`s should use `segment`
as the variable names.
This helps make the doc examples more explicit about what is being used.
It will also makes it a bit easier to do future refactoring of Ratatui,
into several crates, as the ambiguity of where types are coming from
will be reduced.
Additionally, several doc examples have been simplified to use Stylize,
and necessary imports are no longer hidden.
This doesn't remove the prelude. Only the internal usages.
`ratatui::widgets::block::Title` is deprecated in favor of using `Line`
to represent titles.
This removes an unnecessary layer of wrapping (string -> Span -> Line ->
Title).
This struct will be removed in a future release of Ratatui (likely
0.31).
For more information see:
<https://github.com/ratatui/ratatui/issues/738>
To update your code:
```rust
Block::new().title(Title::from("foo"));
// becomes any of
Block::new().title("foo");
Block::new().title(Line::from("foo"));
Block::new().title(Title::from("foo").position(Position::TOP));
// becomes any of
Block::new().title_top("foo");
Block::new().title_top(Line::from("foo"));
Block::new().title(Title::from("foo").position(Position::BOTTOM));
// becomes any of
Block::new().title_bottom("foo");
Block::new().title_bottom(Line::from("foo"));
```
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
These are simple opinionated methods for creating a terminal that is
useful to use in most apps. The new init method creates a crossterm
backend writing to stdout, enables raw mode, enters the alternate
screen, and sets a panic handler that restores the terminal on panic.
A minimal hello world now looks a bit like:
```rust
use ratatui::{
crossterm::event::{self, Event},
text::Text,
Frame,
};
fn main() {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("Failed to draw");
if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
break;
}
}
ratatui::restore();
}
```
A type alias `DefaultTerminal` is added to represent this terminal
type and to simplify any cases where applications need to pass this
terminal around. It is equivalent to:
`Terminal<CrosstermBackend<Stdout>>`
We also added `ratatui::try_init()` and `try_restore()`, for situations
where you might want to handle initialization errors yourself instead
of letting the panic handler fire and cleanup. Simple Apps should
prefer the `init` and `restore` functions over these functions.
Corresponding functions to allow passing a `TerminalOptions` with
a `Viewport` (e.g. inline, fixed) are also available
(`init_with_options`,
and `try_init_with_options`).
The existing code to create a backend and terminal will remain and
is not deprecated by this approach. This just provides a simple one
line initialization using the common options.
---------
Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
If the amount of characters in the screen above the viewport was greater
than u16::MAX, a multiplication would overflow. The multiply was used to
compute the maximum chunk size. The fix is to just do the multiplication
as a usize and also do the subsequent division as a usize.
There is currently another outstanding issue that limits the amount of
characters that can be inserted when calling Terminal::insert_before to
u16::MAX. However, this bug can still occur even if the viewport and the
amount of characters being inserted are both less than u16::MAX, since
it's dependant on how large the screen is above the viewport.
Fixes#1322
Termion is not supported on Windows, so we need to avoid building it.
Adds a conditional dependency to the Cargo.toml file to only include
termion when the target is not Windows. This allows contributors to
build using the `--all-features` flag on Windows rather than needing
to specify the features individually.
On large paragraphs (~1MB), this saves hundreds of thousands of
allocations.
TL;DR: reuse as much memory as possible across `next_line` calls.
Instead of allocating new buffers each time, allocate the buffers once
and clear them before reuse.
Signed-off-by: Alex Saveau <saveau.alexandre@gmail.com>
The new methods return/accept `Into<Position>` which can be either a Position or a (u16, u16) tuple.
```rust
backend.set_cursor_position(Position { x: 0, y: 20 })?;
let position = backend.get_cursor_position()?;
terminal.set_cursor_position((0, 20))?;
let position = terminal.set_cursor_position()?;
```
Code which previously called `buf.get(x, y)` or `buf.get_mut(x, y)`
should now use index operators, or be transitioned to `buff.cell()` or
`buf.cell_mut()` for safe access that avoids panics by returning
`Option<&Cell>` and `Option<&mut Cell>`.
The new methods accept `Into<Position>` instead of `x` and `y`
coordinates, which makes them more ergonomic to use.
```rust
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
let cell = buf[(0, 0)];
let cell = buf[Position::new(0, 0)];
let symbol = buf.cell((0, 0)).map(|cell| cell.symbol());
let symbol = buf.cell(Position::new(0, 0)).map(|cell| cell.symbol());
buf[(0, 0)].set_symbol("🐀");
buf[Position::new(0, 0)].set_symbol("🐀");
buf.cell_mut((0, 0)).map(|cell| cell.set_symbol("🐀"));
buf.cell_mut(Position::new(0, 0)).map(|cell| cell.set_symbol("🐀"));
```
The existing `get()` and `get_mut()` methods are marked as deprecated.
These are fairly widely used and we will leave these methods around on
the buffer for a longer time than our normal deprecation approach (2
major release)
Addresses part of: https://github.com/ratatui-org/ratatui/issues/1011
---------
Co-authored-by: EdJoPaTo <rfc-conform-git-commit-email@funny-long-domain-label-everyone-hates-as-it-is-too-long.edjopato.de>
Addresses some confusion about when to implement `WidgetRef` vs `impl
Widget for &W`. Notes the stability rationale and links to an issue that
helps explain the context of where we're at in working this out.
Removes the height and width fields from TestBackend, which can get
out of sync with the Buffer, which currently clamps to 255,255.
This changes the `TestBackend` serde representation. It should be
possible to read older data, but data generated after this change
can't be read by older versions.
`axis.labels(vec![])` removes all the labels correctly.
This makes calling axis.labels with an empty Vec the equivalent
of not calling axis.labels. It's likely that this is never used, but it
prevents weird cases by removing the mix-up of `Option::None`
and `Vec::is_empty`, and simplifies the implementation code.