mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-28 05:21:23 +00:00
feat: add ratatui-macros crate
This crate previously lived at https://github.com/ratatui/ratatui-macros and is now part of the ratatui mono-repo.
This commit is contained in:
commit
d4415204e1
73
Cargo.lock
generated
73
Cargo.lock
generated
@ -883,6 +883,12 @@ dependencies = [
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dissimilar"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d"
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.10"
|
||||
@ -2536,6 +2542,7 @@ dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"ratatui-core",
|
||||
"ratatui-crossterm",
|
||||
"ratatui-macros",
|
||||
"ratatui-termion",
|
||||
"ratatui-termwiz",
|
||||
"ratatui-widgets",
|
||||
@ -2587,6 +2594,15 @@ dependencies = [
|
||||
"rstest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui-macros"
|
||||
version = "0.7.0-alpha.0"
|
||||
dependencies = [
|
||||
"ratatui-core",
|
||||
"ratatui-widgets",
|
||||
"trybuild",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui-termion"
|
||||
version = "0.1.0-alpha.1"
|
||||
@ -2996,6 +3012,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
@ -3250,6 +3275,12 @@ dependencies = [
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-triple"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.16.0"
|
||||
@ -3264,6 +3295,15 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminfo"
|
||||
version = "0.8.0"
|
||||
@ -3505,11 +3545,26 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
@ -3518,6 +3573,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
@ -3660,6 +3717,22 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "trybuild"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b812699e0c4f813b872b373a4471717d9eb550da14b311058a4d9cf4173cbca6"
|
||||
dependencies = [
|
||||
"dissimilar",
|
||||
"glob",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"target-triple",
|
||||
"termcolor",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
|
@ -7,6 +7,7 @@ default-members = [
|
||||
"ratatui-crossterm",
|
||||
# this is not included as it doesn't compile on windows
|
||||
# "ratatui-termion",
|
||||
"ratatui-macros",
|
||||
"ratatui-termwiz",
|
||||
"ratatui-widgets",
|
||||
"examples/apps/*",
|
||||
@ -37,6 +38,7 @@ pretty_assertions = "1.4.1"
|
||||
ratatui = { path = "ratatui", version = "0.30.0-alpha.1" }
|
||||
ratatui-core = { path = "ratatui-core", version = "0.1.0-alpha.2" }
|
||||
ratatui-crossterm = { path = "ratatui-crossterm", version = "0.1.0-alpha.1" }
|
||||
ratatui-macros = { path = "ratatui-macros", version = "0.7.0-alpha.0" }
|
||||
ratatui-termion = { path = "ratatui-termion", version = "0.1.0-alpha.1" }
|
||||
ratatui-termwiz = { path = "ratatui-termwiz", version = "0.1.0-alpha.1" }
|
||||
ratatui-widgets = { path = "ratatui-widgets", version = "0.3.0-alpha.1" }
|
||||
|
2
ratatui-macros/.rustfmt.toml
Normal file
2
ratatui-macros/.rustfmt.toml
Normal file
@ -0,0 +1,2 @@
|
||||
format_macro_bodies = true
|
||||
format_macro_matchers = true
|
117
ratatui-macros/CHANGELOG.md
Normal file
117
ratatui-macros/CHANGELOG.md
Normal file
@ -0,0 +1,117 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.6.0](https://github.com/ratatui/ratatui-macros/compare/v0.5.0...v0.6.0) - 2024-10-21
|
||||
|
||||
### Other
|
||||
|
||||
- *(deps)* bump the cargo-dependencies group with 2 updates ([#73](https://github.com/ratatui/ratatui-macros/pull/73))
|
||||
- *(deps)* bump ratatui from 0.28.0 to 0.28.1 in the cargo-dependencies group ([#70](https://github.com/ratatui/ratatui-macros/pull/70))
|
||||
|
||||
## [0.5.0] - 2024-08-12
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Bump version to 0.5.0
|
||||
|
||||
## [0.4.4](https://github.com/ratatui-org/ratatui-macros/compare/v0.4.3...v0.4.4) - 2024-08-09
|
||||
|
||||
### Other
|
||||
|
||||
- *(deps)* bump ratatui to 0.28.0 ([#66](https://github.com/ratatui-org/ratatui-macros/pull/66))
|
||||
- *(deps)* bump trybuild from 1.0.98 to 1.0.99 in the cargo-dependencies group ([#65](https://github.com/ratatui-org/ratatui-macros/pull/65))
|
||||
- *(deps)* bump trybuild from 1.0.97 to 1.0.98 in the cargo-dependencies group ([#62](https://github.com/ratatui-org/ratatui-macros/pull/62))
|
||||
|
||||
## [0.4.3](https://github.com/ratatui-org/ratatui-macros/compare/v0.4.2...v0.4.3) - 2024-07-22
|
||||
|
||||
### Added
|
||||
|
||||
- allow span macro to accept a bare expression ([#61](https://github.com/ratatui-org/ratatui-macros/pull/61))
|
||||
|
||||
### Other
|
||||
|
||||
- *(deps)* bump trybuild from 1.0.96 to 1.0.97 in the cargo-dependencies group ([#59](https://github.com/ratatui-org/ratatui-macros/pull/59))
|
||||
|
||||
## [0.4.2](https://github.com/ratatui-org/ratatui-macros/compare/v0.4.1...v0.4.2) - 2024-06-29
|
||||
|
||||
### Added
|
||||
|
||||
- Use `::ratatui` instead of `ratatui` ([#54](https://github.com/ratatui-org/ratatui-macros/pull/54))
|
||||
- Add row! macro ([#52](https://github.com/ratatui-org/ratatui-macros/pull/52))
|
||||
|
||||
### Other
|
||||
|
||||
- Update README with row! documentation ([#56](https://github.com/ratatui-org/ratatui-macros/pull/56))
|
||||
- Make doc examples shorter by removing duplicate imports ([#55](https://github.com/ratatui-org/ratatui-macros/pull/55))
|
||||
|
||||
## [0.4.1](https://github.com/ratatui-org/ratatui-macros/compare/v0.4.0...v0.4.1) - 2024-06-24
|
||||
|
||||
### Other
|
||||
|
||||
- *(deps)* bump ratatui from 0.26.3 to 0.27.0 in the cargo-dependencies group ([#51](https://github.com/ratatui-org/ratatui-macros/pull/51))
|
||||
- Update dependabot.yml to group dependencies ([#50](https://github.com/ratatui-org/ratatui-macros/pull/50))
|
||||
- *(deps)* bump ratatui from 0.26.2 to 0.26.3 ([#48](https://github.com/ratatui-org/ratatui-macros/pull/48))
|
||||
- *(deps)* bump trybuild from 1.0.95 to 1.0.96 ([#47](https://github.com/ratatui-org/ratatui-macros/pull/47))
|
||||
|
||||
## [0.4.0](https://github.com/ratatui-org/ratatui-macros/compare/v0.3.1...v0.4.0) - 2024-05-15
|
||||
|
||||
### Added
|
||||
|
||||
- *(layout)* [**breaking**] Use `*=` instead of `=*` ([#45](https://github.com/ratatui-org/ratatui-macros/pull/45))
|
||||
|
||||
## [0.3.1](https://github.com/ratatui-org/ratatui-macros/compare/v0.3.0...v0.3.1) - 2024-05-13
|
||||
|
||||
### Added
|
||||
|
||||
- Better error messages for `span!` macro ([#43](https://github.com/ratatui-org/ratatui-macros/pull/43))
|
||||
|
||||
### Fixed
|
||||
|
||||
- downgrade ratatui to 0.26.2 ([#41](https://github.com/ratatui-org/ratatui-macros/pull/41))
|
||||
|
||||
### Other
|
||||
|
||||
- Update authors to ratatui developers ([#44](https://github.com/ratatui-org/ratatui-macros/pull/44))
|
||||
|
||||
## [0.3.0](https://github.com/ratatui-org/ratatui-macros/compare/v0.2.4...v0.3.0) - 2024-05-09
|
||||
|
||||
### Added
|
||||
|
||||
- Use release-plz ([#38](https://github.com/ratatui-org/ratatui-macros/pull/38))
|
||||
- Add text! macro ([#36](https://github.com/ratatui-org/ratatui-macros/pull/36))
|
||||
- Add fill constraint ([#34](https://github.com/ratatui-org/ratatui-macros/pull/34))
|
||||
- [**breaking**] Remove color `palette!` macro ([#32](https://github.com/ratatui-org/ratatui-macros/pull/32))
|
||||
- Replace raw! and styled! with span! macro ([#30](https://github.com/ratatui-org/ratatui-macros/pull/30))
|
||||
- Add `line!` attribute macro ([#29](https://github.com/ratatui-org/ratatui-macros/pull/29))
|
||||
- *(text)* add raw! and styled! macros ([#4](https://github.com/ratatui-org/ratatui-macros/pull/4))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Update repo url in Cargo.toml ([#39](https://github.com/ratatui-org/ratatui-macros/pull/39))
|
||||
|
||||
### Other
|
||||
|
||||
- Use `.areas(area)` instead of `.split(area).to_vec().try_into().unwrap()` ([#37](https://github.com/ratatui-org/ratatui-macros/pull/37))
|
||||
- Update README.md with short description of span and line macros ([#33](https://github.com/ratatui-org/ratatui-macros/pull/33))
|
||||
- format using cargo +nightly fmt ([#31](https://github.com/ratatui-org/ratatui-macros/pull/31))
|
||||
- *(deps)* bump ratatui from 0.27.0-alpha.3 to 0.27.0-alpha.5 ([#27](https://github.com/ratatui-org/ratatui-macros/pull/27))
|
||||
- *(deps)* bump trybuild from 1.0.91 to 1.0.93 ([#28](https://github.com/ratatui-org/ratatui-macros/pull/28))
|
||||
- *(deps)* bump ratatui from 0.27.0-alpha.2 to 0.27.0-alpha.3 ([#24](https://github.com/ratatui-org/ratatui-macros/pull/24))
|
||||
- *(deps)* bump trybuild from 1.0.90 to 1.0.91 ([#23](https://github.com/ratatui-org/ratatui-macros/pull/23))
|
||||
- *(deps)* bump trybuild from 1.0.88 to 1.0.90 ([#20](https://github.com/ratatui-org/ratatui-macros/pull/20))
|
||||
- *(deps)* bump ratatui from 0.27.0-alpha.0 to 0.27.0-alpha.2 ([#22](https://github.com/ratatui-org/ratatui-macros/pull/22))
|
||||
- *(deps)* bump mio from 0.8.10 to 0.8.11 ([#18](https://github.com/ratatui-org/ratatui-macros/pull/18))
|
||||
- *(deps)* bump ratatui from 0.26.0-alpha.1 to 0.27.0-alpha.0 ([#19](https://github.com/ratatui-org/ratatui-macros/pull/19))
|
||||
- add cargo husky pre-commit hook ([#8](https://github.com/ratatui-org/ratatui-macros/pull/8))
|
||||
- Create dependabot.yml ([#7](https://github.com/ratatui-org/ratatui-macros/pull/7))
|
||||
- use rust cache to cache deps ([#6](https://github.com/ratatui-org/ratatui-macros/pull/6))
|
||||
- readme tweaks ([#5](https://github.com/ratatui-org/ratatui-macros/pull/5))
|
||||
- Update README.md
|
||||
- Update README.md
|
||||
- Update README.md
|
||||
- Update README.md
|
||||
- Add link to ratatui
|
18
ratatui-macros/Cargo.toml
Normal file
18
ratatui-macros/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "ratatui-macros"
|
||||
version = "0.7.0-alpha.0"
|
||||
edition = "2021"
|
||||
authors = ["The Ratatui Developers"]
|
||||
description = "Macros for Ratatui"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/ratatui/ratatui"
|
||||
documentation = "https://docs.rs/ratatui-macros"
|
||||
keywords = ["ratatui", "macros", "tui", "ui"]
|
||||
categories = ["command-line-interface"]
|
||||
|
||||
[dependencies]
|
||||
ratatui-core.workspace = true
|
||||
ratatui-widgets.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = { version = "1.0.103", features = ["diff"] }
|
20
ratatui-macros/LICENSE
Normal file
20
ratatui-macros/LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2024 Dheepak Krishnamurthy
|
||||
Copyright (c) 2025 The Ratatui Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
200
ratatui-macros/README.md
Normal file
200
ratatui-macros/README.md
Normal file
@ -0,0 +1,200 @@
|
||||
# Ratatui Macros
|
||||
|
||||
[![Crates.io badge]][ratatui_macros crate] [![License badge]](./LICENSE)
|
||||
[![Docs.rs badge]][API Docs] [![CI Badge]][CI Status]
|
||||
[![Crate Downloads badge]][ratatui_macros crate]
|
||||
|
||||
`ratatui-macros` is a Rust crate that provides easy-to-use macros for simplifying boilerplate
|
||||
associated with creating UI using [Ratatui].
|
||||
|
||||
This is an experimental playground for us to explore macros that would be useful to have in Ratatui
|
||||
proper.
|
||||
|
||||
## Features
|
||||
|
||||
- Constraint-based Layouts: Easily define layout constraints such as fixed, percentage, minimum, and
|
||||
maximum sizes, as well as ratios.
|
||||
- Directional Layouts: Specify layouts as either horizontal or vertical with simple macro commands.
|
||||
- Span and Line macros: Make it easier to create spans and lines with styling.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To use `ratatui-macros` in your Rust project, add it as a dependency in your `Cargo.toml`:
|
||||
|
||||
```shell
|
||||
cargo add ratatui-macros
|
||||
```
|
||||
|
||||
Then, import the macros in your Rust file:
|
||||
|
||||
```rust
|
||||
use ratatui_macros::{
|
||||
constraint,
|
||||
constraints,
|
||||
horizontal,
|
||||
vertical,
|
||||
span,
|
||||
line,
|
||||
};
|
||||
```
|
||||
|
||||
### Layout
|
||||
|
||||
If you are new to Ratatui, check out the [Layout concepts] article on the Ratatui website before proceeding.
|
||||
|
||||
Use the `constraints!` macro to define layout constraints:
|
||||
|
||||
```rust
|
||||
# use ratatui_core::layout::Constraint;
|
||||
use ratatui_macros::constraints;
|
||||
|
||||
assert_eq!(
|
||||
constraints![==50, ==30%, >=3, <=1, ==1/2, *=1],
|
||||
[
|
||||
Constraint::Length(50),
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Min(3),
|
||||
Constraint::Max(1),
|
||||
Constraint::Ratio(1, 2),
|
||||
Constraint::Fill(1),
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
```rust
|
||||
# use ratatui_core::layout::Constraint;
|
||||
use ratatui_macros::constraints;
|
||||
|
||||
assert_eq!(
|
||||
constraints![==1/4; 4],
|
||||
[
|
||||
Constraint::Ratio(1, 4),
|
||||
Constraint::Ratio(1, 4),
|
||||
Constraint::Ratio(1, 4),
|
||||
Constraint::Ratio(1, 4),
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
Use the `constraint!` macro to define individual constraints:
|
||||
|
||||
```rust
|
||||
# use ratatui_core::layout::Constraint;
|
||||
use ratatui_macros::constraint;
|
||||
|
||||
assert_eq!(
|
||||
constraint!(==50),
|
||||
Constraint::Length(50),
|
||||
)
|
||||
```
|
||||
|
||||
Create vertical and horizontal layouts using the `vertical!` and `horizontal!` macros:
|
||||
|
||||
```rust
|
||||
# use ratatui_core::layout::Rect;
|
||||
use ratatui_macros::{vertical, horizontal};
|
||||
|
||||
let area = Rect { x: 0, y: 0, width: 10, height: 10 };
|
||||
|
||||
let [main, bottom] = vertical![==100%, >=3].areas(area);
|
||||
|
||||
assert_eq!(bottom.y, 7);
|
||||
assert_eq!(bottom.height, 3);
|
||||
|
||||
let [left, main, right] = horizontal![>=3, ==100%, >=3].areas(area);
|
||||
|
||||
assert_eq!(left.width, 3);
|
||||
assert_eq!(right.width, 3);
|
||||
```
|
||||
|
||||
## Spans
|
||||
|
||||
The `span!` macro create raw and styled `Span`s. They each take a format string and arguments.
|
||||
`span!` accepts as the first parameter any value that can be converted to a `Style` followed by a
|
||||
`;` followed by the format string and arguments.
|
||||
|
||||
```rust
|
||||
# use ratatui_core::style::{Color, Modifier, Style, Stylize};
|
||||
use ratatui_macros::span;
|
||||
|
||||
let name = "world!";
|
||||
let raw_greeting = span!("hello {name}");
|
||||
let styled_greeting = span!(Style::new().green(); "hello {name}");
|
||||
let styled_greeting = span!(Color::Green; "hello {name}");
|
||||
let styled_greeting = span!(Modifier::BOLD; "hello {name}");
|
||||
```
|
||||
|
||||
## Line
|
||||
|
||||
The `line!` macro creates a `Line` that contains a sequence of spans. It is similar to the `vec!`
|
||||
macro.
|
||||
|
||||
```rust
|
||||
use ratatui_macros::line;
|
||||
|
||||
let name = "world!";
|
||||
let line = line!["hello", format!("{name}")];
|
||||
let line = line!["bye"; 2];
|
||||
```
|
||||
|
||||
## Text
|
||||
|
||||
The `text!` macro creates a `Text` that contains a sequence of lines. It is similar to the `vec!`
|
||||
macro.
|
||||
|
||||
```rust
|
||||
use ratatui_macros::{span, line, text};
|
||||
|
||||
let name = "world!";
|
||||
let text = text!["hello", format!("{name}")];
|
||||
let text = text!["bye"; 2];
|
||||
```
|
||||
|
||||
It is even possible to use `span!` and `line!` in the `text!` macro:
|
||||
|
||||
```rust
|
||||
# use ratatui_core::style::{Modifier, Stylize};
|
||||
use ratatui_macros::{span, line, text};
|
||||
let name = "Bye!!!";
|
||||
let text = text![line!["hello", "world".bold()], span!(Modifier::BOLD; "{name}")];
|
||||
```
|
||||
|
||||
## Row
|
||||
|
||||
The `row!` macro creates a `Row` that contains a sequence of `Cell`. It is similar to the `vec!`
|
||||
macro. A `Row` represents a sequence of `Cell`s in a single row of a table.
|
||||
|
||||
```rust
|
||||
use ratatui_macros::row;
|
||||
|
||||
let rows = [
|
||||
row!["hello", "world"],
|
||||
row!["goodbye", "world"],
|
||||
];
|
||||
```
|
||||
|
||||
It is even possible to use `span!`, `line!` and `text!` in the `row!` macro:
|
||||
|
||||
```rust
|
||||
# use ratatui_core::style::{Modifier, Stylize};
|
||||
use ratatui_macros::{span, line, text, row};
|
||||
let name = "Bye!!!";
|
||||
let text = row![text![line!["hello", "world".bold()]], span!(Modifier::BOLD; "{name}")];
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions to `ratatui-macros` are welcome! Whether it's submitting a bug report, a feature
|
||||
request, or a pull request, all forms of contributions are valued and appreciated.
|
||||
|
||||
[Crates.io badge]: https://img.shields.io/crates/v/ratatui-macros?logo=rust&style=flat-square
|
||||
[License badge]: https://img.shields.io/crates/l/ratatui-macros
|
||||
[CI Badge]:
|
||||
https://img.shields.io/github/actions/workflow/status/ratatui-org/ratatui-macros/ci.yml?logo=github&style=flat-square
|
||||
[Docs.rs badge]: https://img.shields.io/docsrs/ratatui-macros?logo=rust&style=flat-square
|
||||
[Crate Downloads badge]: https://img.shields.io/crates/d/ratatui-macros?logo=rust&style=flat-square
|
||||
[ratatui_macros crate]: https://crates.io/crates/ratatui-macros
|
||||
[API Docs]: https://docs.rs/ratatui-macros
|
||||
[CI Status]: https://github.com/kdheepak/ratatui-macros/actions
|
||||
[Ratatui]: https://github.com/ratatui-org/ratatui
|
||||
[Layout concepts]: https://ratatui.rs/concepts/layout
|
197
ratatui-macros/src/layout.rs
Normal file
197
ratatui-macros/src/layout.rs
Normal file
@ -0,0 +1,197 @@
|
||||
/// Creates a single constraint.
|
||||
///
|
||||
/// If creating an array of constraints, you probably want to use
|
||||
/// [`constraints!`] instead.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use ratatui_core::layout::Constraint;
|
||||
/// use ratatui_macros::constraint;
|
||||
/// assert_eq!(constraint!(>= 3 + 4), Constraint::Min(7));
|
||||
/// assert_eq!(constraint!(<= 3 + 4), Constraint::Max(7));
|
||||
/// assert_eq!(constraint!(== 1 / 3), Constraint::Ratio(1, 3));
|
||||
/// assert_eq!(constraint!(== 3), Constraint::Length(3));
|
||||
/// assert_eq!(constraint!(== 10 %), Constraint::Percentage(10));
|
||||
/// assert_eq!(constraint!(*= 1), Constraint::Fill(1));
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! constraint {
|
||||
(== $token:tt %) => {
|
||||
$crate::ratatui_core::layout::Constraint::Percentage($token)
|
||||
};
|
||||
(>= $expr:expr) => {
|
||||
$crate::ratatui_core::layout::Constraint::Min($expr)
|
||||
};
|
||||
(<= $expr:expr) => {
|
||||
$crate::ratatui_core::layout::Constraint::Max($expr)
|
||||
};
|
||||
(== $num:tt / $denom:tt) => {
|
||||
$crate::ratatui_core::layout::Constraint::Ratio($num as u32, $denom as u32)
|
||||
};
|
||||
(== $expr:expr) => {
|
||||
$crate::ratatui_core::layout::Constraint::Length($expr)
|
||||
};
|
||||
(*= $expr:expr) => {
|
||||
$crate::ratatui_core::layout::Constraint::Fill($expr)
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates an array of constraints.
|
||||
///
|
||||
/// See [`constraint!`] for more information.
|
||||
///
|
||||
/// If you want to solve the constraints, see
|
||||
/// [`vertical!`] and [`horizontal!`] macros.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use ratatui_macros::constraints;
|
||||
/// assert_eq!(constraints![==5, ==30%, >=3, <=1, ==1/2].len(), 5);
|
||||
/// assert_eq!(constraints![==5; 5].len(), 5);
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_core::layout::Constraint;
|
||||
/// # use ratatui_macros::constraints;
|
||||
/// assert_eq!(
|
||||
/// constraints![==50, ==30%, >=3, <=1, ==1/2, *=1],
|
||||
/// [
|
||||
/// Constraint::Length(50),
|
||||
/// Constraint::Percentage(30),
|
||||
/// Constraint::Min(3),
|
||||
/// Constraint::Max(1),
|
||||
/// Constraint::Ratio(1, 2),
|
||||
/// Constraint::Fill(1),
|
||||
/// ]
|
||||
/// )
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! constraints {
|
||||
// Note: this implementation forgoes speed for the sake of simplicity. Adding variations of the
|
||||
// comma and semicolon rules for each constraint type would be faster, but would result in a lot
|
||||
// of duplicated code.
|
||||
|
||||
// Cannot start the constraints macro with a ,
|
||||
([ , $($rest:tt)* ] -> () []) => {
|
||||
compile_error!("No rules expected the token `,` while trying to match the end of the macro")
|
||||
};
|
||||
|
||||
// Comma finishes a constraint element, so parse it and continue.
|
||||
// When a comma is encountered, it marks the end of a constraint element, so this rule is responsible
|
||||
// for parsing the constraint expression up to the comma and continuing the parsing process.
|
||||
// It accumulated the $partial contains a Constraint and is parsed using a separate $crate::constraint! macro.
|
||||
// The constraint is then appended to the list of parsed constraints.
|
||||
//
|
||||
// [ , $($rest:tt)* ] -> In the rule matcher, this pattern matches a comma followed
|
||||
// by the rest of the tokens. The comma signals the end of
|
||||
// the current constraint element.
|
||||
// ($($partial:tt)*) -> In the rule matcher, this contains the partial tokens
|
||||
// accumulated so far for the current constraint element.
|
||||
// [$($parsed:tt)* ] -> This contains the constraints that have been successfully
|
||||
// parsed so far.
|
||||
// $crate::constraint!($($partial)*) -> This macro call parses and expands the accumulated
|
||||
// partial tokens into a single Constraint expression.
|
||||
// [$($parsed)* $crate::constraint!(...)] -> Appends the newly parsed constraint to the list of
|
||||
// already parsed constraints.
|
||||
([ , $($rest:tt)* ] -> ($($partial:tt)*) [ $($parsed:tt)* ]) => {
|
||||
$crate::constraints!([$($rest)*] -> () [$($parsed)* $crate::constraint!($($partial)*) ,])
|
||||
};
|
||||
|
||||
// Semicolon indicates that there's repetition. The trailing comma is required because the 'entrypoint'
|
||||
// rule adds a trailing comma.
|
||||
// This rule is triggered when a semicolon is encountered, indicating that there is repetition of
|
||||
// constraints. It handles the repetition logic by parsing the count and generating an array of
|
||||
// constraints using the $crate::constraint! macro.
|
||||
//
|
||||
// [ ; $count:expr , ] -> In the rule matcher, this pattern matches a semicolon
|
||||
// followed by an expression representing the count, and a
|
||||
// trailing comma.
|
||||
// ($($partial:tt)*) -> In the rule matcher, this contains the partial tokens
|
||||
// accumulated so far for the current constraint element.
|
||||
// This represents everything before the ;
|
||||
// [] -> There will be no existed parsed constraints when using ;
|
||||
// $crate::constraint!($($partial)*) -> This macro call parses and expands the accumulated
|
||||
// partial tokens into a single Constraint expression.
|
||||
// [$crate::constraint!(...) ; $count] -> Generates an array of constraints by repeating the
|
||||
// parsed constraint count number of times.
|
||||
([ ; $count:expr , ] -> ($($partial:tt)*) []) => {
|
||||
[$crate::constraint!($($partial)*); $count]
|
||||
};
|
||||
|
||||
// Pull the first token (which can't be a comma or semicolon) onto the accumulator.
|
||||
// if first token is a comma or semicolon, previous rules will match before this rule
|
||||
//
|
||||
// [ $head:tt $($rest:tt)* ] -> In the rule matcher, this pulls a single `head` token
|
||||
// out of the previous rest, and puts
|
||||
// the remaining into `rest`
|
||||
// [ $($rest)* ] -> This is what is fed back into the `constraints!` macro
|
||||
// as the first segment for the match rule
|
||||
//
|
||||
// ($($partial:tt)*) -> In the rule matcher, this contains previous partial
|
||||
// tokens that will make up a `Constraint` expression
|
||||
// ($($partial)* $head) -> This combines head with the previous partial tokens
|
||||
// i.e. this is the accumulated tokens
|
||||
//
|
||||
// [ $($parsed:tt)* ] -> In the rule matcher, this contains all parsed exprs
|
||||
// [$($parsed)* ] -> These are passed on to the next match untouched.
|
||||
([ $head:tt $($rest:tt)* ] -> ($($partial:tt)*) [ $($parsed:tt)* ]) => {
|
||||
$crate::constraints!([$($rest)*] -> ($($partial)* $head) [$($parsed)* ])
|
||||
};
|
||||
|
||||
// This rule is triggered when there are no more input tokens to process. It signals the end of the
|
||||
// macro invocation and outputs the parsed constraints as a final array.
|
||||
([$(,)?] -> () [ $( $parsed:tt )* ]) => {
|
||||
[$($parsed)*]
|
||||
};
|
||||
|
||||
// Entrypoint where there's no comma at the end.
|
||||
// We add a comma to make sure there's always a trailing comma.
|
||||
// Right-hand side will accumulate the actual `Constraint` literals.
|
||||
($( $constraint:tt )+) => {
|
||||
$crate::constraints!([ $($constraint)+ , ] -> () [])
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a vertical layout with specified constraints.
|
||||
///
|
||||
/// It accepts a series of constraints and applies them to create a vertical layout. The constraints
|
||||
/// can include fixed sizes, minimum and maximum sizes, percentages, and ratios.
|
||||
///
|
||||
/// See [`constraint!`] or [`constraints!`] for more information.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// // Vertical layout with a fixed size and a percentage constraint
|
||||
/// use ratatui_macros::vertical;
|
||||
/// vertical![== 50, == 30%];
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! vertical {
|
||||
($( $constraint:tt )+) => {
|
||||
$crate::ratatui_core::layout::Layout::vertical($crate::constraints!( $($constraint)+ ))
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a horizontal layout with specified constraints.
|
||||
///
|
||||
/// It takes a series of constraints and applies them to create a horizontal layout. The constraints
|
||||
/// can include fixed sizes, minimum and maximum sizes, percentages, and ratios.
|
||||
///
|
||||
/// See [`constraint!`] or [`constraints!`] for more information.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// // Horizontal layout with a ratio constraint and a minimum size constraint
|
||||
/// use ratatui_macros::horizontal;
|
||||
/// horizontal![== 1/3, >= 100];
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! horizontal {
|
||||
($( $constraint:tt )+) => {
|
||||
$crate::ratatui_core::layout::Layout::horizontal($crate::constraints!( $($constraint)+ ))
|
||||
};
|
||||
}
|
10
ratatui-macros/src/lib.rs
Normal file
10
ratatui-macros/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
||||
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
|
||||
|
||||
mod layout;
|
||||
mod line;
|
||||
mod row;
|
||||
mod span;
|
||||
mod text;
|
||||
|
||||
// Re-export the core crate to use the types in macros
|
||||
pub use ratatui_core;
|
97
ratatui-macros/src/line.rs
Normal file
97
ratatui-macros/src/line.rs
Normal file
@ -0,0 +1,97 @@
|
||||
/// A macro for creating a [`Line`] using vec! syntax.
|
||||
///
|
||||
/// `line!` is similar to the [`vec!`] macro, but it returns a [`Line`] instead of a `Vec`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// * Create a [`Line`] containing a vector of [`Span`]s:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_core::style::Stylize;
|
||||
/// use ratatui_macros::line;
|
||||
///
|
||||
/// let line = line!["hello", "world"];
|
||||
/// let line = line!["hello".red(), "world".red().bold()];
|
||||
/// ```
|
||||
///
|
||||
/// * Create a [`Line`] from a given [`Span`] repeated some amount of times:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_macros::line;
|
||||
/// let line = line!["hello"; 2];
|
||||
/// ```
|
||||
///
|
||||
/// * Use [`span!`] macro inside [`line!`] macro for formatting.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_core::style::Modifier;
|
||||
/// use ratatui_macros::{line, span};
|
||||
///
|
||||
/// let line = line![span!("hello {}", "world"), span!(Modifier::BOLD; "goodbye {}", "world")];
|
||||
/// ```
|
||||
///
|
||||
/// [`Line`]: crate::text::Line
|
||||
/// [`Span`]: crate::text::Span
|
||||
#[macro_export]
|
||||
macro_rules! line {
|
||||
() => {
|
||||
$crate::ratatui_core::text::Line::default()
|
||||
};
|
||||
($span:expr; $n:expr) => {
|
||||
$crate::ratatui_core::text::Line::from(vec![$span.into(); $n])
|
||||
};
|
||||
($($span:expr),+ $(,)?) => {{
|
||||
$crate::ratatui_core::text::Line::from(vec![
|
||||
$(
|
||||
$span.into(),
|
||||
)+
|
||||
])
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ratatui_core::text::{Line, Span};
|
||||
|
||||
#[test]
|
||||
fn line_literal() {
|
||||
let line = line!["hello", "world"];
|
||||
assert_eq!(line, Line::from(vec!["hello".into(), "world".into()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_raw_instead_of_literal() {
|
||||
let line = line![Span::raw("hello"), "world"];
|
||||
assert_eq!(line, Line::from(vec!["hello".into(), "world".into()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_vec_count_syntax() {
|
||||
let line = line!["hello"; 2];
|
||||
assert_eq!(line, Line::from(vec!["hello".into(), "hello".into()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_vec_count_syntax_with_span() {
|
||||
let line = line![crate::span!("hello"); 2];
|
||||
assert_eq!(line, Line::from(vec!["hello".into(), "hello".into()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_empty() {
|
||||
let line = line![];
|
||||
assert_eq!(line, Line::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_single_span() {
|
||||
let line = line![Span::raw("foo")];
|
||||
assert_eq!(line, Line::from(vec!["foo".into()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_repeated_span() {
|
||||
let line = line![Span::raw("foo"); 2];
|
||||
assert_eq!(line, Line::from(vec!["foo".into(), "foo".into()]));
|
||||
}
|
||||
}
|
139
ratatui-macros/src/row.rs
Normal file
139
ratatui-macros/src/row.rs
Normal file
@ -0,0 +1,139 @@
|
||||
/// A macro for creating a [`Row`] using vec! syntax.
|
||||
///
|
||||
/// `row!` is similar to the [`vec!`] macro, but it returns a [`Row`] instead of a `Vec`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// * Create a [`Row`] containing a vector of [`Cell`]s:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_core::style::Stylize;
|
||||
/// use ratatui_macros::row;
|
||||
///
|
||||
/// let row = row!["hello", "world"];
|
||||
/// let row = row!["hello".red(), "world".red().bold()];
|
||||
/// ```
|
||||
///
|
||||
/// * Create an empty [`Row`]:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_macros::row;
|
||||
/// let empty_row = row![];
|
||||
/// ```
|
||||
///
|
||||
/// * Create a [`Row`] from a given [`Cell`] repeated some amount of times:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_macros::row;
|
||||
/// let row = row!["hello"; 2];
|
||||
/// ```
|
||||
///
|
||||
/// * Use [`text!`], [`line!`] or [`span!`] macro inside [`row!`] macro.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_core::style::{Modifier};
|
||||
/// use ratatui_macros::{row, line, text, span};
|
||||
///
|
||||
/// let row = row![
|
||||
/// line!["hello", "world"], span!(Modifier::BOLD; "goodbye {}", "world"),
|
||||
/// text!["hello", "world"],
|
||||
/// ];
|
||||
/// ```
|
||||
///
|
||||
/// [`Row`]: crate::widgets::Row
|
||||
/// [`Cell`]: crate::widgets::Cell
|
||||
#[macro_export]
|
||||
macro_rules! row {
|
||||
() => {
|
||||
::ratatui_widgets::table::Row::default()
|
||||
};
|
||||
($cell:expr; $n:expr) => {
|
||||
::ratatui_widgets::table::Row::new(vec![::ratatui_widgets::table::Cell::from($cell); $n])
|
||||
};
|
||||
($($cell:expr),+ $(,)?) => {{
|
||||
::ratatui_widgets::table::Row::new(vec![
|
||||
$(
|
||||
::ratatui_widgets::table::Cell::from($cell),
|
||||
)+
|
||||
])
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use ratatui_core::text::Text;
|
||||
use ratatui_widgets::table::{Cell, Row};
|
||||
|
||||
#[test]
|
||||
fn row_literal() {
|
||||
let row = row!["hello", "world"];
|
||||
assert_eq!(
|
||||
row,
|
||||
Row::new(vec![Cell::from("hello"), Cell::from("world")])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_empty() {
|
||||
let row = row![];
|
||||
assert_eq!(row, Row::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_single_cell() {
|
||||
let row = row![Cell::from("foo")];
|
||||
assert_eq!(row, Row::new(vec![Cell::from("foo")]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_repeated_cell() {
|
||||
let row = row![Cell::from("foo"); 2];
|
||||
assert_eq!(row, Row::new(vec![Cell::from("foo"), Cell::from("foo")]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_explicit_use_of_span_and_line() {
|
||||
let row = row![crate::line!("hello"), crate::span!["world"]];
|
||||
assert_eq!(
|
||||
row,
|
||||
Row::new(vec![Cell::from("hello"), Cell::from("world")])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_vec_count_syntax() {
|
||||
let row = row!["hello"; 2];
|
||||
assert_eq!(
|
||||
row,
|
||||
Row::new(vec![Cell::from("hello"), Cell::from("hello")])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_rows() {
|
||||
use crate::text;
|
||||
let rows = [
|
||||
row!["Find File", text!["ctrl+f"].right_aligned()],
|
||||
row!["Open recent", text!["ctrl+r"].right_aligned()],
|
||||
row!["Open config", text!["ctrl+k"].right_aligned()],
|
||||
];
|
||||
assert_eq!(
|
||||
rows,
|
||||
[
|
||||
Row::new([
|
||||
Cell::from("Find File"),
|
||||
Cell::from(Text::raw("ctrl+f").right_aligned()),
|
||||
]),
|
||||
Row::new([
|
||||
Cell::from("Open recent"),
|
||||
Cell::from(Text::raw("ctrl+r").right_aligned()),
|
||||
]),
|
||||
Row::new([
|
||||
Cell::from("Open config"),
|
||||
Cell::from(Text::raw("ctrl+k").right_aligned()),
|
||||
]),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
245
ratatui-macros/src/span.rs
Normal file
245
ratatui-macros/src/span.rs
Normal file
@ -0,0 +1,245 @@
|
||||
/// A macro for creating a [`Span`] using formatting syntax.
|
||||
///
|
||||
/// `span!` is similar to the [`format!`] macro, but it returns a [`Span`] instead of a `String`. In
|
||||
/// addition, it also accepts an expression for the first argument, which will be converted to a
|
||||
/// string using the [`format!`] macro.
|
||||
///
|
||||
/// If semicolon follows the first argument, then the first argument is a [`Style`] and a styled
|
||||
/// [`Span`] will be created. Otherwise, the [`Span`] will be created as a raw span (i.e. with style
|
||||
/// set to `Style::default()`).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_core::style::{Color, Modifier, Style, Stylize};
|
||||
/// use ratatui_macros::span;
|
||||
///
|
||||
/// let content = "content";
|
||||
///
|
||||
/// // expression
|
||||
/// let span = span!(content);
|
||||
///
|
||||
/// // format string
|
||||
/// let span = span!("test content");
|
||||
/// let span = span!("test {}", "content");
|
||||
/// let span = span!("{} {}", "test", "content");
|
||||
/// let span = span!("test {content}");
|
||||
/// let span = span!("test {content}", content = "content");
|
||||
///
|
||||
/// // with format specifiers
|
||||
/// let span = span!("test {:4}", 123);
|
||||
/// let span = span!("test {:04}", 123);
|
||||
///
|
||||
/// let style = Style::new().green();
|
||||
///
|
||||
/// // styled expression
|
||||
/// let span = span!(style; content);
|
||||
///
|
||||
/// // styled format string
|
||||
/// let span = span!(style; "test content");
|
||||
/// let span = span!(style; "test {}", "content");
|
||||
/// let span = span!(style; "{} {}", "test", "content");
|
||||
/// let span = span!(style; "test {content}");
|
||||
/// let span = span!(style; "test {content}", content = "content");
|
||||
///
|
||||
/// // accepts any type that is convertible to Style
|
||||
/// let span = span!(Style::new().green(); "test {content}");
|
||||
/// let span = span!(Color::Green; "test {content}");
|
||||
/// let span = span!(Modifier::BOLD; "test {content}");
|
||||
///
|
||||
/// // with format specifiers
|
||||
/// let span = span!(style; "test {:4}", 123);
|
||||
/// let span = span!(style; "test {:04}", 123);
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The first parameter must be a formatting specifier followed by a comma OR anything that can be
|
||||
/// converted into a [`Style`] followed by a semicolon.
|
||||
///
|
||||
/// For example, the following will fail to compile:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use ratatui::prelude::*;
|
||||
/// # use ratatui_macros::span;
|
||||
/// let span = span!(Modifier::BOLD, "hello world");
|
||||
/// ```
|
||||
///
|
||||
/// But this will work:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_core::style::{Modifier};
|
||||
/// # use ratatui_macros::span;
|
||||
/// let span = span!(Modifier::BOLD; "hello world");
|
||||
/// ```
|
||||
///
|
||||
/// The following will fail to compile:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use ratatui::prelude::*;
|
||||
/// # use ratatui_macros::span;
|
||||
/// let span = span!("hello", "world");
|
||||
/// ```
|
||||
///
|
||||
/// But this will work:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_macros::span;
|
||||
/// let span = span!("hello {}", "world");
|
||||
/// ```
|
||||
///
|
||||
/// [`Color`]: crate::style::Color
|
||||
/// [`Style`]: crate::style::Style
|
||||
/// [`Span`]: crate::text::Span
|
||||
/// [`Style`]: crate::style::Style
|
||||
#[macro_export]
|
||||
macro_rules! span {
|
||||
($string:literal) => {
|
||||
$crate::ratatui_core::text::Span::raw(format!($string))
|
||||
};
|
||||
($string:literal, $($arg:tt)*) => {
|
||||
$crate::ratatui_core::text::Span::raw(format!($string, $($arg)*))
|
||||
};
|
||||
($expr:expr) => {
|
||||
$crate::ratatui_core::text::Span::raw(format!("{}", $expr))
|
||||
};
|
||||
($style:expr, $($arg:tt)*) => {
|
||||
compile_error!("first parameter must be a formatting specifier followed by a comma OR a `Style` followed by a semicolon")
|
||||
};
|
||||
($style:expr; $string:literal) => {
|
||||
$crate::ratatui_core::text::Span::styled(format!($string), $style)
|
||||
};
|
||||
($style:expr; $string:literal, $($arg:tt)*) => {
|
||||
$crate::ratatui_core::text::Span::styled(format!($string, $($arg)*), $style)
|
||||
};
|
||||
($style:expr; $expr:expr) => {
|
||||
$crate::ratatui_core::text::Span::styled(format!("{}", $expr), $style)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ratatui_core::{
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
text::Span,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn raw() {
|
||||
let test = "test";
|
||||
let content = "content";
|
||||
let number = 123;
|
||||
|
||||
// literal
|
||||
let span = span!("test content");
|
||||
assert_eq!(span, Span::raw("test content"));
|
||||
|
||||
// string
|
||||
let span = span!("test {}", "content");
|
||||
assert_eq!(span, Span::raw("test content"));
|
||||
|
||||
// string variable
|
||||
let span = span!("test {}", content);
|
||||
assert_eq!(span, Span::raw("test content"));
|
||||
|
||||
// string variable in the format string
|
||||
let span = span!("test {content}");
|
||||
assert_eq!(span, Span::raw("test content"));
|
||||
|
||||
// named variable
|
||||
let span = span!("test {content}", content = "content");
|
||||
assert_eq!(span, Span::raw("test content"));
|
||||
|
||||
// named variable pointing at a local variable
|
||||
let span = span!("test {content}", content = content);
|
||||
assert_eq!(span, Span::raw("test content"));
|
||||
|
||||
// two strings
|
||||
let span = span!("{} {}", "test", "content");
|
||||
assert_eq!(span, Span::raw("test content"));
|
||||
|
||||
// two string variables
|
||||
let span = span!("{test} {content}");
|
||||
assert_eq!(span, Span::raw("test content"));
|
||||
|
||||
// a number
|
||||
let span = span!("test {number}");
|
||||
assert_eq!(span, Span::raw("test 123"));
|
||||
|
||||
// a number with a format specifier
|
||||
let span = span!("test {number:04}");
|
||||
assert_eq!(span, Span::raw("test 0123"));
|
||||
|
||||
// directly pass a number expression
|
||||
let span = span!(number);
|
||||
assert_eq!(span, Span::raw("123"));
|
||||
|
||||
// directly pass a string expression
|
||||
let span = span!(test);
|
||||
assert_eq!(span, Span::raw("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn styled() {
|
||||
const STYLE: Style = Style::new().fg(Color::Green);
|
||||
|
||||
let test = "test";
|
||||
let content = "content";
|
||||
let number = 123;
|
||||
|
||||
// literal
|
||||
let span = span!(STYLE; "test content");
|
||||
assert_eq!(span, Span::styled("test content", STYLE));
|
||||
|
||||
// string
|
||||
let span = span!(STYLE; "test {}", "content");
|
||||
assert_eq!(span, Span::styled("test content", STYLE));
|
||||
|
||||
// string variable
|
||||
let span = span!(STYLE; "test {}", content);
|
||||
assert_eq!(span, Span::styled("test content", STYLE));
|
||||
|
||||
// string variable in the format string
|
||||
let span = span!(STYLE; "test {content}");
|
||||
assert_eq!(span, Span::styled("test content", STYLE));
|
||||
|
||||
// named variable
|
||||
let span = span!(STYLE; "test {content}", content = "content");
|
||||
assert_eq!(span, Span::styled("test content", STYLE));
|
||||
|
||||
// named variable pointing at a local variable
|
||||
let span = span!(STYLE; "test {content}", content = content);
|
||||
assert_eq!(span, Span::styled("test content", STYLE));
|
||||
|
||||
// two strings
|
||||
let span = span!(STYLE; "{} {}", "test", "content");
|
||||
assert_eq!(span, Span::styled("test content", STYLE));
|
||||
|
||||
// two string variables
|
||||
let span = span!(STYLE; "{test} {content}");
|
||||
assert_eq!(span, Span::styled("test content", STYLE));
|
||||
|
||||
// a number
|
||||
let span = span!(STYLE; "test {number}");
|
||||
assert_eq!(span, Span::styled("test 123", STYLE));
|
||||
|
||||
// a number with a format specifier
|
||||
let span = span!(STYLE; "test {number:04}");
|
||||
assert_eq!(span, Span::styled("test 0123", STYLE));
|
||||
|
||||
// accepts any type that is convertible to Style
|
||||
let span = span!(Color::Green; "test {content}");
|
||||
assert_eq!(span, Span::styled("test content", STYLE));
|
||||
|
||||
let span = span!(Modifier::BOLD; "test {content}");
|
||||
assert_eq!(span, Span::styled("test content", Style::new().bold()));
|
||||
|
||||
// directly pass a number expression
|
||||
let span = span!(STYLE; number);
|
||||
assert_eq!(span, Span::styled("123", STYLE));
|
||||
|
||||
// directly pass a string expression
|
||||
let span = span!(STYLE; test);
|
||||
assert_eq!(span, Span::styled("test", STYLE));
|
||||
}
|
||||
}
|
71
ratatui-macros/src/text.rs
Normal file
71
ratatui-macros/src/text.rs
Normal file
@ -0,0 +1,71 @@
|
||||
/// A macro for creating a [`Text`] using vec! syntax.
|
||||
///
|
||||
/// `text!` is similar to the [`vec!`] macro, but it returns a [`Text`] instead of a `Vec`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// * Create a [`Text`] containing a vector of [`Line`]s:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_core::style::Stylize;
|
||||
/// use ratatui_macros::text;
|
||||
///
|
||||
/// let text = text!["hello", "world"];
|
||||
/// let text = text!["hello".red(), "world".red().bold()];
|
||||
/// ```
|
||||
///
|
||||
/// * Create a [`text`] from a given [`Line`] repeated some amount of times:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_macros::text;
|
||||
/// let text = text!["hello"; 2];
|
||||
/// ```
|
||||
///
|
||||
/// * Use [`line!`] or [`span!`] macro inside [`text!`] macro.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ratatui_core::style::{Modifier};
|
||||
/// use ratatui_macros::{line, text, span};
|
||||
///
|
||||
/// let text = text![line!["hello", "world"], span!(Modifier::BOLD; "goodbye {}", "world")];
|
||||
/// ```
|
||||
///
|
||||
/// [`Text`]: crate::text::Text
|
||||
/// [`Line`]: crate::text::Line
|
||||
/// [`Span`]: crate::text::Span
|
||||
#[macro_export]
|
||||
macro_rules! text {
|
||||
() => {
|
||||
ratatui_core::text::Text::default()
|
||||
};
|
||||
($line:expr; $n:expr) => {
|
||||
ratatui_core::text::Text::from(vec![$line.into(); $n])
|
||||
};
|
||||
($($line:expr),+ $(,)?) => {{
|
||||
ratatui_core::text::Text::from(vec![
|
||||
$(
|
||||
$line.into(),
|
||||
)+
|
||||
])
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ratatui_core::text::Text;
|
||||
|
||||
#[test]
|
||||
fn text() {
|
||||
// literal
|
||||
let text = text!["hello", "world"];
|
||||
assert_eq!(text, Text::from(vec!["hello".into(), "world".into()]));
|
||||
|
||||
// explicit use of span and line
|
||||
let text = text![crate::line!("hello"), crate::span!["world"]];
|
||||
assert_eq!(text, Text::from(vec!["hello".into(), "world".into()]));
|
||||
|
||||
// vec count syntax
|
||||
let text = text!["hello"; 2];
|
||||
assert_eq!(text, Text::from(vec!["hello".into(), "hello".into()]));
|
||||
}
|
||||
}
|
104
ratatui-macros/tests/macros.rs
Normal file
104
ratatui-macros/tests/macros.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use ratatui_core::layout::{Constraint, Rect};
|
||||
use ratatui_macros::{constraints, horizontal, vertical};
|
||||
|
||||
#[test]
|
||||
fn layout_constraints_macro() {
|
||||
let rect = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
};
|
||||
|
||||
let [rect1, rect2] = vertical![==7, <=3].split(rect).to_vec().try_into().unwrap();
|
||||
assert_eq!(rect1, Rect::new(0, 0, 10, 7));
|
||||
assert_eq!(rect2, Rect::new(0, 7, 10, 3));
|
||||
|
||||
let [rect1, rect2] = horizontal![==7, <=3]
|
||||
.split(rect)
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
assert_eq!(rect1, Rect::new(0, 0, 7, 10));
|
||||
assert_eq!(rect2, Rect::new(7, 0, 3, 10));
|
||||
|
||||
let one = 1;
|
||||
let two = 2;
|
||||
let ten = 10;
|
||||
let zero = 0;
|
||||
let [a, b, c, d, e, f] = horizontal![==one, >=one, <=one, == 1 / two, == ten %, >=zero]
|
||||
.split(rect)
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(a, Rect::new(0, 0, 1, 10));
|
||||
assert_eq!(b, Rect::new(1, 0, 1, 10));
|
||||
assert_eq!(c, Rect::new(2, 0, 1, 10));
|
||||
assert_eq!(d, Rect::new(3, 0, 5, 10));
|
||||
assert_eq!(e, Rect::new(8, 0, 1, 10));
|
||||
assert_eq!(f, Rect::new(9, 0, 1, 10));
|
||||
|
||||
let one = 1;
|
||||
let two = 2;
|
||||
let ten = 10;
|
||||
let zero = 0;
|
||||
let [a, b, c, d, e, f] = horizontal![
|
||||
== one*one, // expr allowed here
|
||||
>= one+zero, // expr allowed here
|
||||
<= one-zero, // expr allowed here
|
||||
== 1/two, // only single token allowed in numerator and denominator
|
||||
== ten%, // only single token allowed before %
|
||||
>= zero // no trailing comma
|
||||
]
|
||||
.split(rect)
|
||||
.to_vec()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(a, Rect::new(0, 0, 1, 10));
|
||||
assert_eq!(b, Rect::new(1, 0, 1, 10));
|
||||
assert_eq!(c, Rect::new(2, 0, 1, 10));
|
||||
assert_eq!(d, Rect::new(3, 0, 5, 10));
|
||||
assert_eq!(e, Rect::new(8, 0, 1, 10));
|
||||
assert_eq!(f, Rect::new(9, 0, 1, 10));
|
||||
|
||||
let [a, b, c, d, e] = constraints![ >=0, ==1, <=5, ==10%, ==1/2 ];
|
||||
assert_eq!(a, Constraint::Min(0));
|
||||
assert_eq!(b, Constraint::Length(1));
|
||||
assert_eq!(c, Constraint::Max(5));
|
||||
assert_eq!(d, Constraint::Percentage(10));
|
||||
assert_eq!(e, Constraint::Ratio(1, 2));
|
||||
|
||||
let [a, b, c, d, e] = constraints![ >=0; 5 ];
|
||||
assert_eq!(a, Constraint::Min(0));
|
||||
assert_eq!(b, Constraint::Min(0));
|
||||
assert_eq!(c, Constraint::Min(0));
|
||||
assert_eq!(d, Constraint::Min(0));
|
||||
assert_eq!(e, Constraint::Min(0));
|
||||
|
||||
let [a, b, c, d, e] = constraints![ <=0; 5 ];
|
||||
assert_eq!(a, Constraint::Max(0));
|
||||
assert_eq!(b, Constraint::Max(0));
|
||||
assert_eq!(c, Constraint::Max(0));
|
||||
assert_eq!(d, Constraint::Max(0));
|
||||
assert_eq!(e, Constraint::Max(0));
|
||||
|
||||
let [a, b] = constraints![ ==0; 2 ];
|
||||
assert_eq!(a, Constraint::Length(0));
|
||||
assert_eq!(b, Constraint::Length(0));
|
||||
|
||||
let [a, b] = constraints![ == 50%; 2 ];
|
||||
assert_eq!(a, Constraint::Percentage(50));
|
||||
assert_eq!(b, Constraint::Percentage(50));
|
||||
|
||||
let [a, b] = constraints![ == 1/2; 2 ];
|
||||
assert_eq!(a, Constraint::Ratio(1, 2));
|
||||
assert_eq!(b, Constraint::Ratio(1, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/fails.rs");
|
||||
}
|
20
ratatui-macros/tests/ui/fails.rs
Normal file
20
ratatui-macros/tests/ui/fails.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use ratatui_core::layout::Constraint;
|
||||
use ratatui_macros::{constraints, span};
|
||||
|
||||
fn main() {
|
||||
constraints![,];
|
||||
|
||||
// TODO: Make this compiler error pass
|
||||
let [a, b] = constraints![
|
||||
== 1/2,
|
||||
== 2,
|
||||
];
|
||||
assert_eq!(a, Constraint::Ratio(1, 2));
|
||||
assert_eq!(b, Constraint::Length(2));
|
||||
|
||||
let [a, b, c] = constraints![ == 1, == 10%, == 2; 4];
|
||||
|
||||
let _ = span!(Modifier::BOLD, "hello world");
|
||||
|
||||
let _ = span!("hello", "hello world");
|
||||
}
|
58
ratatui-macros/tests/ui/fails.stderr
Normal file
58
ratatui-macros/tests/ui/fails.stderr
Normal file
@ -0,0 +1,58 @@
|
||||
error: No rules expected the token `,` while trying to match the end of the macro
|
||||
--> tests/ui/fails.rs:5:5
|
||||
|
|
||||
5 | constraints![,];
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the macro `$crate::constraints` which comes from the expansion of the macro `constraints` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unexpected end of macro invocation
|
||||
--> tests/ui/fails.rs:8:18
|
||||
|
|
||||
8 | let [a, b] = constraints![
|
||||
| __________________^
|
||||
9 | | == 1/2,
|
||||
10 | | == 2,
|
||||
11 | | ];
|
||||
| |_____^ missing tokens in macro arguments
|
||||
|
|
||||
note: while trying to match `==`
|
||||
--> src/layout.rs
|
||||
|
|
||||
| (== $token:tt %) => {
|
||||
| ^^
|
||||
= note: this error originates in the macro `$crate::constraints` which comes from the expansion of the macro `constraints` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: no rules expected `;`
|
||||
--> tests/ui/fails.rs:15:53
|
||||
|
|
||||
15 | let [a, b, c] = constraints![ == 1, == 10%, == 2; 4];
|
||||
| ^ no rules expected this token in macro call
|
||||
|
|
||||
note: while trying to match `%`
|
||||
--> src/layout.rs
|
||||
|
|
||||
| (== $token:tt %) => {
|
||||
| ^
|
||||
|
||||
error: first parameter must be a formatting specifier followed by a comma OR a `Style` followed by a semicolon
|
||||
--> tests/ui/fails.rs:17:13
|
||||
|
|
||||
17 | let _ = span!(Modifier::BOLD, "hello world");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the macro `span` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: argument never used
|
||||
--> tests/ui/fails.rs:19:28
|
||||
|
|
||||
19 | let _ = span!("hello", "hello world");
|
||||
| ------- ^^^^^^^^^^^^^ argument never used
|
||||
| |
|
||||
| formatting specifier missing
|
||||
|
||||
error[E0527]: pattern requires 2 elements but array has 3
|
||||
--> tests/ui/fails.rs:8:9
|
||||
|
|
||||
8 | let [a, b] = constraints![
|
||||
| ^^^^^^ expected 3 elements
|
@ -25,8 +25,9 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
#!
|
||||
## By default, we enable the crossterm backend as this is a reasonable choice for most applications
|
||||
## as it is supported on Linux/Mac/Windows systems. We also enable the `underline-color` feature
|
||||
## which allows you to set the underline color of text.
|
||||
default = ["crossterm", "underline-color", "all-widgets"]
|
||||
## which allows you to set the underline color of text and the `macros` feature which provides
|
||||
## some useful macros.
|
||||
default = ["crossterm", "underline-color", "all-widgets", "macros"]
|
||||
#! 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"]
|
||||
@ -52,6 +53,10 @@ scrolling-regions = [
|
||||
"ratatui-termwiz?/scrolling-regions",
|
||||
]
|
||||
|
||||
## enables the [`macros`](macros) module which provides some useful macros for creating spans,
|
||||
## lines, text, and layouts
|
||||
macros = ["dep:ratatui-macros"]
|
||||
|
||||
## enables all widgets.
|
||||
all-widgets = ["widget-calendar"]
|
||||
|
||||
@ -102,6 +107,7 @@ itertools.workspace = true
|
||||
palette = { version = "0.7.6", optional = true }
|
||||
ratatui-core = { workspace = true }
|
||||
ratatui-crossterm = { workspace = true, optional = true }
|
||||
ratatui-macros = { workspace = true, optional = true }
|
||||
ratatui-termwiz = { workspace = true, optional = true }
|
||||
ratatui-widgets = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
|
@ -335,6 +335,8 @@ pub use ratatui_core::{
|
||||
/// re-export the `crossterm` crate so that users don't have to add it as a dependency
|
||||
#[cfg(feature = "crossterm")]
|
||||
pub use ratatui_crossterm::crossterm;
|
||||
#[cfg(feature = "macros")]
|
||||
pub use ratatui_macros as macros;
|
||||
/// re-export the `termion` crate so that users don't have to add it as a dependency
|
||||
#[cfg(all(not(windows), feature = "termion"))]
|
||||
pub use ratatui_termion::termion;
|
||||
|
Loading…
x
Reference in New Issue
Block a user