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 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 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}