nu_source/
pretty.rs

1use crate::meta::Spanned;
2use crate::term_colored::TermColored;
3use crate::text::Text;
4use derive_new::new;
5use pretty::{BoxAllocator, DocAllocator};
6use std::hash::Hash;
7use termcolor::{Color, ColorSpec};
8
9#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
10pub enum ShellStyle {
11    Delimiter,
12    Key,
13    Value,
14    Equals,
15    Kind,
16    Keyword,
17    Operator,
18    Variable,
19    Primitive,
20    Opaque,
21    Description,
22    Error,
23}
24
25impl From<ShellAnnotation> for ColorSpec {
26    fn from(ann: ShellAnnotation) -> ColorSpec {
27        match ann.style {
28            ShellStyle::Delimiter => ColorSpec::new()
29                .set_fg(Some(Color::White))
30                .set_intense(false)
31                .clone(),
32            ShellStyle::Key => ColorSpec::new()
33                .set_fg(Some(Color::Green))
34                .set_intense(true)
35                .clone(),
36            ShellStyle::Value => ColorSpec::new()
37                .set_fg(Some(Color::White))
38                .set_intense(true)
39                .clone(),
40            ShellStyle::Equals => ColorSpec::new()
41                .set_fg(Some(Color::Green))
42                .set_intense(true)
43                .clone(),
44            ShellStyle::Kind => ColorSpec::new().set_fg(Some(Color::Cyan)).clone(),
45            ShellStyle::Variable => ColorSpec::new()
46                .set_fg(Some(Color::Green))
47                .set_intense(true)
48                .clone(),
49            ShellStyle::Keyword => ColorSpec::new().set_fg(Some(Color::Magenta)).clone(),
50            ShellStyle::Operator => ColorSpec::new().set_fg(Some(Color::Yellow)).clone(),
51            ShellStyle::Primitive => ColorSpec::new()
52                .set_fg(Some(Color::Green))
53                .set_intense(true)
54                .clone(),
55            ShellStyle::Opaque => ColorSpec::new()
56                .set_fg(Some(Color::Yellow))
57                .set_intense(true)
58                .clone(),
59            ShellStyle::Description => ColorSpec::new()
60                .set_fg(Some(Color::Green))
61                .set_intense(true)
62                .clone(),
63            ShellStyle::Error => ColorSpec::new()
64                .set_fg(Some(Color::Red))
65                .set_intense(true)
66                .clone(),
67        }
68    }
69}
70
71#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash, new)]
72pub struct ShellAnnotation {
73    style: ShellStyle,
74}
75
76impl std::fmt::Debug for ShellAnnotation {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        write!(f, "{:?}", self.style)
79    }
80}
81
82impl ShellAnnotation {
83    pub fn style(style: impl Into<ShellStyle>) -> ShellAnnotation {
84        ShellAnnotation {
85            style: style.into(),
86        }
87    }
88}
89
90pub type PrettyDebugDoc =
91    pretty::Doc<'static, pretty::BoxDoc<'static, ShellAnnotation>, ShellAnnotation>;
92
93pub type PrettyDebugDocBuilder = pretty::DocBuilder<'static, pretty::BoxAllocator, ShellAnnotation>;
94
95pub use self::DebugDocBuilder as DbgDocBldr;
96
97#[derive(Clone, new)]
98pub struct DebugDocBuilder {
99    pub inner: PrettyDebugDocBuilder,
100}
101
102impl PrettyDebug for bool {
103    fn pretty(&self) -> DebugDocBuilder {
104        match self {
105            true => DbgDocBldr::primitive("true"),
106            false => DbgDocBldr::primitive("false"),
107        }
108    }
109}
110
111impl PrettyDebug for () {
112    fn pretty(&self) -> DebugDocBuilder {
113        DbgDocBldr::primitive("nothing")
114    }
115}
116
117impl PrettyDebug for DebugDocBuilder {
118    fn pretty(&self) -> DebugDocBuilder {
119        self.clone()
120    }
121}
122
123impl std::ops::Add for DebugDocBuilder {
124    type Output = DebugDocBuilder;
125
126    fn add(self, rhs: DebugDocBuilder) -> DebugDocBuilder {
127        DebugDocBuilder::new(self.inner.append(rhs.inner))
128    }
129}
130
131impl DebugDocBuilder {
132    pub fn from_doc(doc: DebugDoc) -> DebugDocBuilder {
133        DebugDocBuilder {
134            inner: BoxAllocator.nil().append(doc),
135        }
136    }
137
138    pub fn blank() -> DebugDocBuilder {
139        BoxAllocator.nil().into()
140    }
141
142    pub fn delimiter(string: impl std::fmt::Display) -> DebugDocBuilder {
143        DebugDocBuilder::styled(string, ShellStyle::Delimiter)
144    }
145
146    pub fn key(string: impl std::fmt::Display) -> DebugDocBuilder {
147        DebugDocBuilder::styled(string, ShellStyle::Key)
148    }
149
150    pub fn value(string: impl std::fmt::Display) -> DebugDocBuilder {
151        DebugDocBuilder::styled(string, ShellStyle::Value)
152    }
153
154    pub fn into_value(self) -> DebugDocBuilder {
155        self.inner
156            .annotate(ShellAnnotation::style(ShellStyle::Value))
157            .into()
158    }
159
160    pub fn equals() -> DebugDocBuilder {
161        DebugDocBuilder::styled("=", ShellStyle::Equals)
162    }
163
164    pub fn kind(string: impl std::fmt::Display) -> DebugDocBuilder {
165        DebugDocBuilder::styled(string, ShellStyle::Kind)
166    }
167
168    pub fn into_kind(self) -> DebugDocBuilder {
169        self.inner
170            .annotate(ShellAnnotation::style(ShellStyle::Kind))
171            .into()
172    }
173
174    pub fn typed(kind: &str, value: DebugDocBuilder) -> DebugDocBuilder {
175        DbgDocBldr::kind(kind) + DbgDocBldr::delimit("[", value.group(), "]")
176    }
177
178    pub fn subtyped(
179        kind: &str,
180        subkind: impl std::fmt::Display,
181        value: DebugDocBuilder,
182    ) -> DebugDocBuilder {
183        DbgDocBldr::delimit(
184            "(",
185            (DbgDocBldr::kind(kind) + DbgDocBldr::delimit("[", DbgDocBldr::kind(subkind), "]"))
186                .group()
187                + DbgDocBldr::space()
188                + value.group(),
189            ")",
190        )
191        .group()
192    }
193
194    pub fn keyword(string: impl std::fmt::Display) -> DebugDocBuilder {
195        DebugDocBuilder::styled(string, ShellStyle::Keyword)
196    }
197
198    pub fn var(string: impl std::fmt::Display) -> DebugDocBuilder {
199        DebugDocBuilder::styled(string, ShellStyle::Variable)
200    }
201
202    pub fn operator(string: impl std::fmt::Display) -> DebugDocBuilder {
203        DebugDocBuilder::styled(string, ShellStyle::Operator)
204    }
205
206    pub fn primitive(string: impl std::fmt::Display) -> DebugDocBuilder {
207        DebugDocBuilder::styled(string, ShellStyle::Primitive)
208    }
209
210    pub fn opaque(string: impl std::fmt::Display) -> DebugDocBuilder {
211        DebugDocBuilder::styled(string, ShellStyle::Opaque)
212    }
213
214    pub fn description(string: impl std::fmt::Display) -> DebugDocBuilder {
215        DebugDocBuilder::styled(string, ShellStyle::Description)
216    }
217
218    pub fn error(string: impl std::fmt::Display) -> DebugDocBuilder {
219        DebugDocBuilder::styled(string, ShellStyle::Error)
220    }
221
222    pub fn delimit(start: &str, doc: DebugDocBuilder, end: &str) -> DebugDocBuilder {
223        DebugDocBuilder::delimiter(start) + doc + DebugDocBuilder::delimiter(end)
224    }
225
226    pub fn preceded(before: DebugDocBuilder, body: DebugDocBuilder) -> DebugDocBuilder {
227        if body.is_empty() {
228            body
229        } else {
230            before + body
231        }
232    }
233
234    pub fn surrounded_option(
235        before: Option<DebugDocBuilder>,
236        builder: Option<DebugDocBuilder>,
237        after: Option<DebugDocBuilder>,
238    ) -> DebugDocBuilder {
239        match builder {
240            None => DebugDocBuilder::blank(),
241            Some(b) => DbgDocBldr::option(before) + b + DbgDocBldr::option(after),
242        }
243    }
244
245    pub fn preceded_option(
246        before: Option<DebugDocBuilder>,
247        builder: Option<DebugDocBuilder>,
248    ) -> DebugDocBuilder {
249        DebugDocBuilder::surrounded_option(before, builder, None)
250    }
251
252    pub fn option(builder: Option<DebugDocBuilder>) -> DebugDocBuilder {
253        builder.unwrap_or_else(DebugDocBuilder::blank)
254    }
255
256    pub fn space() -> DebugDocBuilder {
257        BoxAllocator.space().into()
258    }
259
260    pub fn newline() -> DebugDocBuilder {
261        BoxAllocator.newline().into()
262    }
263
264    pub fn is_empty(&self) -> bool {
265        matches!(&self.inner.1, pretty::Doc::Nil)
266    }
267
268    pub fn or(self, doc: DebugDocBuilder) -> DebugDocBuilder {
269        if self.is_empty() {
270            doc
271        } else {
272            self
273        }
274    }
275
276    pub fn group(self) -> DebugDocBuilder {
277        self.inner.group().into()
278    }
279
280    pub fn nest(self) -> DebugDocBuilder {
281        self.inner.nest(1).group().into()
282    }
283
284    pub fn intersperse_with_source<'a, T: PrettyDebugWithSource + 'a>(
285        list: impl IntoIterator<Item = &'a T>,
286        separator: DebugDocBuilder,
287        source: &str,
288    ) -> DebugDocBuilder {
289        BoxAllocator
290            .intersperse(
291                list.into_iter().filter_map(|item| {
292                    let item = item.pretty_debug(source);
293                    if item.is_empty() {
294                        None
295                    } else {
296                        Some(item)
297                    }
298                }),
299                separator,
300            )
301            .into()
302    }
303
304    pub fn intersperse<T: PrettyDebug>(
305        list: impl IntoIterator<Item = T>,
306        separator: DebugDocBuilder,
307    ) -> DebugDocBuilder {
308        BoxAllocator
309            .intersperse(
310                list.into_iter().filter_map(|item| {
311                    let item = item.pretty();
312                    if item.is_empty() {
313                        None
314                    } else {
315                        Some(item)
316                    }
317                }),
318                separator,
319            )
320            .into()
321    }
322
323    pub fn list(list: impl IntoIterator<Item = DebugDocBuilder>) -> DebugDocBuilder {
324        let mut result: DebugDocBuilder = BoxAllocator.nil().into();
325
326        for item in list {
327            result = result + item;
328        }
329
330        result
331    }
332
333    fn styled(string: impl std::fmt::Display, style: ShellStyle) -> DebugDocBuilder {
334        BoxAllocator
335            .text(string.to_string())
336            .annotate(ShellAnnotation::style(style))
337            .into()
338    }
339}
340
341impl std::ops::Deref for DebugDocBuilder {
342    type Target = PrettyDebugDocBuilder;
343
344    fn deref(&self) -> &Self::Target {
345        &self.inner
346    }
347}
348
349#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, new)]
350pub struct DebugDoc {
351    pub inner: PrettyDebugDoc,
352}
353
354#[derive(Debug, Copy, Clone)]
355pub enum PrettyDebugRefineKind {
356    ContextFree,
357    WithContext,
358}
359
360pub trait PrettyDebugWithSource: Sized {
361    fn pretty_debug(&self, source: &str) -> DebugDocBuilder;
362
363    fn refined_pretty_debug(
364        &self,
365        _refine: PrettyDebugRefineKind,
366        source: &str,
367    ) -> DebugDocBuilder {
368        self.pretty_debug(source)
369    }
370
371    // This is a transitional convenience method
372    fn debug(&self, source: impl Into<Text>) -> String
373    where
374        Self: Clone,
375    {
376        self.clone().debuggable(source).display()
377    }
378
379    fn debuggable(self, source: impl Into<Text>) -> DebuggableWithSource<Self> {
380        DebuggableWithSource {
381            inner: self,
382            source: source.into(),
383        }
384    }
385}
386
387impl<T: PrettyDebug> PrettyDebug for Spanned<T> {
388    fn pretty(&self) -> DebugDocBuilder {
389        self.item.pretty()
390    }
391}
392
393impl<T: PrettyDebug> PrettyDebugWithSource for T {
394    fn pretty_debug(&self, _source: &str) -> DebugDocBuilder {
395        self.pretty()
396    }
397}
398
399impl<T: PrettyDebugWithSource, E> PrettyDebugWithSource for Result<T, E> {
400    fn pretty_debug(&self, source: &str) -> DebugDocBuilder {
401        match self {
402            Err(_) => DbgDocBldr::error("error"),
403            Ok(val) => val.pretty_debug(source),
404        }
405    }
406}
407
408pub struct DebuggableWithSource<T: PrettyDebugWithSource> {
409    inner: T,
410    source: Text,
411}
412
413impl<T> PrettyDebug for DebuggableWithSource<T>
414where
415    T: PrettyDebugWithSource,
416{
417    fn pretty(&self) -> DebugDocBuilder {
418        self.inner.pretty_debug(&self.source)
419    }
420}
421
422impl PrettyDebug for DebugDoc {
423    fn pretty(&self) -> DebugDocBuilder {
424        DebugDocBuilder::new(BoxAllocator.nil().append(self.inner.clone()))
425    }
426}
427
428pub trait PrettyDebug {
429    fn pretty(&self) -> DebugDocBuilder;
430
431    fn to_doc(&self) -> DebugDoc {
432        DebugDoc::new(self.pretty().into())
433    }
434
435    fn pretty_doc(&self) -> PrettyDebugDoc {
436        let builder = self.pretty();
437        builder.inner.into()
438    }
439
440    fn pretty_builder(&self) -> PrettyDebugDocBuilder {
441        let doc = self.pretty();
442        doc.inner
443    }
444
445    /// A convenience method that prints out the document without colors in
446    /// 70 columns. Generally, you should use plain_string or colored_string
447    /// if possible, but display() can be useful for trace lines and things
448    /// like that, where you don't have control over the terminal.
449    fn display(&self) -> String {
450        self.plain_string(70)
451    }
452
453    fn plain_string(&self, width: usize) -> String {
454        let doc = self.pretty_doc();
455        let mut buffer = termcolor::Buffer::no_color();
456
457        let _ = doc.render_raw(width, &mut TermColored::new(&mut buffer));
458
459        String::from_utf8_lossy(buffer.as_slice()).to_string()
460    }
461
462    fn colored_string(&self, width: usize) -> String {
463        let doc = self.pretty_doc();
464        let mut buffer = termcolor::Buffer::ansi();
465
466        let _ = doc.render_raw(width, &mut TermColored::new(&mut buffer));
467
468        String::from_utf8_lossy(buffer.as_slice()).to_string()
469    }
470}
471
472impl From<PrettyDebugDocBuilder> for DebugDocBuilder {
473    fn from(x: PrettyDebugDocBuilder) -> Self {
474        DebugDocBuilder { inner: x }
475    }
476}
477
478impl std::ops::Deref for DebugDoc {
479    type Target = PrettyDebugDoc;
480
481    fn deref(&self) -> &Self::Target {
482        &self.inner
483    }
484}
485
486impl From<DebugDoc> for PrettyDebugDoc {
487    fn from(input: DebugDoc) -> PrettyDebugDoc {
488        input.inner
489    }
490}
491
492impl From<DebugDocBuilder> for PrettyDebugDoc {
493    fn from(x: DebugDocBuilder) -> Self {
494        x.inner.into()
495    }
496}
497
498fn hash_doc<H: std::hash::Hasher>(doc: &PrettyDebugDoc, state: &mut H) {
499    match doc {
500        pretty::Doc::Nil => 0u8.hash(state),
501        pretty::Doc::Append(a, b) => {
502            1u8.hash(state);
503            hash_doc(a, state);
504            hash_doc(b, state);
505        }
506        pretty::Doc::Group(a) => {
507            2u8.hash(state);
508            hash_doc(a, state);
509        }
510        pretty::Doc::Nest(a, b) => {
511            3u8.hash(state);
512            a.hash(state);
513            hash_doc(b, state);
514        }
515        pretty::Doc::Space => 4u8.hash(state),
516        pretty::Doc::Newline => 5u8.hash(state),
517        pretty::Doc::Text(t) => {
518            6u8.hash(state);
519            t.hash(state);
520        }
521        pretty::Doc::Annotated(a, b) => {
522            7u8.hash(state);
523            a.hash(state);
524            hash_doc(b, state);
525        }
526    }
527}
528
529#[allow(clippy::derive_hash_xor_eq)]
530impl std::hash::Hash for DebugDoc {
531    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
532        hash_doc(&self.inner, state);
533    }
534}