Skip to main content

spectrum/emit/
fragment.rs

1use std::{fmt, fmt::Formatter, io::Write};
2
3use super::{
4    buf::Buf,
5    error::{EmitError, EmitResult},
6    style::Style,
7};
8
9/// A [StyledFragmentTrait] represents a fragment of content that can be emitted into an output
10/// stream (through an EmitBackend).
11///
12/// An implementation of StyledFragmentTrait must implement `emit_into_formatter`, and gets default
13/// implementations of `emit_into`, which takes a `fmt::Write` and a backend and writes into the
14/// `fmt::Write`, and `emit_into_string`, which takes a backend and produces a `String`.
15///
16/// In general, you should implement [StyledFragmentTrait] and store [StyledFragment].
17pub trait StyledFragmentTrait {
18    fn clone_frag(&self) -> StyledFragment;
19
20    fn emit_into_formatter(&self, f: &mut Formatter<'_>, backend: &EmitBackend<'_>) -> EmitResult;
21}
22
23impl<T> From<T> for StyledFragment
24where
25    T: StyledFragmentTrait + 'static,
26{
27    fn from(value: T) -> StyledFragment {
28        StyledFragment {
29            fragment: Box::new(value),
30        }
31    }
32}
33
34/// A [StyledFragment] is a concrete value that represents an implementation of
35/// [StyledFragmentTrait].
36pub struct StyledFragment {
37    fragment: Box<dyn StyledFragmentTrait + 'static>,
38}
39
40impl Clone for StyledFragment {
41    fn clone(&self) -> Self {
42        self.clone_frag()
43    }
44}
45
46impl StyledFragment {
47    pub fn new(frag: impl StyledFragmentTrait + 'static) -> StyledFragment {
48        StyledFragment {
49            fragment: Box::new(frag),
50        }
51    }
52
53    pub fn clone_frag(&self) -> StyledFragment {
54        self.fragment.clone_frag()
55    }
56
57    pub fn plain(&self) -> String {
58        self.emit_into_string(EmitPlain).unwrap()
59    }
60
61    pub fn emit_into_formatter(
62        &self,
63        f: &mut Formatter<'_>,
64        backend: &EmitBackend<'_>,
65    ) -> EmitResult {
66        self.fragment.emit_into_formatter(f, backend)
67    }
68
69    pub fn emit_into(
70        &self,
71        write: &mut dyn std::io::Write,
72        backend: &EmitBackend<'_>,
73    ) -> EmitResult {
74        let formatted = format::Display(move |f| Ok(self.emit_into_formatter(f, backend)?));
75        Ok(write!(write, "{}", formatted)?)
76    }
77
78    pub fn emit_into_string(&self, backend: impl EmitBackendTrait) -> EmitResult<String> {
79        Ok(Buf::collect_string(|write| {
80            Ok(self.emit_into(write, &backend.emitter())?)
81        })?)
82    }
83}
84
85pub struct StyledNewline;
86
87impl StyledFragmentTrait for StyledNewline {
88    fn emit_into_formatter(&self, f: &mut Formatter<'_>, backend: &EmitBackend<'_>) -> EmitResult {
89        backend.emit(f, "\n", &Style::default())
90    }
91
92    fn clone_frag(&self) -> StyledFragment {
93        StyledFragment::new(StyledNewline)
94    }
95}
96
97/// A `StyledString` is the simplest implementation of `StyledFragment`, holding a `String` and a
98/// `Style`.
99#[derive(Debug, Clone)]
100pub struct StyledString {
101    string: String,
102    style: Style,
103}
104
105impl StyledString {
106    pub fn new(string: impl Into<String>, style: impl Into<Style>) -> StyledString {
107        StyledString {
108            string: string.into(),
109            style: style.into(),
110        }
111    }
112}
113
114impl StyledFragmentTrait for StyledString {
115    fn emit_into_formatter(&self, f: &mut Formatter<'_>, backend: &EmitBackend<'_>) -> EmitResult {
116        backend.emit(f, &self.string[..], &self.style)
117    }
118
119    fn clone_frag(&self) -> StyledFragment {
120        StyledFragment::new(self.clone())
121    }
122}
123
124/// A [StyledLine] is a list of [StyledFragment]s, intended to be laid out on a single line
125pub struct StyledLine {
126    line: Vec<StyledFragment>,
127}
128
129impl StyledLine {
130    pub fn new(fragments: Vec<StyledFragment>) -> StyledLine {
131        StyledLine { line: fragments }
132    }
133}
134
135impl StyledFragmentTrait for StyledLine {
136    fn emit_into_formatter(&self, f: &mut Formatter<'_>, backend: &EmitBackend<'_>) -> EmitResult {
137        for fragment in &self.line {
138            fragment.emit_into_formatter(f, backend)?
139        }
140
141        Ok(())
142    }
143
144    fn clone_frag(&self) -> StyledFragment {
145        StyledFragment::new(StyledLine {
146            line: self.line.to_vec(),
147        })
148    }
149}
150
151/// An implementation of `EmitBackendTrait` takes a piece of styled text and emits it into the
152/// supplied [std::fmt::Formatter].
153pub trait EmitBackendTrait {
154    fn emit(&self, f: &mut Formatter<'_>, fragment: &str, style: &Style) -> EmitResult;
155
156    fn emitter(&self) -> EmitBackend<'_>
157    where
158        Self: Sized,
159    {
160        EmitBackend { backend: self }
161    }
162}
163
164impl<'a, T> From<&'a T> for EmitBackend<'a>
165where
166    T: EmitBackendTrait + 'a,
167{
168    fn from(backend: &'a T) -> Self {
169        backend.emitter()
170    }
171}
172
173pub struct EmitBackend<'a> {
174    backend: &'a dyn EmitBackendTrait,
175}
176
177impl<'a> EmitBackend<'a> {
178    fn emit(&self, f: &mut Formatter<'_>, fragment: &str, style: &Style) -> EmitResult {
179        self.backend.emit(f, fragment, style)
180    }
181}
182
183pub struct EmitColored;
184
185impl EmitBackendTrait for EmitColored {
186    fn emit(&self, f: &mut Formatter<'_>, fragment: &str, style: &Style) -> EmitResult {
187        write!(f, "{}", style.apply_to(fragment)).map_err(EmitError::new)
188    }
189}
190
191pub struct EmitPlain;
192
193impl EmitBackendTrait for EmitPlain {
194    fn emit(&self, f: &mut Formatter<'_>, fragment: &str, _style: &Style) -> EmitResult {
195        write!(f, "{}", fragment).map_err(EmitError::new)
196    }
197}
198
199pub fn write_into(
200    write: &mut impl Write,
201    callback: impl Fn(&mut Formatter<'_>) -> fmt::Result,
202) -> EmitResult {
203    let formatted = format::Display(move |f| callback(f));
204    Ok(write!(write, "{}", formatted)?)
205}
206
207#[cfg(test)]
208mod tests {
209    use crate::EmitForTest;
210
211    use super::*;
212    use console::Color;
213
214    #[test]
215    fn emit_test() -> EmitResult {
216        let styled: StyledFragment =
217            StyledString::new("hello emitter world", Style::new().fg(Color::Red)).into();
218        let string = styled.emit_into_string(EmitForTest)?;
219
220        assert_eq!(&string, "[Red:hello emitter world]");
221
222        Ok(())
223    }
224
225    #[test]
226    fn emit_plain() -> EmitResult {
227        let styled: StyledFragment =
228            StyledString::new("hello emitter world", Style::new().fg(Color::Red)).into();
229        let string = styled.emit_into_string(EmitPlain)?;
230
231        assert_eq!(&string, "hello emitter world");
232
233        Ok(())
234    }
235
236    #[test]
237    fn emit_colored() -> EmitResult {
238        let styled: StyledFragment =
239            StyledString::new("hello emitter world", Style::new().fg(Color::Red)).into();
240        let string = styled.emit_into_string(EmitColored)?;
241
242        assert_eq!(&string, "\u{1b}[31mhello emitter world\u{1b}[0m");
243
244        Ok(())
245    }
246}