"Rats, we're rats; we're the rats." – Rat Movie
We are excited to announce the new version of ratatui - a Rust library that's all about cooking up TUIs 👨🍳🐀
✨ Release highlights: https://ratatui.rs/highlights/v0301/
⚠️ List of breaking changes can be found here.
Features
-
74d6a84 (block) Support shadows by orhun in #2481
Introduce
Block::shadow(...)with a newShadowtype that supports:- presets:
overlay,block,light_shade,medium_shade,
dark_shade - custom symbols via
Shadow::symbol(...) - custom effects via
Shadow::custom(...)
use ratatui::layout::Offset; use ratatui::style::Stylize; use ratatui::widgets::{Block, Shadow}; let popup = Block::bordered().title("Popup").shadow( Shadow::dark_shade() .black() .on_white() .offset(Offset::new(2, 1)), );
Results in:
┌Popup─────┐ │content │▒ └──────────┘▒ ▒▒▒▒▒▒▒▒▒▒▒fixes #1892
- presets:
-
4d30420 (buffer) Add
CellDiffOption::AlwaysUpdateto force cell updates by sxyazi in #2480When this option is used, the cells are updated even when content is identical.
Follow-up for #1605
Trying to resolve #1116
-
39c32c6 (buffer) Add cell diff options by benjajaja in #1605
Problem:Escape sequences always cause a cell to count as "multiwidth",
even when it doesn't render wider than one cell, or not as wide as the
escape sequence would be computed as.Solution:Convert
skip:boolto enum. Add enum optionForceWidthto
force a cell width for diffing.When using the option, this also fixes some bug where diffing is not
idempotent and causes a diff operation for(symbol.len() - 1)times.There are three new specific test cases:
- Rendering hyperlinks by squeezing the escape sequence into the first
cell and forcing the width to the unicode width of the text part.
This is much easier to implement for a Link widget, as it would only
need to get the unicode-width once and not iterate over graphemes
like Spans must do. - Rendering hyperlinks by squeezing the opening sequence into the first
cell with the first grapheme and forcing the width to that of the
first grapheme. Then rendering each grapheme as usual. Then squeezing
the closing sequence into the last cell with the last grapheme and
forcing the width to that of the last grapheme.
This is harder to implement for a Link widget, as it would have to
iterate over graphemes with their width like Spans do. - Kitty image sequence with utf-8 placeholders, similar to 2 but with
known constant grapheme widths.
Link widget that leverages this
https://github.com/benjajaja/tui-link
It would be cooler if we could just add something like
.link(url)to
Spans, because it would much simpler to insert some link and leverage
all the Line/Text/Paragraph wrapping and whatnot. With a custom widget
you need to take care of theAreawhere you'd want to render it, so
it's not that clean. But we could iterate on this later, if even
possible. - Rendering hyperlinks by squeezing the escape sequence into the first
-
6faaddb (core) Implement from slice for line and text by NoOPeEKS in #2371
This PR adds the following implementations of the From trait for Line
and Text structs:- Implements From<&[T]> where T is Into<Span> for Line, allowing using
of slices to construct Lines. - Implements From<&[T]> where T is Into<Line> for Text, allowing using
of slices of various types to construct Texts.
closes #2279
- Implements From<&[T]> where T is Into<Span> for Line, allowing using
-
5fc6ab8 (core) Support layout-cache in no_std environments by junkdog in #2399
this enables "layout-cache" for no_std builds; it's meant for embedded
environments, where the layout engine otherwise consumes all CPU,
capping the framerate at around ~10fps. the same app can refresh 300-500
times per second with layout cache enabled.i had to add
layout-cache = ["dep:critical-section"]to all builds -
it's pretty tiny and shouldn't leave a trace in std-builds. the
alternative is to add an extra layer of features for layout-cache with
std and no_std, but it pollutes the feature space.
-
ee4b7a9 (crossterm) Add the missing hidden modifier by sxyazi in #2413
Fixes sxyazi/yazi#3724, see
sxyazi/yazi#3724 (comment) for a
reproducer.This PR adds the missing
Modifier::HIDDENstyle and introduces a
queue_modifier_diffto testModifierDiff::queue().It also fixes a bug where
CrosstermAttribute::Boldand
CrosstermAttribute::Dimwould be emitted twice when resetting
intensity. For example:#[case(Modifier::DIM, Modifier::BOLD, &[CrosstermAttribute::NormalIntensity, CrosstermAttribute::Bold])]would become:
#[case(Modifier::DIM, Modifier::BOLD, &[CrosstermAttribute::NormalIntensity, CrosstermAttribute::Bold, CrosstermAttribute::Bold])]
-
9d9239a (examples) Add volatility-surface 3D visualization by floor-licker in #2322
A design demonstration of a 3D volatility surface rendering using
Braille canvas with interactive rotation and zoom controls. I built this
for myself for an app I'm currently building but just wanted to share it
with the community as well to inspire more 3D perspective terminal
widgets in the future.Final Demo
Description
Adds a new example demonstrating 3D visualization techniques in the
terminal. There aren't many examples showing how to represent 3D objects
in 2D terminal space so my goal is just to demonstrate more advanced
Canvas and Braille rendering techniques for 3D graphics. The example
visualizes an implied volatility surface which is a common financial
visualization using interactive rotation and zoom controls. You can run
the interactive demo for yourself withcargo run -p volatility-surfacedemo.mov
<img width="659" height="432" alt="image"
src="https://github.com/user-attachments/assets/68698cb0-c5d5-4b41-a3c3-65ec8fff12f5"
/>Technical Highlights
- Demonstrates how to implement perspective projection in a terminal
- Shows advanced use of
Canvaswidget withMarker::Braille - Example of smooth animation patterns and state management
- Self-contained with synthetic data generation (no external APIs)
-
ae975c7 (examples) Allow overlap spacing in explorer by joshka in #2316
Store spacing as i16 so negative values map to Spacing::Overlap, and
show overlap in the axis label. -
1e0ab0c (ratatui-crossterm) Add IntoCrossterm for Style by 0xferrous in #2323
-
101a63e (render) Add function for applying buffer by musjj in #2566
Add a public API for applying and flushing the terminal buffer.
A minimal usage will look something like this:
use ratatui::Terminal; use ratatui::backend::CrosstermBackend; use ratatui::buffer::Buffer; use ratatui::widgets::Widget; let backend = CrosstermBackend::new(io::stdout()); let mut terminal = Terminal::new(backend)?; terminal.autoresize()?; let mut custom_buffer = Buffer::default(); custom_buffer.resize(terminal.get_frame().area()); custom_buffer.reset(); "Hello World!".render(custom_buffer.area, &mut custom_buffer); terminal.current_buffer_mut().merge(&custom_buffer); terminal.apply_buffer()?;
My primary motivation for this PR is to improve the ECS ergonomics in
bevy_ratatui. But this
should be useful for anyone who wants to commit incremental writes to
the buffer without having to do everything in one monolithic
Terminal::draw
closure.
-
f9d066f (table) Let Cells span multiple columns by karkhaz in #2150
Add a 'column_span' field to table cells. The default value
is 1; larger values will cause cells to span over multiple columns,
being rendered over all columns plus the spaces between them.Fixes #1568.
-
a5b08d6 (widgets) Add Fill widget by Metbcy in #2520
Adds a new
Fillwidget that paints every cell within its area with a
single repeated symbol and style. Integrates withStylizeso the whole
chain works as expected:use ratatui::widgets::{Fill, Widget}; use ratatui::style::Stylize; Fill::new("X").blue().bold().render(area, buf);
Implements
Widget(for bothFilland&Fill) andStyled, accepts
anything that converts into aCow<'a, str>, and degrades gracefully on
empty / multi-grapheme symbols.
-
9094fd2 (widgets) Add line shape with filled area for Canvas and Chart by bananaofhappiness in #2426
This commit adds filled area-chart rendering for both
CanvasandChart.You can now render a line and fill the area between that line and a baseline Y value, which helps highlight magnitude/volume trends.
- In
Canvas, useFilledLine. - In
Chart, useGraphType::Areaand set the baseline viaDataset::fill_to_y(f64).
The
f64baseline is now configured onDataset(not inGraphType), soGraphTypestays a simple enum variant.Under the hood, line rasterization in canvas was refactored to share a reusable Bresenham point iterator, and
FilledLinebuilds on that to paint vertical spans from each line point tofill_to_y.Some screenshots with and without this new type:

