feat(no_std)!: make ratatui compatible with #![no_std] (#1794)

Resolves #1781

This PR makes it possible to compile ratatui with `#![no_std]`.
Also makes me answer "We Are So Embedded" to "Are We Embedded Yet?"
This commit is contained in:
Jagoda Estera Ślązak
2025-05-07 22:42:03 +02:00
committed by GitHub
parent ab48c06171
commit 3e1c72fb27
10 changed files with 48 additions and 18 deletions

View File

@@ -22,6 +22,7 @@ This is a quick summary of the sections below:
- `TestBackend` now uses `core::convert::Infallible` for error handling instead of `std::io::Error`
- Disabling `default-features` will now disable layout cache, which can have a negative impact on performance
- `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache` feature is enabled
- Disabling `default-features` suppresses the error message if `show_cursor()` fails when dropping `Terminal`
- [v0.29.0](#v0290)
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer const
- Removed public fields from `Rect` iterators
@@ -86,6 +87,13 @@ This is a quick summary of the sections below:
## Unreleased (0.30.0)
### Disabling `default-features` suppresses the error message if `show_cursor()` fails when dropping `Terminal` ([#1794])
[#1794]: https://github.com/ratatui/ratatui/pull/1794
Since disabling `default-features` disables `std`, printing to stderr is not possible. It is
recommended to re-enable `std` when not using Ratatui in `no_std` environment.
### `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache` feature is enabled ([#1795])
[#1795]: https://github.com/ratatui/ratatui/pull/1795

View File

@@ -1,9 +1,7 @@
use alloc::format;
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::iter;
use core::num::NonZeroUsize;
use std::dbg;
use hashbrown::HashMap;
use itertools::Itertools;
@@ -1020,8 +1018,9 @@ fn changes_to_rects(
/// please leave this here as it's useful for debugging unit tests when we make any changes to
/// layout code - we should replace this with tracing in the future.
#[expect(dead_code)]
#[cfg(feature = "std")]
fn debug_elements(elements: &[Element], changes: &HashMap<Variable, f64>) {
let variables = format!(
let variables = alloc::format!(
"{:?}",
elements
.iter()
@@ -1031,7 +1030,7 @@ fn debug_elements(elements: &[Element], changes: &HashMap<Variable, f64>) {
))
.collect::<Vec<(f64, f64)>>()
);
dbg!(variables);
std::dbg!(variables);
}
/// A container used by the solver inside split

View File

@@ -43,9 +43,9 @@
#![warn(clippy::std_instead_of_alloc)]
#![warn(clippy::alloc_instead_of_core)]
extern crate std;
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub mod backend;
pub mod buffer;

View File

@@ -1,5 +1,3 @@
use std::eprintln;
use crate::backend::{Backend, ClearType};
use crate::buffer::{Buffer, Cell};
use crate::layout::{Position, Rect, Size};
@@ -91,8 +89,10 @@ where
fn drop(&mut self) {
// Attempt to restore the cursor state
if self.hidden_cursor {
#[allow(unused_variables)]
if let Err(err) = self.show_cursor() {
eprintln!("Failed to show the cursor: {err}");
#[cfg(feature = "std")]
std::eprintln!("Failed to show the cursor: {err}");
}
}
}

View File

@@ -1,3 +1,4 @@
#![no_std]
// show the feature flags in the generated documentation
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
@@ -80,7 +81,6 @@
//!
//! This project is licensed under the MIT License. See the [LICENSE](../LICENSE) file for details.
#![no_std]
#![warn(clippy::std_instead_of_core)]
#![warn(clippy::std_instead_of_alloc)]
#![warn(clippy::alloc_instead_of_core)]

View File

@@ -31,11 +31,14 @@ rustdoc-args = ["--cfg", "docsrs"]
default = ["crossterm", "underline-color", "all-widgets", "macros", "layout-cache"]
#! Generally an application will only use one backend, so you should only enable one of the following features:
## enables the [`CrosstermBackend`](backend::CrosstermBackend) backend and adds a dependency on [`crossterm`].
crossterm = ["dep:ratatui-crossterm"]
crossterm = ["std", "dep:ratatui-crossterm"]
## enables the [`TermionBackend`](backend::TermionBackend) backend and adds a dependency on [`termion`].
termion = ["dep:ratatui-termion"]
termion = ["std", "dep:ratatui-termion"]
## enables the [`TermwizBackend`](backend::TermwizBackend) backend and adds a dependency on [`termwiz`].
termwiz = ["dep:ratatui-termwiz"]
termwiz = ["std", "dep:ratatui-termwiz"]
## enables std
std = ["ratatui-core/std"]
#! The following optional features are available for all backends:
## enables serialization and deserialization of style and color types using the [`serde`] crate.
@@ -50,7 +53,7 @@ serde = [
]
## enables layout cache
layout-cache = ["ratatui-core/layout-cache"]
layout-cache = ["std", "ratatui-core/layout-cache"]
## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color).
palette = ["ratatui-core/palette", "dep:palette"]

View File

@@ -198,7 +198,7 @@ pub fn try_init_with_options(options: TerminalOptions) -> io::Result<DefaultTerm
pub fn restore() {
if let Err(err) = try_restore() {
// There's not much we can do if restoring the terminal fails, so we just print the error
eprintln!("Failed to restore terminal: {err}");
std::eprintln!("Failed to restore terminal: {err}");
}
}
@@ -236,7 +236,7 @@ pub fn try_restore() -> io::Result<()> {
/// original panic hook. This ensures that the terminal is left in a good state when a panic occurs.
fn set_panic_hook() {
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
std::panic::set_hook(alloc::boxed::Box::new(move |info| {
restore();
hook(info);
}));

View File

@@ -1,3 +1,4 @@
#![no_std]
// show the feature flags in the generated documentation
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
@@ -323,6 +324,14 @@
//! [Forum]: https://forum.ratatui.rs
//! [Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui?logo=github&style=flat-square&color=1370D3
#![warn(clippy::std_instead_of_core)]
#![warn(clippy::std_instead_of_alloc)]
#![warn(clippy::alloc_instead_of_core)]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
/// re-export the `palette` crate so that users don't have to add it as a dependency
#[cfg(feature = "palette")]
pub use palette;

View File

@@ -81,6 +81,11 @@ where
#[cfg(test)]
mod tests {
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use rstest::{fixture, rstest};
use super::*;
@@ -129,7 +134,7 @@ mod tests {
impl StatefulWidgetRef for Bytes {
type State = [u8];
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let slice = std::str::from_utf8(state).unwrap();
let slice = core::str::from_utf8(state).unwrap();
Line::from(format!("Bytes: {slice}")).render(area, buf);
}
}
@@ -145,7 +150,7 @@ mod tests {
impl StatefulWidget for &Bytes {
type State = [u8];
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let slice = std::str::from_utf8(state).unwrap();
let slice = core::str::from_utf8(state).unwrap();
Line::from(format!("Bytes: {slice}")).render(area, buf);
}
}

View File

@@ -1,3 +1,5 @@
use alloc::string::String;
use super::Widget;
use crate::buffer::Buffer;
use crate::layout::Rect;
@@ -166,6 +168,10 @@ impl<W: WidgetRef> WidgetRef for Option<W> {
#[cfg(test)]
mod tests {
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use rstest::{fixture, rstest};
use super::*;