pretty/
render.rs

1use std::cmp;
2use std::fmt;
3use std::io;
4use std::ops::Deref;
5#[cfg(feature = "termcolor")]
6use termcolor::{ColorSpec, WriteColor};
7
8use Doc;
9
10/// Trait representing the operations necessary to render a document
11pub trait Render {
12    type Error;
13
14    fn write_str(&mut self, s: &str) -> Result<usize, Self::Error>;
15
16    fn write_str_all(&mut self, mut s: &str) -> Result<(), Self::Error> {
17        while !s.is_empty() {
18            let count = self.write_str(s)?;
19            s = &s[count..];
20        }
21        Ok(())
22    }
23}
24
25/// Writes to something implementing `std::io::Write`
26pub struct IoWrite<W> {
27    upstream: W,
28}
29
30impl<W> IoWrite<W> {
31    pub fn new(upstream: W) -> IoWrite<W> {
32        IoWrite { upstream }
33    }
34}
35
36impl<W> Render for IoWrite<W>
37where
38    W: io::Write,
39{
40    type Error = io::Error;
41
42    fn write_str(&mut self, s: &str) -> io::Result<usize> {
43        self.upstream.write(s.as_bytes())
44    }
45
46    fn write_str_all(&mut self, s: &str) -> io::Result<()> {
47        self.upstream.write_all(s.as_bytes())
48    }
49}
50
51/// Writes to something implementing `std::fmt::Write`
52pub struct FmtWrite<W> {
53    upstream: W,
54}
55
56impl<W> FmtWrite<W> {
57    pub fn new(upstream: W) -> FmtWrite<W> {
58        FmtWrite { upstream }
59    }
60}
61
62impl<W> Render for FmtWrite<W>
63where
64    W: fmt::Write,
65{
66    type Error = fmt::Error;
67
68    fn write_str(&mut self, s: &str) -> Result<usize, fmt::Error> {
69        self.write_str_all(s).map(|_| s.len())
70    }
71
72    fn write_str_all(&mut self, s: &str) -> fmt::Result {
73        self.upstream.write_str(s)
74    }
75}
76
77/// Trait representing the operations necessary to write an annotated document.
78pub trait RenderAnnotated<A>: Render {
79    fn push_annotation(&mut self, annotation: &A) -> Result<(), Self::Error>;
80    fn pop_annotation(&mut self) -> Result<(), Self::Error>;
81}
82
83impl<A, W> RenderAnnotated<A> for IoWrite<W>
84where
85    W: io::Write,
86{
87    fn push_annotation(&mut self, _: &A) -> Result<(), Self::Error> {
88        Ok(())
89    }
90
91    fn pop_annotation(&mut self) -> Result<(), Self::Error> {
92        Ok(())
93    }
94}
95
96impl<A, W> RenderAnnotated<A> for FmtWrite<W>
97where
98    W: fmt::Write,
99{
100    fn push_annotation(&mut self, _: &A) -> Result<(), Self::Error> {
101        Ok(())
102    }
103
104    fn pop_annotation(&mut self) -> Result<(), Self::Error> {
105        Ok(())
106    }
107}
108
109#[cfg(feature = "termcolor")]
110pub struct TermColored<W> {
111    color_stack: Vec<ColorSpec>,
112    upstream: W,
113}
114
115#[cfg(feature = "termcolor")]
116impl<W> TermColored<W> {
117    pub fn new(upstream: W) -> TermColored<W> {
118        TermColored {
119            color_stack: Vec::new(),
120            upstream,
121        }
122    }
123}
124
125#[cfg(feature = "termcolor")]
126impl<W> Render for TermColored<W>
127where
128    W: io::Write,
129{
130    type Error = io::Error;
131
132    fn write_str(&mut self, s: &str) -> io::Result<usize> {
133        self.upstream.write(s.as_bytes())
134    }
135
136    fn write_str_all(&mut self, s: &str) -> io::Result<()> {
137        self.upstream.write_all(s.as_bytes())
138    }
139}
140
141#[cfg(feature = "termcolor")]
142impl<W> RenderAnnotated<ColorSpec> for TermColored<W>
143where
144    W: WriteColor,
145{
146    fn push_annotation(&mut self, color: &ColorSpec) -> Result<(), Self::Error> {
147        self.color_stack.push(color.clone());
148        self.upstream.set_color(color)
149    }
150
151    fn pop_annotation(&mut self) -> Result<(), Self::Error> {
152        self.color_stack.pop();
153        match self.color_stack.last() {
154            Some(previous) => self.upstream.set_color(previous),
155            None => self.upstream.reset(),
156        }
157    }
158}
159
160#[inline]
161pub fn best<'a, W, T, A>(doc: &'a Doc<'a, T, A>, width: usize, out: &mut W) -> Result<(), W::Error>
162where
163    T: Deref<Target = Doc<'a, T, A>>,
164    W: ?Sized + RenderAnnotated<A>,
165{
166    #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
167    enum Mode {
168        Break,
169        Flat,
170    }
171
172    type Cmd<'a, T, A> = (usize, Mode, &'a Doc<'a, T, A>);
173
174    fn write_newline<W>(ind: usize, out: &mut W) -> Result<(), W::Error>
175    where
176        W: ?Sized + Render,
177    {
178        out.write_str_all("\n")?;
179        write_spaces(ind, out)
180    }
181
182    fn write_spaces<W>(spaces: usize, out: &mut W) -> Result<(), W::Error>
183    where
184        W: ?Sized + Render,
185    {
186        macro_rules! make_spaces {
187            () => { "" };
188            ($s: tt $($t: tt)*) => { concat!("          ", make_spaces!($($t)*)) };
189        }
190
191        const SPACES: &str = make_spaces!(,,,,,,,,,,);
192        let mut inserted = 0;
193        while inserted < spaces {
194            let insert = cmp::min(SPACES.len(), spaces - inserted);
195            inserted += out.write_str(&SPACES[..insert])?;
196        }
197
198        Ok(())
199    }
200
201    #[inline]
202    fn fitting<'a, T, A>(
203        next: Cmd<'a, T, A>,
204        bcmds: &[Cmd<'a, T, A>],
205        fcmds: &mut Vec<Cmd<'a, T, A>>,
206        mut rem: isize,
207    ) -> bool
208    where
209        T: Deref<Target = Doc<'a, T, A>>,
210    {
211        let mut bidx = bcmds.len();
212        fcmds.clear(); // clear from previous calls from best
213        fcmds.push(next);
214
215        while rem >= 0 {
216            match fcmds.pop() {
217                None => {
218                    if bidx == 0 {
219                        // All commands have been processed
220                        return true;
221                    } else {
222                        fcmds.push(bcmds[bidx - 1]);
223                        bidx -= 1;
224                    }
225                }
226                Some((ind, mode, doc)) => {
227                    match *doc {
228                        Doc::Nil => {}
229                        Doc::Append(ref ldoc, ref rdoc) => {
230                            fcmds.push((ind, mode, rdoc));
231                            // Since appended documents often appear in sequence on the left side we
232                            // gain a slight performance increase by batching these pushes (avoiding
233                            // to push and directly pop `Append` documents)
234                            let mut doc = ldoc;
235                            while let Doc::Append(ref l, ref r) = **doc {
236                                fcmds.push((ind, mode, r));
237                                doc = l;
238                            }
239                            fcmds.push((ind, mode, doc));
240                        }
241                        Doc::Group(ref doc) => {
242                            fcmds.push((ind, mode, doc));
243                        }
244                        Doc::Nest(off, ref doc) => {
245                            fcmds.push((ind + off, mode, doc));
246                        }
247                        Doc::Space => match mode {
248                            Mode::Flat => {
249                                rem -= 1;
250                            }
251                            Mode::Break => {
252                                return true;
253                            }
254                        },
255                        Doc::Newline => return true,
256                        Doc::Text(ref str) => {
257                            rem -= str.len() as isize;
258                        }
259                        Doc::Annotated(_, ref doc) => fcmds.push((ind, mode, doc)),
260                    }
261                }
262            }
263        }
264
265        false
266    }
267
268    let mut pos = 0;
269    let mut bcmds = vec![(0, Mode::Break, doc)];
270    let mut fcmds = vec![];
271    let mut annotation_levels = vec![];
272
273    while let Some((ind, mode, doc)) = bcmds.pop() {
274        match *doc {
275            Doc::Nil => {}
276            Doc::Append(ref ldoc, ref rdoc) => {
277                bcmds.push((ind, mode, rdoc));
278                let mut doc = ldoc;
279                while let Doc::Append(ref l, ref r) = **doc {
280                    bcmds.push((ind, mode, r));
281                    doc = l;
282                }
283                bcmds.push((ind, mode, doc));
284            }
285            Doc::Group(ref doc) => match mode {
286                Mode::Flat => {
287                    bcmds.push((ind, Mode::Flat, doc));
288                }
289                Mode::Break => {
290                    let next = (ind, Mode::Flat, &**doc);
291                    let rem = width as isize - pos as isize;
292                    if fitting(next, &bcmds, &mut fcmds, rem) {
293                        bcmds.push(next);
294                    } else {
295                        bcmds.push((ind, Mode::Break, doc));
296                    }
297                }
298            },
299            Doc::Nest(off, ref doc) => {
300                bcmds.push((ind + off, mode, doc));
301            }
302            Doc::Space => match mode {
303                Mode::Flat => {
304                    write_spaces(1, out)?;
305                }
306                Mode::Break => {
307                    write_newline(ind, out)?;
308                    pos = ind;
309                }
310            },
311            Doc::Newline => {
312                write_newline(ind, out)?;
313                pos = ind;
314
315                // Since this newline caused an early break we don't know if the remaining
316                // documents fit the next line so recalculate if they fit
317                fcmds.clear();
318                let docs = bcmds.len()
319                    - bcmds
320                        .iter()
321                        .rev()
322                        .position(|t| t.1 == Mode::Break)
323                        .unwrap_or_else(|| bcmds.len());
324                fcmds.extend_from_slice(&bcmds[docs..]);
325                if let Some(next) = fcmds.pop() {
326                    let rem = width as isize - pos as isize;
327                    if !fitting(next, &bcmds, &mut fcmds, rem) {
328                        for &mut (_, ref mut mode, _) in &mut bcmds[docs..] {
329                            *mode = Mode::Break;
330                        }
331                    }
332                }
333            }
334            Doc::Text(ref s) => {
335                out.write_str_all(s)?;
336                pos += s.len();
337            }
338            Doc::Annotated(ref ann, ref doc) => {
339                out.push_annotation(ann)?;
340                annotation_levels.push(bcmds.len());
341                bcmds.push((ind, mode, doc));
342            }
343        }
344
345        if annotation_levels.last() == Some(&bcmds.len()) {
346            annotation_levels.pop();
347            out.pop_annotation()?;
348        }
349    }
350
351    Ok(())
352}