source_span/
fmt.rs

1//! Source text formatter with span highlights and notes.
2//!
3//! Here are the kind of things you can produce with the
4//! [`Formatter`] (without colors):
5//! ```text
6//!  1 | /     pub fn fibonacci(n: i32) -> u64 {
7//!    | |                     ^______^        ^
8//!    | |  __________________________|________|
9//!    | | |                          |
10//!    | | |                          this is a pair of parenthesis
11//!  2 | | |       if n < 0 {
12//!    | | |  ______________^
13//!  3 | | | |         panic!("{} is negative!", n);
14//!    | | | |               ^"^^             "   ^ this is a pair of parenthesis
15//!    | | | |               ||_|_____________|   |
16//!    | | | |               |__|_____________|___|
17//!    | | | |                  |             |
18//!    | | | |                  |             this is a string
19//!    | | | |                  |
20//!    | | | |                  this is a pair of braces
21//!  4 | | | |     } else if n == 0 {
22//!    | | | |_____^                ^
23//!    | | |  _____|________________|
24//!    | | | |     |
25//!    | | | |     this is a pair of braces
26//!  5 | | | |         panic!("zero is not a right argument to fibonacci()!");
27//!    | | | |               ^"                                         ^^ "^ parentheses
28//!    | | | |               ||__________________________________________|_||
29//!    | | | |               |___________________________________________|_||
30//!    | | | |                                                           | |
31//!    | | | |                                                           | this is a string
32//!    | | | |                                                           |
33//!    | | | |                                                           parentheses
34//!  6 | | | |     } else if n == 1 {
35//!    | | | |_____^                ^
36//!    | | |       |                |
37//!    | | |  _____|________________|
38//!    | | | |     |
39//!    | | | |     this is a pair of braces
40//!  7 | | | |         return 1;
41//!  8 | | | |     }
42//!    | | | |_____^ this is a pair of braces
43//!  9 | | |
44//! 10 | | |       let mut sum = 0;
45//! 11 | | |       let mut last = 0;
46//! 12 | | |       let mut curr = 1;
47//! 13 | | |       for _i in 1..n {
48//!    | | |  ____________________^
49//! 14 | | | |         sum = last + curr;
50//! 15 | | | |         last = curr;
51//! 16 | | | |         curr = sum;
52//! 17 | | | |     }
53//!    | | | |_____^ this is a pair of braces
54//! 18 | | |       sum
55//! 19 | | |   }
56//!    | | |___^^ this is the whole program
57//!    | |_____|| what a nice program!
58//!    |       |
59//!    |       this is a pair of braces
60//! 20 |
61//! ```
62
63use crate::{Metrics, Position, Span};
64use std::fmt;
65
66/// Colors used to render the text.
67#[cfg(feature = "colors")]
68#[derive(Clone, Copy, PartialEq, Eq, Debug)]
69pub enum Color {
70	Red,
71	Green,
72	Blue,
73	Magenta,
74	Yellow,
75	Cyan,
76}
77
78#[cfg(feature = "colors")]
79impl termion::color::Color for Color {
80	fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
81		match self {
82			Self::Red => termion::color::LightRed.write_fg(f),
83			Self::Green => termion::color::LightGreen.write_fg(f),
84			Self::Blue => termion::color::LightBlue.write_fg(f),
85			Self::Magenta => termion::color::LightMagenta.write_fg(f),
86			Self::Yellow => termion::color::LightYellow.write_fg(f),
87			Self::Cyan => termion::color::LightCyan.write_fg(f),
88		}
89	}
90
91	fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
92		match self {
93			Self::Red => termion::color::LightRed.write_bg(f),
94			Self::Green => termion::color::LightGreen.write_bg(f),
95			Self::Blue => termion::color::LightBlue.write_bg(f),
96			Self::Magenta => termion::color::LightMagenta.write_bg(f),
97			Self::Yellow => termion::color::LightYellow.write_bg(f),
98			Self::Cyan => termion::color::LightCyan.write_bg(f),
99		}
100	}
101}
102
103#[cfg(not(feature = "colors"))]
104pub type Color = ();
105
106/// Highlight format description.
107///
108/// Specifies how the highlight should be rendered:
109///  * What character to use to underline highlights.
110/// ```txt
111/// 1 | fn main() {
112/// 2 |     println!("Hello World!")
113///   |              ^++++++++++++^ highlighting this string
114/// 3 | }
115/// ```
116/// In this example, the underline character is `+`.
117///
118///  * What boundary marker character to use to point the first and last
119///    elements of a highlight.
120/// ```txt
121/// 1 |   fn main() {
122///   |  ___________^
123/// 2 | |     println!("Hello World!")
124/// 3 | | }
125///   | |_^ this span covers more than one line
126/// ```
127/// In this example, the boundary marker is `^`.
128/// Note that the underline character is not used.
129///
130/// ## Colors
131///
132/// If the `colors` feature is enabled, it is also possible to set a color to
133/// draw the lines. This will also make the highlights more bright (or bold),
134/// along with the line numbers.
135#[derive(Clone, Copy)]
136pub enum Style {
137	/// Red curvy underline.
138	Error,
139
140	/// Yellow curvy underline.
141	Warning,
142
143	/// Blue straight underline.
144	Note,
145
146	/// Green straight underline.
147	Help,
148
149	/// Custom highlight format.
150	///
151	/// Specifies the underline character, the boundary marker and the color (if
152	/// the `colors` feature is enabled) used to render the highlight.
153	Custom(char, char, Color),
154}
155
156impl Style {
157	/// Create a new custom highlight style.
158	///
159	/// The `line` character is user to draw the line under the span elements.
160	/// The `marker` character is used to point to the first and last elements
161	/// of the span when relevant.
162	#[must_use]
163	#[cfg(not(feature = "colors"))]
164	pub const fn new(underline: char, marker: char) -> Self { Self::Custom(underline, marker, ()) }
165
166	/// Create a new custom highlight style.
167	///
168	/// The `line` character is user to draw the line under the highlighted
169	/// sections. The `marker` character is used to point to the first and last
170	/// elements of the section when relevant.
171	#[must_use]
172	#[cfg(feature = "colors")]
173	pub const fn new(line: char, marker: char, color: Color) -> Self {
174		Self::Custom(line, marker, color)
175	}
176
177	/// The character used to underline the highlighted section.
178	#[must_use]
179	pub fn line(&self) -> char {
180		match self {
181			Self::Error | Self::Warning => '^',
182			Self::Note | Self::Help => '-',
183			Self::Custom(line, _, _) => *line,
184		}
185	}
186
187	/// The character used to point the first and last element of the span when
188	/// relevant.
189	#[must_use]
190	pub fn marker(&self) -> char {
191		match self {
192			Self::Error | Self::Warning => '^',
193			Self::Note | Self::Help => '-',
194			Self::Custom(_, marker, _) => *marker,
195		}
196	}
197
198	/// Get the color used to draw the highlight.
199	#[must_use]
200	pub fn color(&self) -> Color {
201		#[cfg(not(feature = "colors"))]
202		{
203			()
204		}
205		#[cfg(feature = "colors")]
206		{
207			match self {
208				Self::Error => Color::Red,
209				Self::Warning => Color::Yellow,
210				Self::Note => Color::Blue,
211				Self::Help => Color::Green,
212				Self::Custom(_, _, color) => *color,
213			}
214		}
215	}
216}
217
218/// Text highlight.
219///
220/// Defines what should be highlighted in the text formatted with the
221/// [`Formatter`].
222/// A highlight is composed of a span a label and a style.
223/// The formatter will underline the text in the given span and draw the label's
224/// text on its side, with the given style.
225///
226/// ```txt
227/// 1 | fn main() {
228/// 2 |     println!("Hello World!")
229///   |              ^^^^^^^^^^^^^^ highlighting this string
230/// 3 | }
231/// ```
232/// # Highlight spanning multiple lines
233///
234/// The highlight span can cover multiple lines.
235/// In that case, only the first and last elements will be decorated using the
236/// style's marker charatcer (`^` in the below example).
237///
238/// ```txt
239/// 1 |   fn main() {
240///   |  ___________^
241/// 2 | |     println!("Hello World!")
242/// 3 | | }
243///   | |_^ this span covers more than one line
244/// ```
245///
246/// # Entangled highlights
247///
248/// Different highlights can overlap without breaking the formatted output, but
249/// it may become difficult to read the exact boundary of each highlight.
250///
251/// ```txt
252/// 1 |   fn main() {
253///   |          __ ^
254///   |  _________|_|
255///   | |         |
256///   | |         this is a pair of parenthesis
257/// 2 | |     println!("Hello World!")
258///   | |             ^^^^^^^^^^^^^^^^ this is a pair of parenthesis
259///   | |             |_____________||
260///   | |                           |
261///   | |                           this is a string. Hard to see where it starts, uh?
262/// 3 | | }
263///   | |_^ this is a pair of braces
264/// ```
265///
266/// Here the underline character for the string is the same as the boundary
267/// marker for the parenthesis, making it hard to see which is which.
268/// One possible workaround is to change the [`Style`] of the highlights.
269/// Changing the boundary marker for the parenthesis to `|` makes it easier to
270/// read the formatted output:
271///
272/// ```txt
273/// 1 |   fn main() {
274///   |          __ ^
275///   |  _________|_|
276///   | |         |
277///   | |         this is a pair of parenthesis
278/// 2 | |     println!("Hello World!")
279///   | |             |^^^^^^^^^^^^^^| this is a pair of parenthesis
280///   | |             |_____________||
281///   | |                           |
282///   | |                           this is a string. Hard to see where it starts, uh?
283/// 3 | | }
284///   | |_^ this is a pair of braces
285/// ```
286pub struct Highlight {
287	span: Span,
288	label: Option<String>,
289	style: Style,
290}
291
292impl Highlight {
293	/// Compute the "margin nesting level" of the highlight.
294	///
295	/// The "margin nesting level" for multiline highlights correspond to the
296	/// horizontal position of the vertical bar in the margin linking the
297	/// begining and the end of the highlight. In the example below, one
298	/// highlight has a margin nesting level of 1 (further in), and the other of
299	/// 2 (further out).
300	///
301	/// ```text
302	///    ___^ ^
303	///  _|_____|
304	/// | |
305	/// | |
306	/// |_|________^   ^
307	///   |____________|
308	/// ```
309	///
310	/// Single line highlights have a margin nesting level of 0.
311	///
312	/// The nesting level is computed relatively to other mapped highlights that
313	/// have a lower precedence.
314	fn margin_nest_level(&self, highlights: &[MappedHighlight]) -> usize {
315		if self.span.line_count() > 1 {
316			let mut level = 2;
317			for h in highlights {
318				if self.span.overlaps(h.span()) {
319					level = std::cmp::max(level, 2 + h.margin_nest_level)
320				}
321			}
322
323			level
324		} else {
325			0
326		}
327	}
328
329	fn start_nest_level(
330		&self,
331		highlights: &[MappedHighlight],
332		first_non_whitespace: Option<usize>,
333	) -> usize {
334		if self.span.last.line > self.span.start.line
335			&& first_non_whitespace.is_some()
336			&& first_non_whitespace.unwrap() >= self.span.start.column
337		{
338			0
339		} else {
340			let mut level = 1;
341			for h in highlights {
342				if (self.span.start.line == h.span().start.line
343					|| self.span.start.line == h.span().last.line)
344					&& (self.span.overlaps(h.span()) || self.span.line_count() > 1)
345				{
346					level = std::cmp::max(level, 1 + h.start_nest_level)
347				}
348			}
349
350			level
351		}
352	}
353
354	fn end_nest_level(&self, highlights: &[MappedHighlight]) -> usize {
355		let mut level = 1;
356		for h in highlights {
357			if (self.span.last.line == h.span().start.line
358				|| self.span.last.line == h.span().last.line)
359				&& self.span.overlaps(h.span())
360			{
361				level = std::cmp::max(level, 1 + h.end_nest_level)
362			}
363		}
364
365		level
366	}
367}
368
369/// Text formatter with span highlights.
370///
371/// This allows you to format a given input `char` stream with highlights and
372/// colors (if the `colors` feature is enabled).
373/// A [`Highlight`] is defined by a [`Span`], a string label and a [`Style`],
374/// and will be rendered with the stream:
375///
376/// ```txt
377/// 1 | fn main() {
378/// 2 |     println!("Hello World!")
379///   |              ^^^^^^^^^^^^^^ highlighting this string
380/// 3 | }
381/// ```
382///
383/// Highlights spans can cover multiple lines and overlap.
384/// See the [`Highlight`] documentation for more informations.
385pub struct Formatter {
386	highlights: Vec<Highlight>,
387	margin_color: Color,
388	show_line_numbers: bool,
389	use_line_begining_shortcut: bool,
390	viewbox: Option<usize>,
391}
392
393impl Formatter {
394	/// Create a new formatter with no highlights.
395	///
396	/// # Note
397	///
398	/// By default line numbers are shown. You can disable them using the
399	/// [`hide_line_numbers`](Formatter::hide_line_numbers) method.
400	#[must_use]
401	pub fn new() -> Self { Self::default() }
402
403	/// Create a new formatter with no highlights and the specified margin
404	/// color.
405	///
406	/// # Note
407	///
408	/// By default line numbers are shown. You can disable them using the
409	/// [`hide_line_numbers`](Formatter::hide_line_numbers) method.
410	#[must_use]
411	pub const fn with_margin_color(margin_color: Color) -> Self {
412		Self {
413			highlights: Vec::new(),
414			margin_color,
415			viewbox: Some(2),
416			show_line_numbers: true,
417			use_line_begining_shortcut: true,
418		}
419	}
420
421	/// By default, line numbers are shown in a margin in the left side of the
422	/// rendered text, like this:
423	/// ```text
424	/// 1 | fn main() {
425	/// 2 |     println!("Hello World!")
426	///   |              ^^^^^^^^^^^^^^ highlighting this string
427	/// 3 | }
428	/// ```
429	/// The `margin_color` attribute is used to decorate the margin text (blue
430	/// by default). You can use this function to enable or disable this
431	/// functionality. Without line numbers, the previous example will look like
432	/// this:
433	///
434	/// ```text
435	/// fn main() {
436	///     println!("Hello World!")
437	///              ^^^^^^^^^^^^^^ highlighting this string
438	/// }
439	/// ```
440	pub fn set_line_numbers_visible(&mut self, visible: bool) { self.show_line_numbers = visible; }
441
442	/// Show the line numbers (this is the default).
443	pub fn show_line_numbers(&mut self) { self.show_line_numbers = false; }
444
445	/// Hide the line numbers.
446	pub fn hide_line_numbers(&mut self) { self.show_line_numbers = false; }
447
448	/// Set the viewbox (default is 2).
449	///
450	/// The viewbox is used to ommit non-important lines from the render.
451	/// A line is considered important if it is included at the start or end of
452	/// an highlighted span.
453	/// In the below example, lines 1 and 12 are important. With a viewport of
454	/// 2, the two lines around important lines will be visible, and the other
455	/// ommited. In this example, lines 4 to 9 are ommited.
456	///
457	/// ```text
458	///  1 |   fn main() {
459	///    |  __________^
460	///  2 | |     some code;
461	///  3 | |     more code;
462	/// .. | |
463	/// 10 | |     more code;
464	/// 11 | |     more code
465	/// 12 | | }
466	///    | |_^
467	/// 13 |
468	/// 14 |   fn another_function {
469	/// ```
470	/// Here is the same example with a viewbox of 1:
471	/// ```text
472	///  1 |   fn main() {
473	///    |  __________^
474	///  2 | |     some code;
475	/// .. | |
476	/// 11 | |     more code
477	/// 12 | | }
478	///    | |_^
479	/// 13 |
480	/// ```
481	///
482	/// You can disable the viewbox all together by passing `None` to this
483	/// function. In this case, all the lines will be visible.
484	pub fn set_viewbox(&mut self, viewbox: Option<usize>) { self.viewbox = viewbox }
485
486	/// Add a span highlight.
487	pub fn add(&mut self, span: Span, label: Option<String>, style: Style) {
488		self.highlights.push(Highlight { span, label, style });
489		self.highlights.sort_by(|a, b| a.span.cmp(&b.span));
490	}
491
492	/// Returns the smallest span including every highlights.
493	#[must_use]
494	pub fn span(&self) -> Option<Span> {
495		let mut span: Option<Span> = None;
496
497		for h in &self.highlights {
498			span = Some(span.map(|s| s.union(h.span)).unwrap_or(h.span))
499		}
500
501		span
502	}
503}
504
505/// Highlight with some more information about how to draw the lines.
506#[derive(Clone, Copy)]
507struct MappedHighlight<'a> {
508	h: &'a Highlight,
509	margin_nest_level: usize,
510	start_nest_level: usize,
511	end_nest_level: usize,
512}
513
514impl<'a> MappedHighlight<'a> {
515	pub const fn span(&self) -> &Span { &self.h.span }
516
517	pub const fn style(&self) -> &Style { &self.h.style }
518
519	pub const fn label(&self) -> Option<&String> { self.h.label.as_ref() }
520
521	fn update_start_nest_level(
522		&mut self,
523		highlights: &[MappedHighlight],
524		first_non_whitespace: Option<usize>,
525	) {
526		self.start_nest_level = self.h.start_nest_level(highlights, first_non_whitespace)
527	}
528
529	fn update_end_nest_level(&mut self, highlights: &[MappedHighlight]) {
530		self.end_nest_level = self.h.end_nest_level(highlights)
531	}
532}
533
534/// Character with style information.
535#[derive(Clone, Copy)]
536pub enum Char {
537	Empty,
538	Text(char),
539	Margin(char, Color),
540	Label(char, Color),
541	SpanMarker(char, Color),
542	SpanUnderline(char, Color),
543	SpanVertical(Color),
544	SpanHorizontal(Color),
545	SpanMargin(Color),
546	SpanMarginMarker(Color),
547}
548
549impl Char {
550	const fn unwrap(self) -> char {
551		match self {
552			Self::Empty => ' ',
553			Self::Text(c)
554			| Self::Margin(c, _)
555			| Self::Label(c, _)
556			| Self::SpanUnderline(c, _)
557			| Self::SpanMarker(c, _) => c,
558			Self::SpanVertical(_) => '|',
559			Self::SpanHorizontal(_) => '_',
560			Self::SpanMargin(_) => '|',
561			Self::SpanMarginMarker(_) => '/',
562		}
563	}
564
565	#[cfg(feature = "colors")]
566	const fn color(&self) -> Option<Color> {
567		match self {
568			Self::Empty | Self::Text(_) => None,
569			Self::Margin(_, color)
570			| Self::Label(_, color)
571			| Self::SpanUnderline(_, color)
572			| Self::SpanMarker(_, color)
573			| Self::SpanVertical(color)
574			| Self::SpanHorizontal(color)
575			| Self::SpanMargin(color) | Self::SpanMarginMarker(color) => Some(*color),
576		}
577	}
578
579	const fn is_free(&self) -> bool {
580		match self {
581			Self::Empty => true,
582			_ => false,
583		}
584	}
585
586	#[allow(clippy::trivially_copy_pass_by_ref)]
587	const fn is_span_horizontal(&self) -> bool {
588		match self {
589			Self::SpanHorizontal(_) => true,
590			_ => false,
591		}
592	}
593
594	#[allow(clippy::trivially_copy_pass_by_ref)]
595	const fn is_span_margin(&self) -> bool {
596		match self {
597			Self::SpanMargin(_) => true,
598			_ => false,
599		}
600	}
601}
602
603impl From<char> for Char {
604	fn from(c: char) -> Self { Self::Text(c) }
605}
606
607/// A 2D character map.
608struct CharMap {
609	data: Vec<Char>,
610	width: usize,
611	height: usize,
612}
613
614impl CharMap {
615	fn new() -> CharMap {
616		CharMap {
617			data: vec![Char::Empty],
618			width: 1,
619			height: 1,
620		}
621	}
622
623	fn from_label<M: Metrics>(text: &str, color: Color, metrics: &M) -> CharMap {
624		let mut map = CharMap {
625			data: Vec::with_capacity(text.len()),
626			width: 0,
627			height: 0,
628		};
629
630		let mut pos = Position::new(0, 0);
631		for c in text.chars() {
632			match c {
633				'\n' | '\t' => (),
634				_ => map.set(pos.column, pos.line, Char::Label(c, color)),
635			}
636
637			pos.shift(c, metrics)
638		}
639
640		map
641	}
642
643	// fn width(&self) -> usize { self.width }
644
645	fn height(&self) -> usize { self.height }
646
647	fn align<I: Iterator<Item = usize>>(&mut self, width: usize, _height: usize, it: I) {
648		for i in it {
649			let x = i % width;
650			let y = i / width;
651
652			if x < self.width {
653				if y < self.height {
654					let j = x + y * self.width;
655					self.data[i] = self.data[j];
656				} else {
657					let my = self.height - 1;
658					self.data[i] = match (self.get(x, my), self.get(x + 1, my)) {
659						(Char::SpanMargin(_), Char::SpanHorizontal(_))
660							if x == 0 || !self.get(x - 1, my).is_span_horizontal() =>
661						{
662							Char::Empty
663						}
664						(Char::SpanMargin(c), _) => Char::SpanMargin(c),
665						(Char::SpanMarginMarker(c), _) => Char::SpanMargin(c),
666						(Char::Empty, Char::SpanHorizontal(c)) => Char::SpanMargin(c),
667						(Char::Margin('|', c), _) => Char::Margin('|', c),
668						_ => Char::Empty,
669					}
670				}
671			} else {
672				self.data[i] = Char::Empty
673			}
674		}
675	}
676
677	fn resize(&mut self, width: usize, height: usize) {
678		let len = width * height;
679
680		if len != self.data.len() {
681			if len > self.data.len() {
682				self.data.resize(len, Char::Empty);
683			}
684
685			if width < self.width {
686				self.align(width, height, 0..len);
687			} else {
688				self.align(width, height, (0..len).rev());
689			}
690
691			if len < self.data.len() {
692				self.data.resize(len, Char::Empty);
693			}
694
695			self.width = width;
696			self.height = height;
697		}
698	}
699
700	fn reserve(&mut self, width: usize, height: usize) {
701		self.resize(
702			std::cmp::max(width, self.width),
703			std::cmp::max(height, self.height),
704		)
705	}
706
707	fn get(&self, x: usize, y: usize) -> Char {
708		if x >= self.width || y >= self.height {
709			Char::Empty
710		} else {
711			self.data[x + y * self.width]
712		}
713	}
714
715	fn set(&mut self, x: usize, y: usize, c: Char) {
716		self.reserve(x + 1, y + 1);
717		self.data[x + y * self.width] = c;
718	}
719
720	fn draw_marker(&mut self, style: &Style, y: usize, x: usize) {
721		let mut head = false;
722		for j in 1..=y {
723			let previous_c = self.get(x, j);
724			if previous_c.is_free() || previous_c.is_span_horizontal() {
725				let c = if head {
726					Char::SpanVertical(style.color())
727				} else {
728					head = true;
729					Char::SpanMarker(style.marker(), style.color())
730				};
731
732				self.set(x, j, c);
733			}
734		}
735	}
736
737	fn draw_open_line(&mut self, style: &Style, y: usize, start: usize, end: usize) {
738		self.reserve(end + 1, y + 1);
739		for x in start..=end {
740			if x == end {
741				self.draw_marker(style, y, x)
742			} else {
743				if !self.get(x, y).is_span_margin() {
744					self.set(x, y, Char::SpanHorizontal(style.color()))
745				}
746			}
747		}
748	}
749
750	fn draw_closed_line(&mut self, style: &Style, y: usize, start: usize, end: usize) {
751		self.reserve(end + 1, y + 1);
752		for x in start..=end {
753			if x == start || x == end {
754				self.draw_marker(style, y, x)
755			} else {
756				let c = if y == 1 {
757					Char::SpanUnderline(style.line(), style.color())
758				} else {
759					Char::SpanHorizontal(style.color())
760				};
761
762				self.set(x, y, c)
763			}
764		}
765	}
766
767	/// Checks if the given rectangle is free in the char map.
768	fn is_rect_free(&self, offset_x: usize, offset_y: usize, width: usize, height: usize) -> bool {
769		for y in offset_y..(offset_y + height) {
770			for x in offset_x..(offset_x + width) {
771				if !self.get(x, y).is_free() {
772					return false;
773				}
774			}
775		}
776
777		true
778	}
779
780	fn draw_charmap(&mut self, offset_x: usize, offset_y: usize, map: &CharMap) {
781		self.reserve(offset_x + map.width, offset_y + map.height);
782		for y in 0..map.height {
783			for x in 0..map.width {
784				self.set(offset_x + x, offset_y + y, map.get(x, y))
785			}
786		}
787	}
788
789	fn draw_charmap_if_free(&mut self, offset_x: usize, offset_y: usize, map: &CharMap) -> bool {
790		let mut dx = 0;
791		let mut dy = 0;
792
793		if offset_x > 0 {
794			dx = 1;
795		}
796
797		if offset_y > 1 {
798			dy = 1;
799		}
800
801		if self.is_rect_free(
802			offset_x - dx,
803			offset_y - dy,
804			map.width + dx + 1,
805			map.height + dy + 1,
806		) {
807			self.draw_charmap(offset_x, offset_y, map);
808			true
809		} else {
810			false
811		}
812	}
813}
814
815impl fmt::Display for CharMap {
816	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
817		#[cfg(feature = "colors")]
818		let mut current_color = None;
819		for y in 0..self.height {
820			for x in 0..self.width {
821				let i = x + y * self.width;
822				let c = self.data[i];
823				#[cfg(feature = "colors")]
824				{
825					if c.color() != current_color && !c.is_free() {
826						current_color = c.color();
827						if let Some(color) = current_color {
828							write!(f, "{}{}", termion::style::Bold, termion::color::Fg(color))?;
829						} else {
830							write!(f, "{}", termion::style::Reset)?;
831						}
832					}
833				}
834				c.unwrap().fmt(f)?;
835			}
836			write!(f, "\n")?;
837		}
838
839		#[cfg(feature = "colors")]
840		write!(f, "{}", termion::style::Reset)?;
841
842		Ok(())
843	}
844}
845
846/// Formatted text.
847///
848/// This is the result of the [`Formatter::render`] function.
849/// It implements [`Display`](`fmt::Display`) and can hence be printted with a simple `printf!`.
850pub struct Formatted(Vec<CharMap>);
851
852impl fmt::Display for Formatted {
853	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
854		for map in &self.0 {
855			map.fmt(f)?;
856		}
857
858		Ok(())
859	}
860}
861
862/// A set of important lines to render.
863pub enum ImportantLines {
864	All,
865	Lines(Vec<usize>, usize),
866}
867
868impl ImportantLines {
869	fn includes(&self, line: usize) -> bool {
870		match self {
871			ImportantLines::All => true,
872			ImportantLines::Lines(important_lines, viewbox) => important_lines
873				.binary_search_by(|candidate| {
874					use std::cmp::Ordering;
875					if line <= candidate + viewbox
876						&& line >= candidate - std::cmp::min(candidate, viewbox)
877					{
878						Ordering::Equal
879					} else if line <= candidate + viewbox {
880						Ordering::Greater
881					} else {
882						Ordering::Less
883					}
884				})
885				.is_ok(),
886		}
887	}
888}
889
890impl Formatter {
891	fn important_lines(&self) -> ImportantLines {
892		if let Some(viewbox) = self.viewbox {
893			let mut important_lines = Vec::new();
894			for h in &self.highlights {
895				important_lines.push(h.span.start.line);
896				if h.span.start.line != h.span.last.line {
897					important_lines.push(h.span.last.line)
898				}
899			}
900
901			important_lines.sort_unstable();
902			ImportantLines::Lines(important_lines, viewbox)
903		} else {
904			ImportantLines::All
905		}
906	}
907
908	/// Get the margin length (containing the line numbers) for the given span.
909	/// 
910	/// If line numbers are disabled, this will return 0.
911	#[must_use]
912	pub fn margin_len(&self, span: &Span) -> usize {
913		if self.show_line_numbers {
914			let last_line = match self.viewbox {
915				Some(viewbox) => {
916					if let Some(last_highlight) = self.highlights.last() {
917						last_highlight.span.last().line + viewbox
918					} else {
919						return 0
920					}
921				},
922				None => span.last().line
923			};
924
925			(((last_line + 1) as f32).log10() as usize) + 4
926		} else {
927			0
928		}
929	}
930
931	/// Render the given input stream of character.
932	/// The result implements [`Display`](`fmt::Display`) and can then be printed.
933	///
934	/// ```
935	/// # use std::fs::File;
936	/// # use std::io::Read;
937	/// # use source_span::{DEFAULT_METRICS, SourceBuffer, Position};
938	/// # use source_span::fmt::{Color, Style, Formatter};
939	/// let file = File::open("examples/fib.txt").unwrap();
940	/// let chars = utf8_decode::UnsafeDecoder::new(file.bytes());
941	/// let metrics = DEFAULT_METRICS;
942	/// let buffer = SourceBuffer::new(chars, Position::default(), metrics);
943	///
944	/// let mut fmt = Formatter::with_margin_color(Color::Blue);
945	/// fmt.add(buffer.span(), None, Style::Error);
946	///
947	/// let formatted = fmt.render(buffer.iter(), buffer.span(), &metrics).unwrap();
948	/// println!("{}", formatted);
949	/// ```
950	pub fn render<E, I: Iterator<Item = Result<char, E>>, M: Metrics>(
951		&self,
952		input: I,
953		span: Span,
954		metrics: &M,
955	) -> Result<Formatted, E> {
956		let mut mapped_highlights = Vec::with_capacity(self.highlights.len());
957		let mut nest_margin = 0;
958		for h in &self.highlights {
959			let margin_nest_level = h.margin_nest_level(&mapped_highlights);
960			// let start_nest_level = 0;
961			// let end_nest_level = h.end_nest_level(&mapped_highlights);
962
963			if margin_nest_level > nest_margin {
964				nest_margin = margin_nest_level;
965			}
966
967			mapped_highlights.push(MappedHighlight {
968				h,
969				margin_nest_level,
970				start_nest_level: 0,
971				end_nest_level: 0,
972			});
973		}
974
975		let margin_len = self.margin_len(&span);
976		let margin = margin_len + nest_margin;
977
978		let mut pos = span.start();
979		let mut lines = vec![CharMap::new()];
980		let important_lines = self.important_lines();
981		let mut is_important_line = important_lines.includes(pos.line);
982		if is_important_line {
983			lines.push(CharMap::new())
984		}
985		let mut first_non_whitespace = None;
986		for c in input {
987			if pos > span.last() {
988				break;
989			}
990
991			let c = c?;
992			let x = margin + pos.column;
993
994			match c {
995				'\n' => {
996					if is_important_line {
997						let line_charmap = lines.last_mut().unwrap();
998						self.draw_line_number(Some(pos.line), line_charmap, margin_len);
999						self.draw_line_highlights(
1000							pos.line,
1001							line_charmap,
1002							margin,
1003							&mut mapped_highlights,
1004							metrics,
1005							first_non_whitespace,
1006						);
1007					}
1008					first_non_whitespace = None;
1009					if important_lines.includes(pos.line + 1) {
1010						if !is_important_line && !lines.is_empty() {
1011							let mut viewbox_charmap = CharMap::new();
1012							self.draw_line_number(None, &mut viewbox_charmap, margin_len);
1013							self.draw_line_highlights(
1014								pos.line,
1015								&mut viewbox_charmap,
1016								margin,
1017								&mut mapped_highlights,
1018								metrics,
1019								None,
1020							);
1021							lines.push(viewbox_charmap)
1022						}
1023						is_important_line = true
1024					} else {
1025						is_important_line = false
1026					}
1027
1028					if is_important_line {
1029						lines.push(CharMap::new())
1030					}
1031				}
1032				'\t' => (),
1033				_ => {
1034					if is_important_line {
1035						if self.use_line_begining_shortcut
1036							&& first_non_whitespace.is_none()
1037							&& !c.is_whitespace() && !c.is_control()
1038						{
1039							first_non_whitespace = Some(pos.column)
1040						}
1041
1042						lines.last_mut().unwrap().set(x, 0, Char::Text(c))
1043					}
1044				}
1045			}
1046
1047			pos.shift(c, metrics)
1048		}
1049
1050		if is_important_line {
1051			let line_charmap = lines.last_mut().unwrap();
1052			self.draw_line_number(Some(pos.line), line_charmap, margin_len);
1053			self.draw_line_highlights(
1054				pos.line,
1055				line_charmap,
1056				margin,
1057				&mut mapped_highlights,
1058				metrics,
1059				first_non_whitespace,
1060			);
1061		}
1062
1063		Ok(Formatted(lines))
1064	}
1065
1066	fn draw_line_number(
1067		&self,
1068		line: Option<usize>,
1069		charmap: &mut CharMap,
1070		margin_len: usize,
1071	) {
1072		if margin_len > 0 {
1073			charmap.set(
1074				margin_len - 2,
1075				0,
1076				Char::Margin('|', self.margin_color),
1077			);
1078			match line {
1079				Some(mut line) => {
1080					let mut x = margin_len - 3;
1081					line += 1;
1082
1083					while line > 0 {
1084						x -= 1;
1085						let d = line % 10;
1086
1087						charmap.set(
1088							x,
1089							0,
1090							Char::Margin(
1091								std::char::from_digit(d as u32, 10).unwrap(),
1092								self.margin_color,
1093							),
1094						);
1095
1096						line /= 10;
1097					}
1098				}
1099				None => {
1100					for x in 0..(margin_len - 3) {
1101						charmap.set(x, 0, Char::Margin('.', self.margin_color))
1102					}
1103				}
1104			}
1105		}
1106	}
1107
1108	fn draw_line_highlights<M: Metrics>(
1109		&self,
1110		line: usize,
1111		charmap: &mut CharMap,
1112		margin: usize,
1113		highlights: &mut [MappedHighlight],
1114		metrics: &M,
1115		first_non_whitespace: Option<usize>,
1116	) {
1117		// span lines
1118		for i in 0..highlights.len() {
1119			let mut h = highlights[i];
1120
1121			let mut shortcut = false;
1122			if h.span().start.line == line {
1123				h.update_start_nest_level(&highlights[0..i], first_non_whitespace);
1124
1125				if h.span().last.line == line {
1126					charmap.draw_closed_line(
1127						h.style(),
1128						h.start_nest_level,
1129						margin + h.span().start.column,
1130						margin + h.span().last.column,
1131					)
1132				} else {
1133					if first_non_whitespace.is_some()
1134						&& h.span().start.column <= first_non_whitespace.unwrap()
1135					{
1136						// line begining shortcut
1137						shortcut = true;
1138						charmap.set(
1139							margin - h.margin_nest_level,
1140							0,
1141							Char::SpanMarginMarker(h.style().color()),
1142						)
1143					} else {
1144						charmap.draw_open_line(
1145							h.style(),
1146							h.start_nest_level,
1147							margin - h.margin_nest_level + 1,
1148							margin + h.span().start.column,
1149						)
1150					}
1151				}
1152			} else if h.span().last.line == line {
1153				h.update_end_nest_level(&highlights[0..i]);
1154				charmap.draw_open_line(
1155					h.style(),
1156					h.end_nest_level,
1157					margin - h.margin_nest_level + 1,
1158					margin + h.span().last.column,
1159				);
1160				// charmap.set(margin - h.margin_nest_level, h.end_nest_level,
1161				// Char::SpanMargin(h.style().color()))
1162			}
1163
1164			if shortcut || (h.span().start.line < line && h.span().last.line >= line) {
1165				let end = if h.span().last.line == line {
1166					h.end_nest_level
1167				} else {
1168					charmap.height() - 1
1169				};
1170
1171				let x = margin - h.margin_nest_level;
1172				let offset_y = if shortcut { 1 } else { 0 };
1173
1174				for y in offset_y..=end {
1175					charmap.set(x, y, Char::SpanMargin(h.style().color()))
1176				}
1177			}
1178
1179			highlights[i] = h;
1180		}
1181
1182		// labels
1183		for h in highlights.iter().rev() {
1184			if h.span().last.line == line {
1185				if let Some(label) = h.label() {
1186					let label_charmap = CharMap::from_label(&label, h.style().color(), metrics);
1187					let x = margin + h.span().last.column;
1188					let mut y = 1;
1189					if !charmap.draw_charmap_if_free(x + 2, y, &label_charmap) {
1190						y += 2;
1191						while !charmap.draw_charmap_if_free(x, y, &label_charmap) {
1192							y += 1;
1193						}
1194					}
1195
1196					for vy in 2..y {
1197						charmap.set(x, vy, Char::SpanVertical(h.style().color()));
1198					}
1199				}
1200			}
1201		}
1202	}
1203}
1204
1205impl Default for Formatter {
1206	fn default() -> Formatter {
1207		Formatter {
1208			highlights: Vec::new(),
1209			#[cfg(not(feature = "colors"))]
1210			margin_color: (),
1211			#[cfg(feature = "colors")]
1212			margin_color: Color::Blue,
1213			viewbox: Some(2),
1214			show_line_numbers: true,
1215			use_line_begining_shortcut: true,
1216		}
1217	}
1218}