radicle_term/ansi/
style.rs1use std::fmt::{self, Display};
2use std::hash::{Hash, Hasher};
3use std::ops::BitOr;
4
5use super::{Color, Paint};
6
7#[derive(Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
8pub struct Property(u8);
9
10impl Property {
11 pub const BOLD: Self = Property(1 << 0);
12 pub const DIM: Self = Property(1 << 1);
13 pub const ITALIC: Self = Property(1 << 2);
14 pub const UNDERLINE: Self = Property(1 << 3);
15 pub const BLINK: Self = Property(1 << 4);
16 pub const INVERT: Self = Property(1 << 5);
17 pub const HIDDEN: Self = Property(1 << 6);
18 pub const STRIKETHROUGH: Self = Property(1 << 7);
19
20 pub const fn new() -> Self {
21 Property(0)
22 }
23
24 #[inline(always)]
25 pub const fn is_empty(self) -> bool {
26 self.0 == 0
27 }
28
29 #[inline(always)]
30 pub const fn contains(self, other: Property) -> bool {
31 (other.0 & self.0) == other.0
32 }
33
34 #[inline(always)]
35 pub fn set(&mut self, other: Property) {
36 self.0 |= other.0;
37 }
38
39 #[inline(always)]
40 pub fn iter(self) -> Iter {
41 Iter {
42 index: 0,
43 properties: self,
44 }
45 }
46}
47
48impl BitOr for Property {
49 type Output = Self;
50
51 #[inline(always)]
52 fn bitor(self, rhs: Self) -> Self {
53 Property(self.0 | rhs.0)
54 }
55}
56
57pub struct Iter {
58 index: u8,
59 properties: Property,
60}
61
62impl Iterator for Iter {
63 type Item = usize;
64
65 fn next(&mut self) -> Option<Self::Item> {
66 while self.index < 8 {
67 let index = self.index;
68 self.index += 1;
69
70 if self.properties.contains(Property(1 << index)) {
71 return Some(index as usize);
72 }
73 }
74
75 None
76 }
77}
78
79#[repr(C, packed)]
81#[derive(Default, Debug, Eq, Ord, PartialOrd, Copy, Clone)]
82pub struct Style {
83 pub(crate) foreground: Color,
84 pub(crate) background: Color,
85 pub(crate) properties: Property,
86 pub(crate) wrap: bool,
87}
88
89impl PartialEq for Style {
90 fn eq(&self, other: &Style) -> bool {
91 self.foreground == other.foreground
92 && self.background == other.background
93 && self.properties == other.properties
94 }
95}
96
97impl Hash for Style {
98 fn hash<H: Hasher>(&self, state: &mut H) {
99 self.foreground.hash(state);
100 self.background.hash(state);
101 self.properties.hash(state);
102 }
103}
104
105#[inline]
106fn write_spliced<T: Display>(c: &mut bool, f: &mut dyn fmt::Write, t: T) -> fmt::Result {
107 if *c {
108 write!(f, ";{t}")
109 } else {
110 *c = true;
111 write!(f, "{t}")
112 }
113}
114
115impl Style {
116 #[inline]
119 pub const fn new(color: Color) -> Style {
120 Self {
122 foreground: color,
123 background: Color::Unset,
124 properties: Property::new(),
125 wrap: false,
126 }
127 }
128
129 #[inline]
131 pub const fn fg(mut self, color: Color) -> Style {
132 self.foreground = color;
133 self
134 }
135
136 #[inline]
138 pub const fn bg(mut self, color: Color) -> Style {
139 self.background = color;
140 self
141 }
142
143 pub fn merge(mut self, other: Style) -> Style {
146 if self.foreground == Color::Unset {
147 self.foreground = other.foreground;
148 }
149 if self.background == Color::Unset {
150 self.background = other.background;
151 }
152 self.properties.set(other.properties);
153 self
154 }
155
156 #[inline]
168 pub const fn wrap(mut self) -> Style {
169 self.wrap = true;
170 self
171 }
172
173 pub fn bold(mut self) -> Self {
174 self.properties.set(Property::BOLD);
175 self
176 }
177
178 pub fn dim(mut self) -> Self {
179 self.properties.set(Property::DIM);
180 self
181 }
182
183 pub fn italic(mut self) -> Self {
184 self.properties.set(Property::ITALIC);
185 self
186 }
187
188 pub fn underline(mut self) -> Self {
189 self.properties.set(Property::UNDERLINE);
190 self
191 }
192
193 pub fn invert(mut self) -> Self {
194 self.properties.set(Property::INVERT);
195 self
196 }
197
198 pub fn strikethrough(mut self) -> Self {
199 self.properties.set(Property::STRIKETHROUGH);
200 self
201 }
202
203 #[inline]
206 pub fn paint<T>(self, item: T) -> Paint<T> {
207 Paint::new(item).with_style(self)
208 }
209
210 #[inline]
212 pub const fn fg_color(&self) -> Color {
213 self.foreground
214 }
215
216 #[inline]
218 pub const fn bg_color(&self) -> Color {
219 self.background
220 }
221
222 #[inline]
224 pub const fn is_wrapping(&self) -> bool {
225 self.wrap
226 }
227
228 #[inline(always)]
229 fn is_plain(&self) -> bool {
230 self == &Style::default()
231 }
232
233 pub fn fmt_prefix(&self, f: &mut dyn fmt::Write) -> fmt::Result {
243 let color_enabled = !anstyle_query::no_color();
244
245 if self.is_plain() {
247 return Ok(());
248 }
249 if !color_enabled && self.properties.is_empty() {
251 return Ok(());
252 }
253
254 let mut splice = false;
255 write!(f, "\x1B[")?;
256
257 for i in self.properties.iter() {
258 let k = if i >= 5 { i + 2 } else { i + 1 };
259 write_spliced(&mut splice, f, k)?;
260 }
261
262 if color_enabled {
263 if self.background != Color::Unset {
264 write_spliced(&mut splice, f, "4")?;
265 self.background.ansi_fmt(f)?;
266 }
267
268 if self.foreground != Color::Unset {
269 write_spliced(&mut splice, f, "3")?;
270 self.foreground.ansi_fmt(f)?;
271 }
272 }
273
274 write!(f, "m")
276 }
277
278 pub fn fmt_suffix(&self, f: &mut dyn fmt::Write) -> fmt::Result {
288 let color_enabled = !anstyle_query::no_color();
289
290 if self.is_plain() {
292 return Ok(());
293 }
294 if !color_enabled && self.properties.is_empty() {
296 return Ok(());
297 }
298 write!(f, "\x1B[0m")
299 }
300}