// In Canvas use ratatui::widgets::canvas::FilledLine; Canvas::default() .paint(|ctx| { ctx.draw(&FilledLine::new(0.0, 0.0, 10.0, 5.0, 0.0, Color::Red)); }); // In Chart let dataset = Dataset::default() .data(&data) .graph_type(GraphType::Area) .fill_to_y(0.0); // fill to y = 0 Chart::new(vec![dataset]);
- In
-
0a87882 (uncategorized) Add
impl From<u16>forPaddingandMarginby JayanAXHF in #2438 -
556cc7b (uncategorized) Add comment for inner area to popup example by Its-Just-Nans in #2309
-
01a15f9 (uncategorized) Add AsRef impls for widget types by joshka in #2297
Bug Fixes
-
d12bb83 (barchart) Handle empty horizontal charts by fallintoplace in #2553
Fixes #2552
This makes the
BarChartconstructors ignore empty groups, matching the
existing.data(...)builder behavior. Without this,BarChart::horizontal(Vec::<Bar>::new())stores one empty group,
proceeds into horizontal rendering, skips the bar loop, and then
underflows when computing the group label row frombar_y - self.bar_gap.The fix normalizes constructor input through a shared
non_empty_groups
helper fornew,horizontal, andgrouped. Empty horizontal charts
now render nothing instead of panicking, and constructor behavior is
consistent with.data(...).
-
6396b1c (block) Saturate block edge arithmetic by joshka in #2488
Motivation
- Block border and title layout used unchecked
u16arithmetic in
several places. - In debug builds that can panic on tiny or edge-case geometry; in
release builds the same arithmetic wraps. - The original report came from merge-border rendering on tiny areas,
but the same pattern appeared in title layout and spacing helpers as
well.
Description
- Use saturating arithmetic in
Block::inner,render_sides,
render_corners,titles_area, andvertical_space. - Clamp rendered title widths to
u16for layout arithmetic. - Replace unchecked title-width accumulation and cursor-advance math
with bounded arithmetic. - Add debug-only regression tests covering empty areas, maximal padding,
title-area edge cases, and very large title widths.
Testing
- Ran
cargo test -p ratatui-widgets block::tests.
- Block border and title layout used unchecked
-
9143b83 (buffer) Diff for trailing cells when only style changes by gcavelier in #2308
this PR closes #2307 by preventing unnecessary diff updates for trailing
cells when only style changes.This PR was generated by Claude, and validated by me.
Summary
This PR fixes a visual artifact bug where block borders would appear
offset when rendered over a widget that had a foreground color style
applied to the entire area.The Fix
- if !next_trailing.skip && prev_trailing != next_trailing { + // Only emit update if the SYMBOL changed, not just the style. + // The style of hidden trailing cells is not visible, so style + // differences alone should not trigger updates that can cause + // cursor positioning issues on some terminals. + if !next_trailing.skip && prev_trailing.symbol() != next_trailing.symbol() {
This aligns the code with the documented intent: only emit updates when
the symbol (visible content) changes, not when only the style
changes.Changes
File Change ratatui-core/src/buffer/buffer.rs:526-530Compare only symbol, not full cell ratatui-core/src/buffer/buffer.rs:1376-1425Add regression test Test Added
#[test] fn diff_ignores_style_only_changes_in_trailing_cells() { // Verifies that trailing cells with same symbol but different style // do NOT generate diff updates }
Why This Is Safe
- Trailing cells are hidden - they are visually covered by the wide
character - Style is invisible - the fg/bg color of a hidden cell has no
visual effect - Symbol changes still trigger updates - if the symbol changes
(e.g., from" "to"x"), the update is still emitted - Aligns with documented intent - the original comment says
"non-blank content", not "different style"
Related
- The existing test
diff_clears_trailing_cell_for_wide_grapheme
verifies that symbol changes DO trigger updates - This fix complements that behavior by ensuring style-only changes do
NOT trigger updates
- Trailing cells are hidden - they are visually covered by the wide
-
e6b71f2 (build) Correct rust-toolchain->rust-version on cargo-deny-action by sermuns in #2471
closes #2470
-
43bbaae (clippy) Fix beta clippy errors by Logan-Ruf in #2433
Noticed these errors on my other PR and figured I could just fix them
real quick.closes #2432
-
957fbb0 (core) Use correct width for halfwidth dakuten/handakuten by orhun in #2499
unicode-width reports U+FF9E/U+FF9F as zero-width, but terminals render
them as 1 cell.
AdjustsCellWidthtrait accordingly for fixing this behavior.fixes #2188
-
d7646c7 (core) Avoid overflow in BufferDiff forced-width advance by joshka in #2487
Motivation
- Prevent arithmetic overflow when advancing
self.posfor
CellDiffOption::ForcedWidth(NonZeroU16)in
ratatui-core/src/buffer/diff.rs, which could panic in debug or wrap in
release and cause an iterator hang/DoS.
Description
- Replace the unchecked
self.pos += width.get().saturating_sub(1)with
a saturating addition viaself.pos = self.pos.saturating_add(width.get().saturating_sub(1) as usize)to
avoid overflow while preserving existing iterator semantics.
Testing
- Ran
cargo test -p ratatui-core buffer::diff --liband the buffer
diff tests completed successfully (10 passed, 0 failed).
- Prevent arithmetic overflow when advancing
-
77f8006 (core) Avoid cursor position queries during resize by orhun in #2485
Terminal::resize()now clears without callingget_cursor_position(),
so that CPR (Cursor position report) calls does not interfere with
stdin.Fixes #2483
-
18aa467 (examples) Make line-gauge example compatible with macos sequoia's terminal.app by lazo4 in #2474
Part of the fix for #1972
Summary
This fix makes the
line-gaugeexample compatible with the macos
sequoia Terminal.app which doesn't support truecolor. It reuses the
is_true_color_supportedintroduced in #2211 by ffex and uses a color
theme instead of hardcoding the colors.Result on macos sequoia
Notes
This is my first open source contribution, thanks to @ffex for letting
me help on this issue -
ce2c228 (examples) Change flex example colors for MacOS default terminal by ffex in #2211
Part of the fix for #1972
Summary
This fix introduces a function to check if we are in a terminal without
truecolor(24-bit) and changes the default colors to appear fine of the
flex example.Notes
The function "is_true_color_supported” is an old problem and there is no
common way to determine if a terminal supports or not the truecolor.This is the main reason why the function detects specifically the
Terminal.app version before the Tahoe. If there are other known
terminals with this problem, we can add it to this function.
-
ef72dba (examples) Fix import for widget examples by orhun in #2422
closes #2299
-
88441cf (terminal) Fix inline viewport resizing issues by clearing the screen by wyvernbw in #2355
adds a check to the autoresize function to clear the entire screen and
move the inline viewport to the top when the window shrinks horizontally
in order to avoid line wrapping issues.Other libraries like ink purge the history as well, but the
backend::ClearTypetype does not support that. Without this if the
user scrolls up they will see previous broken renders. This should work
well with all terminal emulators and multiplexers.fixes #2086
-
91b6fb7 (tests) Use the correct type for the cell diff test by orhun in #2472
fixes the CI!
-
4493742 (widgets) Handle single y-axis label by fallintoplace in #2550
Fixes #2549.
This prevents
Chartfrom panicking when the Y axis is configured with
exactly one label. The X-axis rendering path already skips label
placement when fewer than two labels are provided; this applies the same
guard to Y-axis labels before the spacing calculation divides by
labels_len - 1.This also updates the
Axis::labelsdocs so they describe the newbehavior:fewer than two labels are not rendered instead of causing a
panic. -
0bdebd6 (widgets) Prevent chart scaling overflow by fallintoplace in #2546
Summary
Fixes #2545.
This changes BarChart and Sparkline scaling to use a
u128intermediate
before division, then caps the scaled ticks at the drawable area. That
prevents debug-build panics and release-build wrapping when publicu64
chart values are large.Validation
cargo test -p ratatui-widgets barchart::testscargo test -p ratatui-widgets sparkline::testscargo check -p ratatui-widgets --all-featurescargo clippy -p ratatui-widgets --all-targets --all-features -- -D warningscargo test -p ratatui-widgets
-
e27a22a (widgets) Inherit the text alignment for Paragraph by 7Bpencil in #2369
Paragraph didn't take into account alignment of the text it was created
from:let lines = vec![ Line::from("one"), Line::from("double"), Line::from("quadruple"), ]; let text = Text::from(lines).centered(); // used to be rendered left-aligned, now centered let paragraph = Paragraph::new(text).block(block);
Now the Paragraph inherits the text alignment.
-
b5c0831 (widgets) Avoid panic if Clear area is outside of buffer by 7Bpencil in #2368
If Clear area is at least partially outside of buffer, panic "index
outside of buffer" happens on Widget::renderDemo source code
use crossterm::event::{self, Event, KeyModifiers}; use ratatui::{ layout::Rect, text::Line, widgets::{Block, Borders, Clear, Paragraph}, DefaultTerminal, Frame, }; use std::iter; fn main() { ratatui::run(app); } fn app(terminal: &mut DefaultTerminal) { loop { if let Event::Key(key_event) = event::read().expect("failed to read event") { if key_event.kind.is_press() && key_event.modifiers.contains(KeyModifiers::CONTROL) && key_event.code.is_char('c') { break; } } terminal.draw(render).expect("failed to draw frame"); } } fn render(frame: &mut Frame) { { let width = frame.area().width; let area = Rect::new(0, 0, width, 10); let line = Line::from("W".repeat(area.width as usize)); let lines: Vec<Line> = iter::repeat_n(line, area.height as usize).collect(); frame.render_widget(Paragraph::new(lines), area); } { let area = Rect::new(50, 2, 20, 5); let block = Block::default() .title_top(Line::from("Popup-with-Clear").centered()) .borders(Borders::ALL); let lines = vec![ Line::from("one"), Line::from("double"), Line::from("quadruple"), ]; frame.render_widget(Clear, area); frame.render_widget(Paragraph::new(lines).block(block).centered(), area); } { let area = Rect::new(80, 2, 20, 5); let block = Block::default() .title_top(Line::from("Popup").centered()) .borders(Borders::ALL); let lines = vec![ Line::from("one"), Line::from("double"), Line::from("quadruple"), ]; frame.render_widget(Paragraph::new(lines).block(block).centered(), area); } }
Before the fix:
2026-01-23.05-24-04.online-video-cutter.com.mp4
After the fix:
2026-01-23.05-24-49.online-video-cutter.com.mp4
-
1ce29d6 (uncategorized) Decouple std from serde and palette features by december1981 in #2460
The std feature now passes through to the deps rather than
requiring std to use serde / palette. -
2a0b4b2 (uncategorized) Ensure consistent thumb size when scrolling by kdheepak in #2352
This PR removes the use of
f64for calculating scrollbar thumb size
and uses integer rounding (rounding up instead of the default rounding
down) instead. Using integer rounding seems to fix the problem of thumb
size not being consistent.Fixes #2351
-
4986b28 (uncategorized) Allow ratatui-widgets to be used without default features by jakobhellermann in #2350
Previously, ratatui-widgets was always included with default features.
Additionally, the
stdfeature would always enable thetimecrate.Fixing this gets rid of a few crates:
deranged,num-conv,
time-core,time. -
720303e (uncategorized) Align clear() semantics with contract by joshka in #2320
-
d2b0ce1 (uncategorized) Fix the dependency on time by asomers in #2306
ratatui-widgets uses time's Month::length(), which requires time-0.3.37
or later.
Refactor
-
d754c5e (app) Simplify render function in the scrollbar example by marianomarciello in #2388
Divide the render function into two functions for vertical and
horizontal scroll. -
629e4b2 (core) Reintroduce
Cell::skipas a deprecated field by junkdog in #2437as discussed in
#1605 (comment) -
this brings backCell::skipas a deprecated field in order to avoid
breaking the API in a patch release. in terms of noise; the diff() is
left pretty intact, but had to#[allow(deprecated)]in a couple of
places.Cell::skipvsCellDiffOptionCellDiffOption::ForcedWidthtakes precedence overCell::skip, andCellDiffOption::skipis already skip - soCell::skipcan only
override whenCellDiffOption::Noneis set. i believe this is the
correct behavior, but probably good to have another pair of eyes on it.
@benjajaja maybe has some input too.PartialEq and Hash for
Cell::skipCell::skipis part ofPartialEqandHashas normal fields. another
option would be to treatCellDiffOption::NoneasCellDiffOption::SkipwhenCell::skipis set, and only consider the
effectiveCellDiffOptionfor the trait impls.
-
ca5c109 (core) Introduce
CellWidthtrait for cell width computation by junkdog in #2400this PR introduces a
CellWidthtrait for calculating the cell
width/span, implemented for&strandCell.The impl for
Cellrespects the newCellDiffOption::ForcedWidth. All
width calculations are prefixed with a check if the symbol is ascii
before callingsymbol.width(); this micro-optimization probably won't
do much on computers, but it helps on embedded.As discussed recently in #1605, this also changes the
`CellDiffOption::ForcedWidth(type) from NonZeroUsize to NonZeroU16.
-
9ac167d (style) Add descriptive panic for Color::Reset anstyle conversion by singhh-piyush in #2423
Color::Resethas no equivalent inanstyle::Color. Previously,
convertingColor::Reset.into()fell through to the catch-all arm in
From<Color> for anstyle::Color, which calledAnsiColor::try_from(color).unwrap()and panicked with the message:
called Result::unwrap() on an Err value: Ansi.This replaces the opaque
unwrap()with an explicit, immediately
understandable panic:"Color::Reset has no equivalent in anstyle".Fixes #2341
-
f093b02 (uncategorized) Satisfy beta Clippy buffer debug lint by joshka in #2562
Remove a redundant borrow in
Buffer'sDebugimplementation
Documentation
-
f8b42ad (breaking-changes) Update header for 0.30.0 by orhun in #2295
-
e82b3b7 (buffer) Run the doctests for Buffer by orhun in #2319
addresses
#2314 (review)the doctest was not running at all... (due to
fn foo) -
d8c86c3 (canvas) Add usage example for Line widget by ramadhan-dev-bright in #2473
Added a doc-tested example for the Line struct in canvas to improve
documentation and help new users understand how to render lines with
color. -
83c1579 (changelog) Fix doubled words in two entries by adv0r in #2578
Typo:
in in→inandand and→andinCHANGELOG.md -
fff27e8 (contributing) Add packages needed to run xtasks by alabhyajindal in #2409
Fixes #2408
-
be718d0 (ratatui-core) Fix terminal rendering grammar by Zacxxx in #2555
-
095b47d (ratatui-crossterm) Fix doubled article in module docs by adv0r in #2564
Dear maintainer — AI-authored PR by Composer under
@adv0r. Methodology +
opt-out
at tokens-for-good.
A one-line "no thanks" → auto-apology + auto-close + permanent
blacklist. Silent close treated the same. Your time matters more than
this contribution.Typo:
See the the [Examples]→See the [Examples]in
ratatui-crossterm/src/lib.rs. -
160b177 (ratatui-termwiz) Fix doubled article in module docs by adv0r in #2565
Dear maintainer — AI-authored PR by Composer under
@adv0r. Methodology +
opt-out
at tokens-for-good.
A one-line "no thanks" → auto-apology + auto-close + permanent
blacklist. Silent close treated the same. Your time matters more than
this contribution.Typo:
See the the [Examples]→See the [Examples]in
ratatui-termwiz/src/lib.rs. -
8ce5513 (sparkline) Fix typo in doc comment by adv0r in #2554
-
e6529fd (terminal) Improve terminal and setup docs for app authors by joshka in #2461
Summary
This updates the terminal and setup docs to better match the rendering
behavior Ratatui actually implements, while also making the docs.rs path
clearer for application authors.The main docs changes are split into two commits:
docs: align terminal docs with behavior
- align
Terminal, viewport, frame, and flush docs with the real render
pipeline- clarify the boundary between
Terminal::flushandBackend::flush - improve examples for fullscreen, inline, and fixed viewport usage
- clarify the boundary between
docs: improve terminal docs for app authors
- improve setup-path guidance across
ratatui,ratatui-core, and
backend crates- clarify viewport choice, escape hatches, and common edge cases
- reduce duplication so crate-level docs explain choices while method
docs hold detailed contracts
A small follow-up also fixes the crate-doc heading hierarchy in
ratatui-coreand
ratatui-crosstermso the generated READMEs no longer need
markdownlint-disable-next-line heading-incrementsuppressions.Verification
cargo test -p ratatui --doccargo test -p ratatui-core --doccargo test -p ratatui-crossterm --doccargo test -p ratatui-termion --doccargo test -p ratatui-termwiz --doccargo test --workspace --all-targets --no-runcargo doc -p ratatui --no-depscargo doc -p ratatui-core --no-depscargo xtask format --checkcargo xtask readme --check
Note:
cargo doc -p ratatui-core --no-depsstill reports pre-existing
warnings outside this pass inlayoutandtextdocs. The terminal-doc
warnings introduced during this work were fixed.
-
744dc36 (uncategorized) Remove duplicate word in color and backend module by lphuc2250gma in #2535
Two one-line typo fixes for duplicated "the" in doc-comments:
ratatui-core/src/style/color.rs— "/// the the older serialization
implementation of Color are also able to be deserialized." → "...the
older serialization..."ratatui-core/src/backend.rs— "//! See the the [Examples] directory
for more examples." → "//! See the [Examples] directory for more
examples."
No code/behavior change.
-
3df8c20 (uncategorized) Ask issue authors about PR willingness by joshka in #2317
Add a contribution question to the bug report and feature request
templates so maintainers know whether the reporter wants to work on a
fix/PR or needs guidance. -
53d925a (uncategorized) Add Documentation on the Map Projection / CRS by C-Loftus in #2324
-
64d964b (uncategorized) Update Terminal docs by joshka in #2312
Summary
- Improve
TerminalRustdocs to better explain typical app setup, the
rendering pipeline, and how
diff-based rendering works (including full redraw behavior on viewport
size changes). - Clarify viewport concepts and behavior in
Viewport/Terminaldocs,
with a more complete inline
section (anchoring, scrolling, resize behavior). - Reorder
Terminal::draw/Terminal::try_drawcloser to constructors
so the primary rendering
entry points are easier to find.
Notes
- Docs-only change; no public API or runtime behavior changes.
- No new tests in this PR.
Test Plan
cargo +nightly fmtcargo +nightly docs-rs -p ratatui-corecargo test -p ratatui-core --doc --features std
- Improve
-
8d73d47 (uncategorized) Fix comment to reflect correct symbol in assertion by homebrewmellow in #2314
-
fbd5621 (uncategorized) Fix misspellings by cgzones in #2310
Performance
-
dcea52b (core) Eliminate per-frame Vec allocation in Terminal::flush by junkdog in #2416
what and how
this PR removes the
Vec<(u16, u16, &Cell)>allocation in
Terminal::flushwhen callingBuffer::diff. a new method,
Buffer::diff_iteris instead used byTerminal::flush.the existing diff implementation has moved to the
BufferDiffiterator.why (who cares?)
it's the mice. on embedded devices, allocating a short-lived, contiguous
block of up to 40-50kb is problematic due to heap fragmentation in
combination with tiny heaps. the requirements for a full refresh over
1200 terminal cells is 37.5kb+vec growth padding, but since we're
dealing with a contiguous block of memory, the actual memory
requirements are considerably higher, depending on user-land allocation
patterns.
Testing
-
b696ea3 (core) Split
Terminalinto submodules and expand test coverage by joshka in #2315- Split the
Terminalimplementation into focused submodules to improve
readability and maintainability. - Add characterization tests covering
Terminalinitialization, buffer
lifecycle, resizing and autoresize behavior, and rendering paths. - Add inline viewport tests for
compute_inline_sizeand
insert_beforein both fallback and scrolling-regions modes, including
an end-to-enddraw -> insert_before -> draw scenarioscenario. - Extend
TestBackendcursor plumbing to support the new terminal tests
and assert cursor/ behavior.
- Split the
Miscellaneous Tasks
-
746e9c9 (build) Add symlinks from each crate dir to root LICENSE by martinvonz in #2370
AFAICT, each crate directory must have its own LICENSE file for it to
become part of the published crate. This patch therefore adds LICENSE
symlinks from each crate directory to the root LICENSE file. This
matches what e.g. clap does (https://github.com/clap-rs/clap).Since ratatui-macros/LICENSE already exists and with different copyright
holders than in the root LICENSE file, I left it unchanged. -
7b66bd4 (ci) Use latest released cargo-machete action by sermuns in #2469
-
b6dfafd (markdown) Fix linting issues reported by xtask lint by Logan-Ruf in #2435
-
fba4448 (ratatui) Unleash the rats v0.30.1
-
0b03fe4 (toml) Migrate from taplo to tombi by joshka in #2501
Summary
This migrates the repo's TOML tooling from
Taplo to
Tombi.The main motivation is maintenance and installability:
- Ratatui previously used Taplo for
TOML formatting. - Taplo's maintenance future has been uncertain; see
tamasfe/taplo#715. - Taplo's current release artifacts are not friendly to
cargo-binstall, which means some setups fall back to building from
source. - That source-build path is awkward for CI and was also a blocker while
exploring a future Docker/devcontainer setup because of image size and
install cost.
Tombi is actively maintained,
publishes prebuilt binaries, and has a VS Code extension that lets us
keep editor behavior aligned with CI.What Changed
Tooling and CI
- Replace
taplowithtombiincargo xtask format. - Use
tombi-toml/setup-tombiin
the formatting CI job. - Replace
.taplo.tomlwithtombi.toml.
Formatting policy
- Keep the existing 100-column TOML line width.
- Disable schema-driven top-level table ordering for
Cargo.toml. - Disable the corresponding
tables-out-of-orderlint forCargo.toml.
Why disable Cargo schema ordering:
- Tombi can order keys and tables according to schema metadata; see
https://tombi-toml.github.io/tombi/docs/comment-directive/tombi-value-directive/#format-rules-table-keys-order. - The schema order moved sections like
[features]below dependency
sections. - That made feature-gated behavior harder to scan.
- It also created noisy migration churn that was not central to the tool
switch itself.
Why disable the Cargo lint warning:
- The VS Code warning came from Tombi's
tables-out-of-orderlint
rather than from formatting. - Since we are intentionally preserving the existing Cargo manifest
section order, we also disable that lint forCargo.tomlto keep
diagnostics aligned with the formatter configuration. - Related docs:
https://tombi-toml.github.io/tombi/docs/configuration/.
Installation note
- Tombi is better positioned for binary installs than Taplo today, but
it is still not fully in the genericcargo-binstall/
taiki-e/install-actionpath. - Relevant upstream context:
- binary naming / generic installer compatibility:
tombi-toml/tombi#1164 - crates.io publishing for
cargo-binstallfallback:
tombi-toml/tombi#1686 - earlier crates.io publishing discussion:
tombi-toml/tombi#632 - In practice, this still leaves some rough edges around the generic
Rust installer path, but Tombi already has usable prebuilt binaries and
an official installer action, which is a better place for Ratatui than
Taplo's current install story. - CI now uses
tombi-toml/setup-tombi
with a pinned version, which avoids the GitHub token path by not asking
the action to resolvelatestat runtime.
Docs and editor setup
- Update contributor docs to point to Tombi's repo, docs, and
installation guide. - Note that the repo previously used Taplo and link the upstream
maintenance discussion at tamasfe/taplo#715. - Add VS Code extension recommendations for
rust-lang.rust-analyzer
and
tombi-toml.tombi. - Add committed VS Code workspace settings for shared repo defaults,
mainly so the workspace can define formatter behavior that matches CI,
including TOML formatter selection and nightly rustfmt args. - Add a dedicated formatting section to
CONTRIBUTING.mdcovering Rust
formatting, TOML formatting, nightlyrustfmt, and the VS Code
workspace override workaround.
Why Commit VS Code Settings
This PR adds committed VS Code workspace settings for shared repo
defaults only.The goal is to:
- recommend the expected extensions
- make local editor formatting behave more like CI
- avoid formatter drift between editor usage and the repo's checked-in
tooling
This is not intended to standardize everyone's full editor setup.
Nightly rustfmt
The repo already uses unstable rustfmt options in
rustfmt.toml, so formatting Rust code withcargo xtask formatworks best with nightly Rust. This is already how CI
behaves.The docs now call this out more clearly:
- install nightly if you want local formatting to match CI
- the reason is to pick up the unstable formatting options configured in
rustfmt.toml
For contributors who cannot or do not want to install nightly, the docs
also point to a workaround: use a personal VS Code workspace file with
override settings instead of changing the tracked workspace config.Notes From Implementation
- Tombi
v0.9.19added config support to disable schema-defined
ordering per schema, which let us preserve existingCargo.tomlsection
order cleanly. - The
tables-out-of-orderwarning seen in VS Code came from Tombi
lint, not formatting, so that needed a separate config change. - After disabling Cargo section ordering, the remaining TOML churn was
much smaller and mostly mechanical.
Files of Interest
.github/workflows/ci.ymlxtask/src/commands/format.rstombi.toml.vscode/settings.json.vscode/extensions.jsonCONTRIBUTING.md
Verification
cargo xtask format --checkcargo metadata --format-version 1markdownlint-cli2 CONTRIBUTING.md
- Ratatui previously used Taplo for
-
ed46fef (uncategorized) Update versions
-
f30bab9 (uncategorized) Ignore licker as typo
-
c7746e9 (uncategorized) Bump MSRV to 1.88.0 by orhun in #2396
-
b0a7703 (uncategorized) Escape usernames in changelog with backticks by kdheepak in #2377
This PR escapes all github usernames in the CHANGELOG.md file by adding
a backtick before and after the username.e.g.:Before
1dc18bf (calendar) Add width and height functions by joshka in #2198After
1dc18bf (calendar) Add width and height functions by `@joshka` in #2198This change should prevent unnecessary tags in PRs and forks, at least
until the next release is made with git cliff changes from release-plz -
0031fc6 (uncategorized) Remove
@sign from github release and PR by kdheepak in #2353Previous PR (#2336) didn't solve
the problem, and everyone still seems to be getting notifications (e.g.
#2348).This PR removes the
@for GitHub usernames in the autogenerated PR
body byrelease-plz.{{ changelog | replace(from=" by ", to=" by ") }}The motivation behind this is that GitHub mentions in PR descriptions
trigger notifications for every contributor for every release because
their username is part of the changelog.With this change, in GitHub PR descriptions the changelog will change
like so:Before
1dc18bf (calendar) Add width and height functions by joshka in #2198After
1dc18bf (calendar) Add width and height functions by joshka in #2198 -
ab5ad3f (uncategorized) Escape username using backticks in changelog by kdheepak in #2336
The motivation behind this is that GitHub mentions in PR descriptions trigger notifications for every contributor for every release because their username is part of the changelog.
New Contributors
- @musjj made their first contribution in #2566
- @adv0r made their first contribution in #2578
- @fallintoplace made their first contribution in #2550
- @Zacxxx made their first contribution in #2555
- @lphuc2250gma made their first contribution in #2535
- @lazo4 made their first contribution in #2474
- @Metbcy made their first contribution in #2520
- @bananaofhappiness made their first contribution in #2426
- @sermuns made their first contribution in #2469
- @ramadhan-dev-bright made their first contribution in #2473
- @junkdog made their first contribution in #2437
- @december1981 made their first contribution in #2460
- @7Bpencil made their first contribution in #2369
- @JayanAXHF made their first contribution in #2438
- @Logan-Ruf made their first contribution in #2435
- @singhh-piyush made their first contribution in #2423
- @gcavelier made their first contribution in #2308
- @ffex made their first contribution in #2211
- @BenFradet made their first contribution in #2356
- @wyvernbw made their first contribution in #2355
- @NoOPeEKS made their first contribution in #2371
- @alabhyajindal made their first contribution in #2409
- @martinvonz made their first contribution in #2370
- @jakobhellermann made their first contribution in #2350
- @floor-licker made their first contribution in #2322
- @C-Loftus made their first contribution in #2324
- @0xferrous made their first contribution in #2323
- @homebrewmellow made their first contribution in #2314
- @karkhaz made their first contribution in #2150




