1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#![warn(
    rust_2018_idioms,
    trivial_casts,
    trivial_numeric_casts,
    unreachable_pub,
    unused_qualifications
)]
#![forbid(unsafe_code)]

//! A library to render snippets of source code with annotations.
//! It is meant to be used as a building block for compiler diagnostics.
//!
//! # Example
//!
//! ```
//! // Some source code
//! let source = indoc::indoc! {r#"
//!     fn main() {
//!         println!("Hello, world!");
//!     }
//! "#};
//!
//! // Create the snippet
//! let snippet = sourceannot::SourceSnippet::build_from_utf8(1, source.as_bytes(), 4);
//!
//! // Styles are generic over the type of the metadata that accompanies each
//! // chunk of rendered text. In this example, we will use the following enum:
//! #[derive(Copy, Clone, Debug, PartialEq, Eq)]
//! enum Color {
//!     Default,
//!     Red,
//!     Green,
//!     Blue,
//! }
//! // If do not you need this per-chunk metadata, you can use `()` instead.
//!
//! // Define the styles
//! // Use Unicode box drawing characters
//! let main_style = sourceannot::MainStyle {
//!     margin: Some(sourceannot::MarginStyle {
//!         line_char: '│',
//!         dot_char: '·',
//!         meta: Color::Blue,
//!     }),
//!     horizontal_char: '─',
//!     vertical_char: '│',
//!     top_vertical_char: '╭',
//!     top_corner_char: '╭',
//!     bottom_corner_char: '╰',
//!     spaces_meta: Color::Default,
//!     text_normal_meta: Color::Default,
//!     text_alt_meta: Color::Default,
//! };
//!
//! // Uou can use a different style for each annotation, but in
//! // this example we will use the same style for all of them.
//! let annot_style = sourceannot::AnnotStyle {
//!     caret: '^',
//!     text_normal_meta: Color::Red,
//!     text_alt_meta: Color::Red,
//!     line_meta: Color::Red,
//! };
//!
//! // Create the annotations
//! let mut annotations = sourceannot::Annotations::new(&snippet, main_style);
//!
//! annotations.add_annotation(
//!     0..44,
//!     annot_style,
//!     vec![("this is the `main` function".into(), Color::Red)],
//! );
//! annotations.add_annotation(
//!     16..24,
//!     annot_style,
//!     vec![("this is a macro invocation".into(), Color::Red)],
//! );
//!
//! // Render the snippet with annotations
//! let max_line_no_width = annotations.max_line_no_width();
//! let rendered = annotations.render(max_line_no_width, 0, 0);
//!
//! // `rendered` is a `Vec<(String, Color)>`, which you could print with
//! // your favorite terminal coloring library. In this example, we will
//! // ignore the colors.
//!
//! for (chunk, _) in rendered.iter() {
//!     eprint!("{chunk}");
//! }
//!
//! # let rendered = rendered.iter().map(|(s, _)| s.as_str()).collect::<String>();
//! # assert_eq!(
//! #     rendered,
//! #     indoc::indoc! {"
//! #         1 │ ╭ fn main() {
//! #         2 │ │     println!(\"Hello, world!\");
//! #           │ │     ^^^^^^^^ this is a macro invocation
//! #         3 │ │ }
//! #           │ ╰─^ this is the `main` function
//! #     "},
//! # );
//! ```
//!
//! The output will look like this:
//!
//! ```text
//! 1 │ ╭ fn main() {
//! 2 │ │     println!(\"Hello, world!\");
//!   │ │     ^^^^^^^^ this is a macro invocation
//! 3 │ │ }
//!   │ ╰─^ this is the `main` function
//! ```

mod annots;
mod range_set;
mod snippet;

pub use annots::Annotations;
pub use snippet::SourceSnippet;

/// The general style of an annotated snippet.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct MainStyle<M> {
    /// The style of the margin.
    ///
    /// If `None`, there will not be any margin at all.
    pub margin: Option<MarginStyle<M>>,

    /// Character used to draw horizontal lines of multi-line annotations.
    pub horizontal_char: char,

    /// Character used to draw vertical lines of multi-line annotations.
    pub vertical_char: char,

    /// Character used to draw the top corner of multi-line annotations
    /// that start at the first column.
    pub top_vertical_char: char,

    /// Character used to draw the top corner of multi-line annotations.
    pub top_corner_char: char,

    /// Character used to draw the bottom corner of multi-line annotations.
    pub bottom_corner_char: char,

    /// Metadata that accompanies spaces.
    pub spaces_meta: M,

    /// Metadata that accompanies unannotated normal text.
    pub text_normal_meta: M,

    /// Metadata that accompanies unannotated alternative text.
    pub text_alt_meta: M,
}

/// The style of the margin of an annotated snippet.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct MarginStyle<M> {
    /// Character used to draw the vertical separator of the margin.
    pub line_char: char,

    /// Character used to draw discontinuities in the vertical separator
    /// of the margin durin.
    pub dot_char: char,

    /// Metadata that accompanies margin characters.
    pub meta: M,
}

/// The style of a concrete annotation.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct AnnotStyle<M> {
    /// Caret character used to point to the annotated text.
    pub caret: char,

    /// Metadata that accompanies annotated normal text.
    pub text_normal_meta: M,

    /// Metadata that accompanies annotated alternative text.
    pub text_alt_meta: M,

    /// Metadata that accompanies annotation drawings.
    pub line_meta: M,
}