Compare commits

...

8 Commits

Author SHA1 Message Date
Tadeas Uradnik
5fbb77ad20 chore(README): use terminal theme for badges (#1026)
The badges in the readme were all the default theme. Giving them
prettier colors that match the terminal gif is better. I've used the
colors from the VHS repo.
2024-04-25 20:27:28 -07:00
EdJoPaTo
97ee102f17 feat(buffer): track_caller for index_of (#1046)
The caller put in the wrong x/y -> the caller is the cause.
2024-04-25 16:23:09 -07:00
EdJoPaTo
c442dfd1ad perf(canvas): change map data to const instead of static (#1037) 2024-04-24 01:49:57 -07:00
Ibby
0a164965ea fix: use to_string to serialize Color (#934)
Since deserialize now uses `FromStr` to deserialize color, serializing
`Color` RGB values, as well as index values, would produce an output
that would no longer be able to be deserialized without causing an
error.


Color::Rgb will now be serialized as the hex representation of their
value.
For example, with serde_json, `Color::Rgb(255, 0, 255)` would be
serialized as `"#FF00FF"` rather than `{"Rgb": [255, 0, 255]}`.

Color::Indexed will now be serialized as just the string of the index.
For example, with serde_json, `Color::Indexed(10)` would be serialized
as `"10"` rather than `{"Indexed": 10}`.

Other color variants remain the same.
2024-04-23 23:30:00 -07:00
EdJoPaTo
bf0923473c feat(table): make TableState::new const (#1040) 2024-04-21 23:23:56 -06:00
EdJoPaTo
81b96338ea perf(calendar): use const fn (#1039)
Also, do the comparison without `as u8`. Stays the same at runtime and
is cleaner code.
2024-04-21 11:34:17 -06:00
EdJoPaTo
2e71c1874e perf(buffer): simplify Buffer::filled with macro (#1036)
The `vec![]` macro is highly optimized by the Rust team and shorter.
Don't do it manually.

This change is mainly cleaner code. The only production code that uses
this is `Terminal::with_options` and `Terminal::insert_before` so it's
not performance relevant on every render.
2024-04-21 11:29:37 -06:00
Josh McKinney
c75aa1990f build: add clippy::cargo lint (#1053)
Followup to https://github.com/ratatui-org/ratatui/pull/1035 and
https://github.com/ratatui-org/ratatui/discussions/1034

It's reasonable to enable this and deal with breakage by fixing any
specific issues that arise.
2024-04-21 10:43:46 -06:00
10 changed files with 173 additions and 39 deletions

View File

@@ -64,7 +64,9 @@ serde_json = "1.0.109"
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
cargo = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
cast_possible_truncation = "allow"
cast_possible_wrap = "allow"

View File

@@ -327,24 +327,23 @@ Running this example produces the following output:
[Termwiz]: https://crates.io/crates/termwiz
[tui-rs]: https://crates.io/crates/tui
[GitHub Sponsors]: https://github.com/sponsors/ratatui-org
[Crate Badge]: https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square
[License Badge]: https://img.shields.io/crates/l/ratatui?style=flat-square
[CI Badge]:
https://img.shields.io/github/actions/workflow/status/ratatui-org/ratatui/ci.yml?style=flat-square&logo=github
[Crate Badge]: https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square&logoColor=E05D44&color=E05D44
[License Badge]: https://img.shields.io/crates/l/ratatui?style=flat-square&color=1370D3
[CI Badge]: https://img.shields.io/github/actions/workflow/status/ratatui-org/ratatui/ci.yml?style=flat-square&logo=github
[CI Workflow]: https://github.com/ratatui-org/ratatui/actions/workflows/ci.yml
[Codecov Badge]:
https://img.shields.io/codecov/c/github/ratatui-org/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST
https://img.shields.io/codecov/c/github/ratatui-org/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST&color=C43AC3&logoColor=C43AC3
[Codecov]: https://app.codecov.io/gh/ratatui-org/ratatui
[Deps.rs Badge]: https://deps.rs/repo/github/ratatui-org/ratatui/status.svg?style=flat-square
[Deps.rs]: https://deps.rs/repo/github/ratatui-org/ratatui
[Discord Badge]:
https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square
https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square&color=1370D3&logoColor=1370D3
[Discord Server]: https://discord.gg/pMCEU9hNEj
[Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square
[Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square&logoColor=E05D44
[Matrix Badge]:
https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix
https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix&color=C43AC3
[Matrix]: https://matrix.to/#/#ratatui:matrix.org
[Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui-org?logo=github&style=flat-square
[Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui-org?logo=github&style=flat-square&color=1370D3
<!-- cargo-rdme end -->

View File

@@ -1,8 +1,7 @@
use criterion::{criterion_group, criterion_main, BatchSize, Bencher, BenchmarkId, Criterion};
use criterion::{criterion_group, criterion_main, BatchSize, Bencher, Criterion};
use ratatui::{
buffer::Buffer,
layout::Rect,
prelude::Alignment,
layout::{Alignment, Rect},
widgets::{
block::{Position, Title},
Block, Borders, Padding, Widget,
@@ -13,23 +12,23 @@ use ratatui::{
fn block(c: &mut Criterion) {
let mut group = c.benchmark_group("block");
for buffer_size in [
Rect::new(0, 0, 100, 50), // vertically split screen
Rect::new(0, 0, 200, 50), // 1080p fullscreen with medium font
Rect::new(0, 0, 256, 256), // Max sized area
for (width, height) in [
(100, 50), // vertically split screen
(200, 50), // 1080p fullscreen with medium font
(256, 256), // Max sized area
] {
let buffer_area = buffer_size.area();
let buffer_size = Rect::new(0, 0, width, height);
// Render an empty block
group.bench_with_input(
BenchmarkId::new("render_empty", buffer_area),
format!("render_empty/{width}x{height}"),
&Block::new(),
|b, block| render(b, block, buffer_size),
);
// Render with all features
group.bench_with_input(
BenchmarkId::new("render_all_feature", buffer_area),
format!("render_all_feature/{width}x{height}"),
&Block::new()
.borders(Borders::ALL)
.title("test title")

4
clippy.toml Normal file
View File

@@ -0,0 +1,4 @@
# https://rust-lang.github.io/rust-clippy/master/index.html#/multiple_crate_versions
# ratatui -> bitflags v2.3
# termwiz -> wezterm-blob-leases -> mac_address -> nix -> bitflags v1.3.2
allowed-duplicate-crates = ["bitflags"]

View File

@@ -55,22 +55,21 @@ pub struct Buffer {
impl Buffer {
/// Returns a Buffer with all cells set to the default one
#[must_use]
pub fn empty(area: Rect) -> Self {
let cell = Cell::default();
Self::filled(area, &cell)
Self::filled(area, &Cell::default())
}
/// Returns a Buffer with all cells initialized with the attributes of the given Cell
#[must_use]
pub fn filled(area: Rect, cell: &Cell) -> Self {
let size = area.area() as usize;
let mut content = Vec::with_capacity(size);
for _ in 0..size {
content.push(cell.clone());
}
let content = vec![cell.clone(); size];
Self { area, content }
}
/// Returns a Buffer containing the given lines
#[must_use]
pub fn with_lines<'a, Iter>(lines: Iter) -> Self
where
Iter: IntoIterator,
@@ -97,12 +96,14 @@ impl Buffer {
}
/// Returns a reference to Cell at the given coordinates
#[track_caller]
pub fn get(&self, x: u16, y: u16) -> &Cell {
let i = self.index_of(x, y);
&self.content[i]
}
/// Returns a mutable reference to Cell at the given coordinates
#[track_caller]
pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
let i = self.index_of(x, y);
&mut self.content[i]
@@ -134,6 +135,7 @@ impl Buffer {
/// // starts at (200, 100).
/// buffer.index_of(0, 0); // Panics
/// ```
#[track_caller]
pub fn index_of(&self, x: u16, y: u16) -> usize {
debug_assert!(
x >= self.area.left()

View File

@@ -66,7 +66,6 @@ use std::{
///
/// [ANSI color table]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Color {
/// Resets the foreground or background color
#[default]
@@ -141,14 +140,102 @@ impl Color {
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Color {
/// This utilises the [`Display`] implementation for serialization.
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Color {
/// This is used to deserialize a value into Color via serde.
///
/// This implementation uses the `FromStr` trait to deserialize strings, so named colours, RGB,
/// and indexed values are able to be deserialized. In addition, values that were produced by
/// the the older serialization implementation of Color are also able to be deserialized.
///
/// Prior to v0.26.0, Ratatui would be serialized using a map for indexed and RGB values, for
/// examples in json `{"Indexed": 10}` and `{"Rgb": [255, 0, 255]}` respectively. Now they are
/// serialized using the string representation of the index and the RGB hex value, for example
/// in json it would now be `"10"` and `"#FF00FF"` respectively.
///
/// See the [`Color`] documentation for more information on color names.
///
/// # Examples
///
/// ```
/// use ratatui::prelude::*;
///
/// #[derive(Debug, serde::Deserialize)]
/// struct Theme {
/// color: Color,
/// }
///
/// # fn get_theme() -> Result<(), serde_json::Error> {
/// let theme: Theme = serde_json::from_str(r#"{"color": "bright-white"}"#)?;
/// assert_eq!(theme.color, Color::White);
///
/// let theme: Theme = serde_json::from_str(r##"{"color": "#00FF00"}"##)?;
/// assert_eq!(theme.color, Color::Rgb(0, 255, 0));
///
/// let theme: Theme = serde_json::from_str(r#"{"color": "42"}"#)?;
/// assert_eq!(theme.color, Color::Indexed(42));
///
/// let err = serde_json::from_str::<Theme>(r#"{"color": "invalid"}"#).unwrap_err();
/// assert!(err.is_data());
/// assert_eq!(
/// err.to_string(),
/// "Failed to parse Colors at line 1 column 20"
/// );
///
/// // Deserializing from the previous serialization implementation
/// let theme: Theme = serde_json::from_str(r#"{"color": {"Rgb":[255,0,255]}}"#)?;
/// assert_eq!(theme.color, Color::Rgb(255, 0, 255));
///
/// let theme: Theme = serde_json::from_str(r#"{"color": {"Indexed":10}}"#)?;
/// assert_eq!(theme.color, Color::Indexed(10));
/// # Ok(())
/// # }
/// ```
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(serde::de::Error::custom)
/// Colors are currently serialized with the `Display` implementation, so
/// RGB values are serialized via hex, for example "#FFFFFF".
///
/// Previously they were serialized using serde derive, which encoded
/// RGB values as a map, for example { "rgb": [255, 255, 255] }.
///
/// The deserialization implementation utilises a `Helper` struct
/// to be able to support both formats for backwards compatibility.
#[derive(serde::Deserialize)]
enum ColorWrapper {
Rgb(u8, u8, u8),
Indexed(u8),
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum ColorFormat {
V2(String),
V1(ColorWrapper),
}
let multi_type = ColorFormat::deserialize(deserializer)
.map_err(|err| serde::de::Error::custom(format!("Failed to parse Colors: {err}")))?;
match multi_type {
ColorFormat::V2(s) => FromStr::from_str(&s).map_err(serde::de::Error::custom),
ColorFormat::V1(color_wrapper) => match color_wrapper {
ColorWrapper::Rgb(red, green, blue) => Ok(Self::Rgb(red, green, blue)),
ColorWrapper::Indexed(index) => Ok(Self::Indexed(index)),
},
}
}
}
@@ -579,4 +666,42 @@ mod tests {
Color::deserialize("#00000000".into_deserializer());
assert!(color.is_err());
}
#[cfg(feature = "serde")]
#[test]
fn serialize_then_deserialize() -> Result<(), serde_json::Error> {
let json_rgb = serde_json::to_string(&Color::Rgb(255, 0, 255))?;
assert_eq!(json_rgb, r##""#FF00FF""##);
assert_eq!(
serde_json::from_str::<Color>(&json_rgb)?,
Color::Rgb(255, 0, 255)
);
let json_white = serde_json::to_string(&Color::White)?;
assert_eq!(json_white, r#""White""#);
let json_indexed = serde_json::to_string(&Color::Indexed(10))?;
assert_eq!(json_indexed, r#""10""#);
assert_eq!(
serde_json::from_str::<Color>(&json_indexed)?,
Color::Indexed(10)
);
Ok(())
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_with_previous_format() -> Result<(), serde_json::Error> {
assert_eq!(Color::White, serde_json::from_str::<Color>("\"White\"")?);
assert_eq!(
Color::Rgb(255, 0, 255),
serde_json::from_str::<Color>(r#"{"Rgb":[255,0,255]}"#)?
);
assert_eq!(
Color::Indexed(10),
serde_json::from_str::<Color>(r#"{"Indexed":10}"#)?
);
Ok(())
}
}

View File

@@ -28,14 +28,14 @@ pub struct Monthly<'a, DS: DateStyler> {
impl<'a, DS: DateStyler> Monthly<'a, DS> {
/// Construct a calendar for the `display_date` and highlight the `events`
pub fn new(display_date: Date, events: DS) -> Self {
pub const fn new(display_date: Date, events: DS) -> Self {
Self {
display_date,
events,
show_surrounding: None,
show_weekday: None,
show_month: None,
default_style: Style::default(),
default_style: Style::new(),
block: None,
}
}
@@ -90,10 +90,10 @@ impl<'a, DS: DateStyler> Monthly<'a, DS> {
}
/// Return a style with only the background from the default style
fn default_bg(&self) -> Style {
const fn default_bg(&self) -> Style {
match self.default_style.bg {
None => Style::default(),
Some(c) => Style::default().bg(c),
None => Style::new(),
Some(c) => Style::new().bg(c),
}
}
@@ -164,7 +164,7 @@ impl<DS: DateStyler> Monthly<'_, DS> {
let mut y = days_area.y;
// go through all the weeks containing a day in the target month.
while curr_day.month() as u8 != self.display_date.month().next() as u8 {
while curr_day.month() != self.display_date.month().next() {
let mut spans = Vec::with_capacity(14);
for i in 0..7 {
// Draw the gutter. Do it here so we can avoid worrying about

View File

@@ -27,7 +27,7 @@ pub enum MapResolution {
}
impl MapResolution {
fn data(self) -> &'static [(f64, f64)] {
const fn data(self) -> &'static [(f64, f64)] {
match self {
Self::Low => &WORLD_LOW_RESOLUTION,
Self::High => &WORLD_HIGH_RESOLUTION,

View File

@@ -1,5 +1,5 @@
/// [Source data](http://www.gnuplotting.org/plotting-the-world-revisited)
pub static WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
pub const WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
(-163.7128, -78.5956),
(-163.1058, -78.2233),
(-161.2451, -78.3801),
@@ -5127,7 +5127,7 @@ pub static WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
(180.0, -84.71338),
];
pub static WORLD_LOW_RESOLUTION: [(f64, f64); 1166] = [
pub const WORLD_LOW_RESOLUTION: [(f64, f64); 1166] = [
(-92.32, 48.24),
(-88.13, 48.92),
(-83.11, 46.27),

View File

@@ -60,8 +60,11 @@ impl TableState {
/// # use ratatui::{prelude::*, widgets::*};
/// let state = TableState::new();
/// ```
pub fn new() -> Self {
Self::default()
pub const fn new() -> Self {
Self {
offset: 0,
selected: None,
}
}
/// Sets the index of the first row to be displayed