1use core::fmt;
2
3use annotated_string::{ApplyAnnotation, AnnotatedRope, Annotation};
4
5pub type Text = AnnotatedRope<Formatting>;
6
7#[derive(Default, Clone, PartialEq, Debug)]
8pub struct Formatting {
9 pub color: Option<u32>,
10 pub bg_color: Option<u32>,
11 pub bold: bool,
12 pub underline: bool,
13 pub decoration: bool,
14 pub url: Option<String>,
15}
16impl Annotation for Formatting {
17 fn try_merge(&mut self, other: &Self) -> bool {
18 self == other
19 }
20}
21
22impl ApplyAnnotation<Formatting> for Formatting {
23 fn apply(&mut self, change: &Formatting) {
24 if let Some(color) = change.color {
25 self.color = Some(color);
26 }
27 if let Some(bg_color) = change.bg_color {
28 self.bg_color = Some(bg_color);
29 }
30 if change.bold {
31 self.bold = true;
32 }
33 if change.underline {
34 self.underline = true;
35 }
36 if change.url.is_some() {
37 self.url = change.url.clone();
38 }
39 }
40}
41
42pub struct AddColorToUncolored(pub u32);
43impl ApplyAnnotation<AddColorToUncolored> for Formatting {
44 fn apply(&mut self, change: &AddColorToUncolored) {
45 if self.color.is_some() {
46 return;
47 }
48 self.color = Some(change.0);
49 }
50}
51
52impl Formatting {
53 pub fn listchar() -> Self {
54 Self {
55 color: Some(0x92837400),
56 decoration: true,
57 ..Default::default()
58 }
59 }
60 pub fn line_number() -> Self {
61 Self {
62 color: Some(0x92837400),
63 ..Default::default()
65 }
66 }
67 pub fn border() -> Self {
68 Self {
69 color: Some(0x92929200),
70 ..Default::default()
71 }
72 }
73 pub fn filename() -> Self {
74 Self {
75 color: Some(0x6868a900),
76 ..Default::default()
77 }
78 }
79 pub fn color(color: u32) -> Self {
80 Self {
81 color: Some(color),
82 ..Default::default()
83 }
84 }
85 pub fn rgb([r, g, b]: [u8; 3]) -> Self {
86 Self::color(u32::from_be_bytes([r, g, b, 0]))
87 }
88
89 pub fn underline(mut self) -> Self {
90 self.underline = true;
91 self
92 }
93
94 pub fn bold(mut self) -> Self {
95 self.bold = true;
96 self
97 }
98
99 pub fn decoration(mut self) -> Self {
100 self.decoration = true;
101 self
102 }
103
104 pub fn url(mut self, url: String) -> Self {
106 self.url = Some(url);
107 self
108 }
109}
110
111const CSI: &str = "\x1b[";
112const OSC: &str = "\x1b]";
113const ST: &str = "\x1b\\";
114
115pub fn text_to_ansi(buf: &Text, out: &mut String) {
116 text_to_ansi_res(buf, out).expect("no fmt error")
117}
118pub fn text_to_ansi_res(buf: &Text, out: &mut String) -> fmt::Result {
119 use std::fmt::Write;
120
121 for (text, meta) in buf.fragments() {
122 if meta.bold {
123 write!(out, "{CSI}1m")?;
124 }
125 if meta.underline {
126 write!(out, "{CSI}4m")?;
127 }
128 if let Some(color) = meta.color {
129 let [r, g, b, _a] = u32::to_be_bytes(color);
130 write!(out, "{CSI}38;2;{r};{g};{b}m")?;
131 }
132 if let Some(bg_color) = meta.bg_color {
133 let [r, g, b, _a] = u32::to_be_bytes(bg_color);
134 write!(out, "{CSI}48;2;{r};{g};{b}m")?;
135 }
136 if let Some(url) = &meta.url {
138 write!(out, "{OSC}8;;{url}{ST}")?;
139 }
140 for chunk in text {
141 write!(out, "{chunk}")?;
142 }
143 if meta.url.is_some() {
144 write!(out, "{OSC}8;;{ST}")?;
145 }
146 if meta.color.is_some() || meta.bg_color.is_some() || meta.underline || meta.bold {
147 write!(out, "{CSI}0m")?;
148 }
149 }
150 Ok(())
151}