1e73685ebSopenharmony_ciuse std::io::{self, Write};
2e73685ebSopenharmony_ciuse std::ops::Range;
3e73685ebSopenharmony_ciuse termcolor::{ColorSpec, WriteColor};
4e73685ebSopenharmony_ci
5e73685ebSopenharmony_ciuse crate::diagnostic::{LabelStyle, Severity};
6e73685ebSopenharmony_ciuse crate::files::{Error, Location};
7e73685ebSopenharmony_ciuse crate::term::{Chars, Config, Styles};
8e73685ebSopenharmony_ci
9e73685ebSopenharmony_ci/// The 'location focus' of a source code snippet.
10e73685ebSopenharmony_cipub struct Locus {
11e73685ebSopenharmony_ci    /// The user-facing name of the file.
12e73685ebSopenharmony_ci    pub name: String,
13e73685ebSopenharmony_ci    /// The location.
14e73685ebSopenharmony_ci    pub location: Location,
15e73685ebSopenharmony_ci}
16e73685ebSopenharmony_ci
17e73685ebSopenharmony_ci/// Single-line label, with an optional message.
18e73685ebSopenharmony_ci///
19e73685ebSopenharmony_ci/// ```text
20e73685ebSopenharmony_ci/// ^^^^^^^^^ blah blah
21e73685ebSopenharmony_ci/// ```
22e73685ebSopenharmony_cipub type SingleLabel<'diagnostic> = (LabelStyle, Range<usize>, &'diagnostic str);
23e73685ebSopenharmony_ci
24e73685ebSopenharmony_ci/// A multi-line label to render.
25e73685ebSopenharmony_ci///
26e73685ebSopenharmony_ci/// Locations are relative to the start of where the source code is rendered.
27e73685ebSopenharmony_cipub enum MultiLabel<'diagnostic> {
28e73685ebSopenharmony_ci    /// Multi-line label top.
29e73685ebSopenharmony_ci    /// The contained value indicates where the label starts.
30e73685ebSopenharmony_ci    ///
31e73685ebSopenharmony_ci    /// ```text
32e73685ebSopenharmony_ci    /// ╭────────────^
33e73685ebSopenharmony_ci    /// ```
34e73685ebSopenharmony_ci    ///
35e73685ebSopenharmony_ci    /// Can also be rendered at the beginning of the line
36e73685ebSopenharmony_ci    /// if there is only whitespace before the label starts.
37e73685ebSopenharmony_ci    ///
38e73685ebSopenharmony_ci    /// /// ```text
39e73685ebSopenharmony_ci    /// ╭
40e73685ebSopenharmony_ci    /// ```
41e73685ebSopenharmony_ci    Top(usize),
42e73685ebSopenharmony_ci    /// Left vertical labels for multi-line labels.
43e73685ebSopenharmony_ci    ///
44e73685ebSopenharmony_ci    /// ```text
45e73685ebSopenharmony_ci    /// │
46e73685ebSopenharmony_ci    /// ```
47e73685ebSopenharmony_ci    Left,
48e73685ebSopenharmony_ci    /// Multi-line label bottom, with an optional message.
49e73685ebSopenharmony_ci    /// The first value indicates where the label ends.
50e73685ebSopenharmony_ci    ///
51e73685ebSopenharmony_ci    /// ```text
52e73685ebSopenharmony_ci    /// ╰────────────^ blah blah
53e73685ebSopenharmony_ci    /// ```
54e73685ebSopenharmony_ci    Bottom(usize, &'diagnostic str),
55e73685ebSopenharmony_ci}
56e73685ebSopenharmony_ci
57e73685ebSopenharmony_ci#[derive(Copy, Clone)]
58e73685ebSopenharmony_cienum VerticalBound {
59e73685ebSopenharmony_ci    Top,
60e73685ebSopenharmony_ci    Bottom,
61e73685ebSopenharmony_ci}
62e73685ebSopenharmony_ci
63e73685ebSopenharmony_citype Underline = (LabelStyle, VerticalBound);
64e73685ebSopenharmony_ci
65e73685ebSopenharmony_ci/// A renderer of display list entries.
66e73685ebSopenharmony_ci///
67e73685ebSopenharmony_ci/// The following diagram gives an overview of each of the parts of the renderer's output:
68e73685ebSopenharmony_ci///
69e73685ebSopenharmony_ci/// ```text
70e73685ebSopenharmony_ci///                     ┌ outer gutter
71e73685ebSopenharmony_ci///                     │ ┌ left border
72e73685ebSopenharmony_ci///                     │ │ ┌ inner gutter
73e73685ebSopenharmony_ci///                     │ │ │   ┌─────────────────────────── source ─────────────────────────────┐
74e73685ebSopenharmony_ci///                     │ │ │   │                                                                │
75e73685ebSopenharmony_ci///                  ┌────────────────────────────────────────────────────────────────────────────
76e73685ebSopenharmony_ci///        header ── │ error[0001]: oh noes, a cupcake has occurred!
77e73685ebSopenharmony_ci/// snippet start ── │    ┌─ test:9:0
78e73685ebSopenharmony_ci/// snippet empty ── │    │
79e73685ebSopenharmony_ci///  snippet line ── │  9 │   ╭ Cupcake ipsum dolor. Sit amet marshmallow topping cheesecake
80e73685ebSopenharmony_ci///  snippet line ── │ 10 │   │ muffin. Halvah croissant candy canes bonbon candy. Apple pie jelly
81e73685ebSopenharmony_ci///                  │    │ ╭─│─────────^
82e73685ebSopenharmony_ci/// snippet break ── │    · │ │
83e73685ebSopenharmony_ci///  snippet line ── │ 33 │ │ │ Muffin danish chocolate soufflé pastry icing bonbon oat cake.
84e73685ebSopenharmony_ci///  snippet line ── │ 34 │ │ │ Powder cake jujubes oat cake. Lemon drops tootsie roll marshmallow
85e73685ebSopenharmony_ci///                  │    │ │ ╰─────────────────────────────^ blah blah
86e73685ebSopenharmony_ci/// snippet break ── │    · │
87e73685ebSopenharmony_ci///  snippet line ── │ 38 │ │   Brownie lemon drops chocolate jelly-o candy canes. Danish marzipan
88e73685ebSopenharmony_ci///  snippet line ── │ 39 │ │   jujubes soufflé carrot cake marshmallow tiramisu caramels candy canes.
89e73685ebSopenharmony_ci///                  │    │ │           ^^^^^^^^^^^^^^^^^^^ -------------------- blah blah
90e73685ebSopenharmony_ci///                  │    │ │           │
91e73685ebSopenharmony_ci///                  │    │ │           blah blah
92e73685ebSopenharmony_ci///                  │    │ │           note: this is a note
93e73685ebSopenharmony_ci///  snippet line ── │ 40 │ │   Fruitcake jelly-o danish toffee. Tootsie roll pastry cheesecake
94e73685ebSopenharmony_ci///  snippet line ── │ 41 │ │   soufflé marzipan. Chocolate bar oat cake jujubes lollipop pastry
95e73685ebSopenharmony_ci///  snippet line ── │ 42 │ │   cupcake. Candy canes cupcake toffee gingerbread candy canes muffin
96e73685ebSopenharmony_ci///                  │    │ │                                ^^^^^^^^^^^^^^^^^^ blah blah
97e73685ebSopenharmony_ci///                  │    │ ╰──────────^ blah blah
98e73685ebSopenharmony_ci/// snippet break ── │    ·
99e73685ebSopenharmony_ci///  snippet line ── │ 82 │     gingerbread toffee chupa chups chupa chups jelly-o cotton candy.
100e73685ebSopenharmony_ci///                  │    │                 ^^^^^^                         ------- blah blah
101e73685ebSopenharmony_ci/// snippet empty ── │    │
102e73685ebSopenharmony_ci///  snippet note ── │    = blah blah
103e73685ebSopenharmony_ci///  snippet note ── │    = blah blah blah
104e73685ebSopenharmony_ci///                  │      blah blah
105e73685ebSopenharmony_ci///  snippet note ── │    = blah blah blah
106e73685ebSopenharmony_ci///                  │      blah blah
107e73685ebSopenharmony_ci///         empty ── │
108e73685ebSopenharmony_ci/// ```
109e73685ebSopenharmony_ci///
110e73685ebSopenharmony_ci/// Filler text from http://www.cupcakeipsum.com
111e73685ebSopenharmony_cipub struct Renderer<'writer, 'config> {
112e73685ebSopenharmony_ci    writer: &'writer mut dyn WriteColor,
113e73685ebSopenharmony_ci    config: &'config Config,
114e73685ebSopenharmony_ci}
115e73685ebSopenharmony_ci
116e73685ebSopenharmony_ciimpl<'writer, 'config> Renderer<'writer, 'config> {
117e73685ebSopenharmony_ci    /// Construct a renderer from the given writer and config.
118e73685ebSopenharmony_ci    pub fn new(
119e73685ebSopenharmony_ci        writer: &'writer mut dyn WriteColor,
120e73685ebSopenharmony_ci        config: &'config Config,
121e73685ebSopenharmony_ci    ) -> Renderer<'writer, 'config> {
122e73685ebSopenharmony_ci        Renderer { writer, config }
123e73685ebSopenharmony_ci    }
124e73685ebSopenharmony_ci
125e73685ebSopenharmony_ci    fn chars(&self) -> &'config Chars {
126e73685ebSopenharmony_ci        &self.config.chars
127e73685ebSopenharmony_ci    }
128e73685ebSopenharmony_ci
129e73685ebSopenharmony_ci    fn styles(&self) -> &'config Styles {
130e73685ebSopenharmony_ci        &self.config.styles
131e73685ebSopenharmony_ci    }
132e73685ebSopenharmony_ci
133e73685ebSopenharmony_ci    /// Diagnostic header, with severity, code, and message.
134e73685ebSopenharmony_ci    ///
135e73685ebSopenharmony_ci    /// ```text
136e73685ebSopenharmony_ci    /// error[E0001]: unexpected type in `+` application
137e73685ebSopenharmony_ci    /// ```
138e73685ebSopenharmony_ci    pub fn render_header(
139e73685ebSopenharmony_ci        &mut self,
140e73685ebSopenharmony_ci        locus: Option<&Locus>,
141e73685ebSopenharmony_ci        severity: Severity,
142e73685ebSopenharmony_ci        code: Option<&str>,
143e73685ebSopenharmony_ci        message: &str,
144e73685ebSopenharmony_ci    ) -> Result<(), Error> {
145e73685ebSopenharmony_ci        // Write locus
146e73685ebSopenharmony_ci        //
147e73685ebSopenharmony_ci        // ```text
148e73685ebSopenharmony_ci        // test:2:9:
149e73685ebSopenharmony_ci        // ```
150e73685ebSopenharmony_ci        if let Some(locus) = locus {
151e73685ebSopenharmony_ci            self.snippet_locus(locus)?;
152e73685ebSopenharmony_ci            write!(self, ": ")?;
153e73685ebSopenharmony_ci        }
154e73685ebSopenharmony_ci
155e73685ebSopenharmony_ci        // Write severity name
156e73685ebSopenharmony_ci        //
157e73685ebSopenharmony_ci        // ```text
158e73685ebSopenharmony_ci        // error
159e73685ebSopenharmony_ci        // ```
160e73685ebSopenharmony_ci        self.set_color(self.styles().header(severity))?;
161e73685ebSopenharmony_ci        match severity {
162e73685ebSopenharmony_ci            Severity::Bug => write!(self, "bug")?,
163e73685ebSopenharmony_ci            Severity::Error => write!(self, "error")?,
164e73685ebSopenharmony_ci            Severity::Warning => write!(self, "warning")?,
165e73685ebSopenharmony_ci            Severity::Help => write!(self, "help")?,
166e73685ebSopenharmony_ci            Severity::Note => write!(self, "note")?,
167e73685ebSopenharmony_ci        }
168e73685ebSopenharmony_ci
169e73685ebSopenharmony_ci        // Write error code
170e73685ebSopenharmony_ci        //
171e73685ebSopenharmony_ci        // ```text
172e73685ebSopenharmony_ci        // [E0001]
173e73685ebSopenharmony_ci        // ```
174e73685ebSopenharmony_ci        if let Some(code) = &code.filter(|code| !code.is_empty()) {
175e73685ebSopenharmony_ci            write!(self, "[{}]", code)?;
176e73685ebSopenharmony_ci        }
177e73685ebSopenharmony_ci
178e73685ebSopenharmony_ci        // Write diagnostic message
179e73685ebSopenharmony_ci        //
180e73685ebSopenharmony_ci        // ```text
181e73685ebSopenharmony_ci        // : unexpected type in `+` application
182e73685ebSopenharmony_ci        // ```
183e73685ebSopenharmony_ci        self.set_color(&self.styles().header_message)?;
184e73685ebSopenharmony_ci        write!(self, ": {}", message)?;
185e73685ebSopenharmony_ci        self.reset()?;
186e73685ebSopenharmony_ci
187e73685ebSopenharmony_ci        writeln!(self)?;
188e73685ebSopenharmony_ci
189e73685ebSopenharmony_ci        Ok(())
190e73685ebSopenharmony_ci    }
191e73685ebSopenharmony_ci
192e73685ebSopenharmony_ci    /// Empty line.
193e73685ebSopenharmony_ci    pub fn render_empty(&mut self) -> Result<(), Error> {
194e73685ebSopenharmony_ci        writeln!(self)?;
195e73685ebSopenharmony_ci        Ok(())
196e73685ebSopenharmony_ci    }
197e73685ebSopenharmony_ci
198e73685ebSopenharmony_ci    /// Top left border and locus.
199e73685ebSopenharmony_ci    ///
200e73685ebSopenharmony_ci    /// ```text
201e73685ebSopenharmony_ci    /// ┌─ test:2:9
202e73685ebSopenharmony_ci    /// ```
203e73685ebSopenharmony_ci    pub fn render_snippet_start(
204e73685ebSopenharmony_ci        &mut self,
205e73685ebSopenharmony_ci        outer_padding: usize,
206e73685ebSopenharmony_ci        locus: &Locus,
207e73685ebSopenharmony_ci    ) -> Result<(), Error> {
208e73685ebSopenharmony_ci        self.outer_gutter(outer_padding)?;
209e73685ebSopenharmony_ci
210e73685ebSopenharmony_ci        self.set_color(&self.styles().source_border)?;
211e73685ebSopenharmony_ci        write!(self, "{}", self.chars().source_border_top_left)?;
212e73685ebSopenharmony_ci        write!(self, "{0}", self.chars().source_border_top)?;
213e73685ebSopenharmony_ci        self.reset()?;
214e73685ebSopenharmony_ci
215e73685ebSopenharmony_ci        write!(self, " ")?;
216e73685ebSopenharmony_ci        self.snippet_locus(&locus)?;
217e73685ebSopenharmony_ci
218e73685ebSopenharmony_ci        writeln!(self)?;
219e73685ebSopenharmony_ci
220e73685ebSopenharmony_ci        Ok(())
221e73685ebSopenharmony_ci    }
222e73685ebSopenharmony_ci
223e73685ebSopenharmony_ci    /// A line of source code.
224e73685ebSopenharmony_ci    ///
225e73685ebSopenharmony_ci    /// ```text
226e73685ebSopenharmony_ci    /// 10 │   │ muffin. Halvah croissant candy canes bonbon candy. Apple pie jelly
227e73685ebSopenharmony_ci    ///    │ ╭─│─────────^
228e73685ebSopenharmony_ci    /// ```
229e73685ebSopenharmony_ci    pub fn render_snippet_source(
230e73685ebSopenharmony_ci        &mut self,
231e73685ebSopenharmony_ci        outer_padding: usize,
232e73685ebSopenharmony_ci        line_number: usize,
233e73685ebSopenharmony_ci        source: &str,
234e73685ebSopenharmony_ci        severity: Severity,
235e73685ebSopenharmony_ci        single_labels: &[SingleLabel<'_>],
236e73685ebSopenharmony_ci        num_multi_labels: usize,
237e73685ebSopenharmony_ci        multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
238e73685ebSopenharmony_ci    ) -> Result<(), Error> {
239e73685ebSopenharmony_ci        // Trim trailing newlines, linefeeds, and null chars from source, if they exist.
240e73685ebSopenharmony_ci        // FIXME: Use the number of trimmed placeholders when rendering single line carets
241e73685ebSopenharmony_ci        let source = source.trim_end_matches(['\n', '\r', '\0'].as_ref());
242e73685ebSopenharmony_ci
243e73685ebSopenharmony_ci        // Write source line
244e73685ebSopenharmony_ci        //
245e73685ebSopenharmony_ci        // ```text
246e73685ebSopenharmony_ci        // 10 │   │ muffin. Halvah croissant candy canes bonbon candy. Apple pie jelly
247e73685ebSopenharmony_ci        // ```
248e73685ebSopenharmony_ci        {
249e73685ebSopenharmony_ci            // Write outer gutter (with line number) and border
250e73685ebSopenharmony_ci            self.outer_gutter_number(line_number, outer_padding)?;
251e73685ebSopenharmony_ci            self.border_left()?;
252e73685ebSopenharmony_ci
253e73685ebSopenharmony_ci            // Write inner gutter (with multi-line continuations on the left if necessary)
254e73685ebSopenharmony_ci            let mut multi_labels_iter = multi_labels.iter().peekable();
255e73685ebSopenharmony_ci            for label_column in 0..num_multi_labels {
256e73685ebSopenharmony_ci                match multi_labels_iter.peek() {
257e73685ebSopenharmony_ci                    Some((label_index, label_style, label)) if *label_index == label_column => {
258e73685ebSopenharmony_ci                        match label {
259e73685ebSopenharmony_ci                            MultiLabel::Top(start)
260e73685ebSopenharmony_ci                                if *start <= source.len() - source.trim_start().len() =>
261e73685ebSopenharmony_ci                            {
262e73685ebSopenharmony_ci                                self.label_multi_top_left(severity, *label_style)?;
263e73685ebSopenharmony_ci                            }
264e73685ebSopenharmony_ci                            MultiLabel::Top(..) => self.inner_gutter_space()?,
265e73685ebSopenharmony_ci                            MultiLabel::Left | MultiLabel::Bottom(..) => {
266e73685ebSopenharmony_ci                                self.label_multi_left(severity, *label_style, None)?;
267e73685ebSopenharmony_ci                            }
268e73685ebSopenharmony_ci                        }
269e73685ebSopenharmony_ci                        multi_labels_iter.next();
270e73685ebSopenharmony_ci                    }
271e73685ebSopenharmony_ci                    Some((_, _, _)) | None => self.inner_gutter_space()?,
272e73685ebSopenharmony_ci                }
273e73685ebSopenharmony_ci            }
274e73685ebSopenharmony_ci
275e73685ebSopenharmony_ci            // Write source text
276e73685ebSopenharmony_ci            write!(self, " ")?;
277e73685ebSopenharmony_ci            let mut in_primary = false;
278e73685ebSopenharmony_ci            for (metrics, ch) in self.char_metrics(source.char_indices()) {
279e73685ebSopenharmony_ci                let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
280e73685ebSopenharmony_ci
281e73685ebSopenharmony_ci                // Check if we are overlapping a primary label
282e73685ebSopenharmony_ci                let is_primary = single_labels.iter().any(|(ls, range, _)| {
283e73685ebSopenharmony_ci                    *ls == LabelStyle::Primary && is_overlapping(range, &column_range)
284e73685ebSopenharmony_ci                }) || multi_labels.iter().any(|(_, ls, label)| {
285e73685ebSopenharmony_ci                    *ls == LabelStyle::Primary
286e73685ebSopenharmony_ci                        && match label {
287e73685ebSopenharmony_ci                            MultiLabel::Top(start) => column_range.start >= *start,
288e73685ebSopenharmony_ci                            MultiLabel::Left => true,
289e73685ebSopenharmony_ci                            MultiLabel::Bottom(start, _) => column_range.end <= *start,
290e73685ebSopenharmony_ci                        }
291e73685ebSopenharmony_ci                });
292e73685ebSopenharmony_ci
293e73685ebSopenharmony_ci                // Set the source color if we are in a primary label
294e73685ebSopenharmony_ci                if is_primary && !in_primary {
295e73685ebSopenharmony_ci                    self.set_color(self.styles().label(severity, LabelStyle::Primary))?;
296e73685ebSopenharmony_ci                    in_primary = true;
297e73685ebSopenharmony_ci                } else if !is_primary && in_primary {
298e73685ebSopenharmony_ci                    self.reset()?;
299e73685ebSopenharmony_ci                    in_primary = false;
300e73685ebSopenharmony_ci                }
301e73685ebSopenharmony_ci
302e73685ebSopenharmony_ci                match ch {
303e73685ebSopenharmony_ci                    '\t' => (0..metrics.unicode_width).try_for_each(|_| write!(self, " "))?,
304e73685ebSopenharmony_ci                    _ => write!(self, "{}", ch)?,
305e73685ebSopenharmony_ci                }
306e73685ebSopenharmony_ci            }
307e73685ebSopenharmony_ci            if in_primary {
308e73685ebSopenharmony_ci                self.reset()?;
309e73685ebSopenharmony_ci            }
310e73685ebSopenharmony_ci            writeln!(self)?;
311e73685ebSopenharmony_ci        }
312e73685ebSopenharmony_ci
313e73685ebSopenharmony_ci        // Write single labels underneath source
314e73685ebSopenharmony_ci        //
315e73685ebSopenharmony_ci        // ```text
316e73685ebSopenharmony_ci        //   │     - ---- ^^^ second mutable borrow occurs here
317e73685ebSopenharmony_ci        //   │     │ │
318e73685ebSopenharmony_ci        //   │     │ first mutable borrow occurs here
319e73685ebSopenharmony_ci        //   │     first borrow later used by call
320e73685ebSopenharmony_ci        //   │     help: some help here
321e73685ebSopenharmony_ci        // ```
322e73685ebSopenharmony_ci        if !single_labels.is_empty() {
323e73685ebSopenharmony_ci            // Our plan is as follows:
324e73685ebSopenharmony_ci            //
325e73685ebSopenharmony_ci            // 1. Do an initial scan to find:
326e73685ebSopenharmony_ci            //    - The number of non-empty messages.
327e73685ebSopenharmony_ci            //    - The right-most start and end positions of labels.
328e73685ebSopenharmony_ci            //    - A candidate for a trailing label (where the label's message
329e73685ebSopenharmony_ci            //      is printed to the left of the caret).
330e73685ebSopenharmony_ci            // 2. Check if the trailing label candidate overlaps another label -
331e73685ebSopenharmony_ci            //    if so we print it underneath the carets with the other labels.
332e73685ebSopenharmony_ci            // 3. Print a line of carets, and (possibly) the trailing message
333e73685ebSopenharmony_ci            //    to the left.
334e73685ebSopenharmony_ci            // 4. Print vertical lines pointing to the carets, and the messages
335e73685ebSopenharmony_ci            //    for those carets.
336e73685ebSopenharmony_ci            //
337e73685ebSopenharmony_ci            // We try our best avoid introducing new dynamic allocations,
338e73685ebSopenharmony_ci            // instead preferring to iterate over the labels multiple times. It
339e73685ebSopenharmony_ci            // is unclear what the performance tradeoffs are however, so further
340e73685ebSopenharmony_ci            // investigation may be required.
341e73685ebSopenharmony_ci
342e73685ebSopenharmony_ci            // The number of non-empty messages to print.
343e73685ebSopenharmony_ci            let mut num_messages = 0;
344e73685ebSopenharmony_ci            // The right-most start position, eg:
345e73685ebSopenharmony_ci            //
346e73685ebSopenharmony_ci            // ```text
347e73685ebSopenharmony_ci            // -^^^^---- ^^^^^^^
348e73685ebSopenharmony_ci            //           │
349e73685ebSopenharmony_ci            //           right-most start position
350e73685ebSopenharmony_ci            // ```
351e73685ebSopenharmony_ci            let mut max_label_start = 0;
352e73685ebSopenharmony_ci            // The right-most end position, eg:
353e73685ebSopenharmony_ci            //
354e73685ebSopenharmony_ci            // ```text
355e73685ebSopenharmony_ci            // -^^^^---- ^^^^^^^
356e73685ebSopenharmony_ci            //                 │
357e73685ebSopenharmony_ci            //                 right-most end position
358e73685ebSopenharmony_ci            // ```
359e73685ebSopenharmony_ci            let mut max_label_end = 0;
360e73685ebSopenharmony_ci            // A trailing message, eg:
361e73685ebSopenharmony_ci            //
362e73685ebSopenharmony_ci            // ```text
363e73685ebSopenharmony_ci            // ^^^ second mutable borrow occurs here
364e73685ebSopenharmony_ci            // ```
365e73685ebSopenharmony_ci            let mut trailing_label = None;
366e73685ebSopenharmony_ci
367e73685ebSopenharmony_ci            for (label_index, label) in single_labels.iter().enumerate() {
368e73685ebSopenharmony_ci                let (_, range, message) = label;
369e73685ebSopenharmony_ci                if !message.is_empty() {
370e73685ebSopenharmony_ci                    num_messages += 1;
371e73685ebSopenharmony_ci                }
372e73685ebSopenharmony_ci                max_label_start = std::cmp::max(max_label_start, range.start);
373e73685ebSopenharmony_ci                max_label_end = std::cmp::max(max_label_end, range.end);
374e73685ebSopenharmony_ci                // This is a candidate for the trailing label, so let's record it.
375e73685ebSopenharmony_ci                if range.end == max_label_end {
376e73685ebSopenharmony_ci                    if message.is_empty() {
377e73685ebSopenharmony_ci                        trailing_label = None;
378e73685ebSopenharmony_ci                    } else {
379e73685ebSopenharmony_ci                        trailing_label = Some((label_index, label));
380e73685ebSopenharmony_ci                    }
381e73685ebSopenharmony_ci                }
382e73685ebSopenharmony_ci            }
383e73685ebSopenharmony_ci            if let Some((trailing_label_index, (_, trailing_range, _))) = trailing_label {
384e73685ebSopenharmony_ci                // Check to see if the trailing label candidate overlaps any of
385e73685ebSopenharmony_ci                // the other labels on the current line.
386e73685ebSopenharmony_ci                if single_labels
387e73685ebSopenharmony_ci                    .iter()
388e73685ebSopenharmony_ci                    .enumerate()
389e73685ebSopenharmony_ci                    .filter(|(label_index, _)| *label_index != trailing_label_index)
390e73685ebSopenharmony_ci                    .any(|(_, (_, range, _))| is_overlapping(trailing_range, range))
391e73685ebSopenharmony_ci                {
392e73685ebSopenharmony_ci                    // If it does, we'll instead want to render it below the
393e73685ebSopenharmony_ci                    // carets along with the other hanging labels.
394e73685ebSopenharmony_ci                    trailing_label = None;
395e73685ebSopenharmony_ci                }
396e73685ebSopenharmony_ci            }
397e73685ebSopenharmony_ci
398e73685ebSopenharmony_ci            // Write a line of carets
399e73685ebSopenharmony_ci            //
400e73685ebSopenharmony_ci            // ```text
401e73685ebSopenharmony_ci            //   │ ^^^^^^  -------^^^^^^^^^-------^^^^^----- ^^^^ trailing label message
402e73685ebSopenharmony_ci            // ```
403e73685ebSopenharmony_ci            self.outer_gutter(outer_padding)?;
404e73685ebSopenharmony_ci            self.border_left()?;
405e73685ebSopenharmony_ci            self.inner_gutter(severity, num_multi_labels, multi_labels)?;
406e73685ebSopenharmony_ci            write!(self, " ")?;
407e73685ebSopenharmony_ci
408e73685ebSopenharmony_ci            let mut previous_label_style = None;
409e73685ebSopenharmony_ci            let placeholder_metrics = Metrics {
410e73685ebSopenharmony_ci                byte_index: source.len(),
411e73685ebSopenharmony_ci                unicode_width: 1,
412e73685ebSopenharmony_ci            };
413e73685ebSopenharmony_ci            for (metrics, ch) in self
414e73685ebSopenharmony_ci                .char_metrics(source.char_indices())
415e73685ebSopenharmony_ci                // Add a placeholder source column at the end to allow for
416e73685ebSopenharmony_ci                // printing carets at the end of lines, eg:
417e73685ebSopenharmony_ci                //
418e73685ebSopenharmony_ci                // ```text
419e73685ebSopenharmony_ci                // 1 │ Hello world!
420e73685ebSopenharmony_ci                //   │             ^
421e73685ebSopenharmony_ci                // ```
422e73685ebSopenharmony_ci                .chain(std::iter::once((placeholder_metrics, '\0')))
423e73685ebSopenharmony_ci            {
424e73685ebSopenharmony_ci                // Find the current label style at this column
425e73685ebSopenharmony_ci                let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
426e73685ebSopenharmony_ci                let current_label_style = single_labels
427e73685ebSopenharmony_ci                    .iter()
428e73685ebSopenharmony_ci                    .filter(|(_, range, _)| is_overlapping(range, &column_range))
429e73685ebSopenharmony_ci                    .map(|(label_style, _, _)| *label_style)
430e73685ebSopenharmony_ci                    .max_by_key(label_priority_key);
431e73685ebSopenharmony_ci
432e73685ebSopenharmony_ci                // Update writer style if necessary
433e73685ebSopenharmony_ci                if previous_label_style != current_label_style {
434e73685ebSopenharmony_ci                    match current_label_style {
435e73685ebSopenharmony_ci                        None => self.reset()?,
436e73685ebSopenharmony_ci                        Some(label_style) => {
437e73685ebSopenharmony_ci                            self.set_color(self.styles().label(severity, label_style))?;
438e73685ebSopenharmony_ci                        }
439e73685ebSopenharmony_ci                    }
440e73685ebSopenharmony_ci                }
441e73685ebSopenharmony_ci
442e73685ebSopenharmony_ci                let caret_ch = match current_label_style {
443e73685ebSopenharmony_ci                    Some(LabelStyle::Primary) => Some(self.chars().single_primary_caret),
444e73685ebSopenharmony_ci                    Some(LabelStyle::Secondary) => Some(self.chars().single_secondary_caret),
445e73685ebSopenharmony_ci                    // Only print padding if we are before the end of the last single line caret
446e73685ebSopenharmony_ci                    None if metrics.byte_index < max_label_end => Some(' '),
447e73685ebSopenharmony_ci                    None => None,
448e73685ebSopenharmony_ci                };
449e73685ebSopenharmony_ci                if let Some(caret_ch) = caret_ch {
450e73685ebSopenharmony_ci                    // FIXME: improve rendering of carets between character boundaries
451e73685ebSopenharmony_ci                    (0..metrics.unicode_width).try_for_each(|_| write!(self, "{}", caret_ch))?;
452e73685ebSopenharmony_ci                }
453e73685ebSopenharmony_ci
454e73685ebSopenharmony_ci                previous_label_style = current_label_style;
455e73685ebSopenharmony_ci            }
456e73685ebSopenharmony_ci            // Reset style if it was previously set
457e73685ebSopenharmony_ci            if previous_label_style.is_some() {
458e73685ebSopenharmony_ci                self.reset()?;
459e73685ebSopenharmony_ci            }
460e73685ebSopenharmony_ci            // Write first trailing label message
461e73685ebSopenharmony_ci            if let Some((_, (label_style, _, message))) = trailing_label {
462e73685ebSopenharmony_ci                write!(self, " ")?;
463e73685ebSopenharmony_ci                self.set_color(self.styles().label(severity, *label_style))?;
464e73685ebSopenharmony_ci                write!(self, "{}", message)?;
465e73685ebSopenharmony_ci                self.reset()?;
466e73685ebSopenharmony_ci            }
467e73685ebSopenharmony_ci            writeln!(self)?;
468e73685ebSopenharmony_ci
469e73685ebSopenharmony_ci            // Write hanging labels pointing to carets
470e73685ebSopenharmony_ci            //
471e73685ebSopenharmony_ci            // ```text
472e73685ebSopenharmony_ci            //   │     │ │
473e73685ebSopenharmony_ci            //   │     │ first mutable borrow occurs here
474e73685ebSopenharmony_ci            //   │     first borrow later used by call
475e73685ebSopenharmony_ci            //   │     help: some help here
476e73685ebSopenharmony_ci            // ```
477e73685ebSopenharmony_ci            if num_messages > trailing_label.iter().count() {
478e73685ebSopenharmony_ci                // Write first set of vertical lines before hanging labels
479e73685ebSopenharmony_ci                //
480e73685ebSopenharmony_ci                // ```text
481e73685ebSopenharmony_ci                //   │     │ │
482e73685ebSopenharmony_ci                // ```
483e73685ebSopenharmony_ci                self.outer_gutter(outer_padding)?;
484e73685ebSopenharmony_ci                self.border_left()?;
485e73685ebSopenharmony_ci                self.inner_gutter(severity, num_multi_labels, multi_labels)?;
486e73685ebSopenharmony_ci                write!(self, " ")?;
487e73685ebSopenharmony_ci                self.caret_pointers(
488e73685ebSopenharmony_ci                    severity,
489e73685ebSopenharmony_ci                    max_label_start,
490e73685ebSopenharmony_ci                    single_labels,
491e73685ebSopenharmony_ci                    trailing_label,
492e73685ebSopenharmony_ci                    source.char_indices(),
493e73685ebSopenharmony_ci                )?;
494e73685ebSopenharmony_ci                writeln!(self)?;
495e73685ebSopenharmony_ci
496e73685ebSopenharmony_ci                // Write hanging labels pointing to carets
497e73685ebSopenharmony_ci                //
498e73685ebSopenharmony_ci                // ```text
499e73685ebSopenharmony_ci                //   │     │ first mutable borrow occurs here
500e73685ebSopenharmony_ci                //   │     first borrow later used by call
501e73685ebSopenharmony_ci                //   │     help: some help here
502e73685ebSopenharmony_ci                // ```
503e73685ebSopenharmony_ci                for (label_style, range, message) in
504e73685ebSopenharmony_ci                    hanging_labels(single_labels, trailing_label).rev()
505e73685ebSopenharmony_ci                {
506e73685ebSopenharmony_ci                    self.outer_gutter(outer_padding)?;
507e73685ebSopenharmony_ci                    self.border_left()?;
508e73685ebSopenharmony_ci                    self.inner_gutter(severity, num_multi_labels, multi_labels)?;
509e73685ebSopenharmony_ci                    write!(self, " ")?;
510e73685ebSopenharmony_ci                    self.caret_pointers(
511e73685ebSopenharmony_ci                        severity,
512e73685ebSopenharmony_ci                        max_label_start,
513e73685ebSopenharmony_ci                        single_labels,
514e73685ebSopenharmony_ci                        trailing_label,
515e73685ebSopenharmony_ci                        source
516e73685ebSopenharmony_ci                            .char_indices()
517e73685ebSopenharmony_ci                            .take_while(|(byte_index, _)| *byte_index < range.start),
518e73685ebSopenharmony_ci                    )?;
519e73685ebSopenharmony_ci                    self.set_color(self.styles().label(severity, *label_style))?;
520e73685ebSopenharmony_ci                    write!(self, "{}", message)?;
521e73685ebSopenharmony_ci                    self.reset()?;
522e73685ebSopenharmony_ci                    writeln!(self)?;
523e73685ebSopenharmony_ci                }
524e73685ebSopenharmony_ci            }
525e73685ebSopenharmony_ci        }
526e73685ebSopenharmony_ci
527e73685ebSopenharmony_ci        // Write top or bottom label carets underneath source
528e73685ebSopenharmony_ci        //
529e73685ebSopenharmony_ci        // ```text
530e73685ebSopenharmony_ci        //     │ ╰───│──────────────────^ woops
531e73685ebSopenharmony_ci        //     │   ╭─│─────────^
532e73685ebSopenharmony_ci        // ```
533e73685ebSopenharmony_ci        for (multi_label_index, (_, label_style, label)) in multi_labels.iter().enumerate() {
534e73685ebSopenharmony_ci            let (label_style, range, bottom_message) = match label {
535e73685ebSopenharmony_ci                MultiLabel::Left => continue, // no label caret needed
536e73685ebSopenharmony_ci                // no label caret needed if this can be started in front of the line
537e73685ebSopenharmony_ci                MultiLabel::Top(start) if *start <= source.len() - source.trim_start().len() => {
538e73685ebSopenharmony_ci                    continue
539e73685ebSopenharmony_ci                }
540e73685ebSopenharmony_ci                MultiLabel::Top(range) => (*label_style, range, None),
541e73685ebSopenharmony_ci                MultiLabel::Bottom(range, message) => (*label_style, range, Some(message)),
542e73685ebSopenharmony_ci            };
543e73685ebSopenharmony_ci
544e73685ebSopenharmony_ci            self.outer_gutter(outer_padding)?;
545e73685ebSopenharmony_ci            self.border_left()?;
546e73685ebSopenharmony_ci
547e73685ebSopenharmony_ci            // Write inner gutter.
548e73685ebSopenharmony_ci            //
549e73685ebSopenharmony_ci            // ```text
550e73685ebSopenharmony_ci            //  │ ╭─│───│
551e73685ebSopenharmony_ci            // ```
552e73685ebSopenharmony_ci            let mut underline = None;
553e73685ebSopenharmony_ci            let mut multi_labels_iter = multi_labels.iter().enumerate().peekable();
554e73685ebSopenharmony_ci            for label_column in 0..num_multi_labels {
555e73685ebSopenharmony_ci                match multi_labels_iter.peek() {
556e73685ebSopenharmony_ci                    Some((i, (label_index, ls, label))) if *label_index == label_column => {
557e73685ebSopenharmony_ci                        match label {
558e73685ebSopenharmony_ci                            MultiLabel::Left => {
559e73685ebSopenharmony_ci                                self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
560e73685ebSopenharmony_ci                            }
561e73685ebSopenharmony_ci                            MultiLabel::Top(..) if multi_label_index > *i => {
562e73685ebSopenharmony_ci                                self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
563e73685ebSopenharmony_ci                            }
564e73685ebSopenharmony_ci                            MultiLabel::Bottom(..) if multi_label_index < *i => {
565e73685ebSopenharmony_ci                                self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
566e73685ebSopenharmony_ci                            }
567e73685ebSopenharmony_ci                            MultiLabel::Top(..) if multi_label_index == *i => {
568e73685ebSopenharmony_ci                                underline = Some((*ls, VerticalBound::Top));
569e73685ebSopenharmony_ci                                self.label_multi_top_left(severity, label_style)?
570e73685ebSopenharmony_ci                            }
571e73685ebSopenharmony_ci                            MultiLabel::Bottom(..) if multi_label_index == *i => {
572e73685ebSopenharmony_ci                                underline = Some((*ls, VerticalBound::Bottom));
573e73685ebSopenharmony_ci                                self.label_multi_bottom_left(severity, label_style)?;
574e73685ebSopenharmony_ci                            }
575e73685ebSopenharmony_ci                            MultiLabel::Top(..) | MultiLabel::Bottom(..) => {
576e73685ebSopenharmony_ci                                self.inner_gutter_column(severity, underline)?;
577e73685ebSopenharmony_ci                            }
578e73685ebSopenharmony_ci                        }
579e73685ebSopenharmony_ci                        multi_labels_iter.next();
580e73685ebSopenharmony_ci                    }
581e73685ebSopenharmony_ci                    Some((_, _)) | None => self.inner_gutter_column(severity, underline)?,
582e73685ebSopenharmony_ci                }
583e73685ebSopenharmony_ci            }
584e73685ebSopenharmony_ci
585e73685ebSopenharmony_ci            // Finish the top or bottom caret
586e73685ebSopenharmony_ci            match bottom_message {
587e73685ebSopenharmony_ci                None => self.label_multi_top_caret(severity, label_style, source, *range)?,
588e73685ebSopenharmony_ci                Some(message) => {
589e73685ebSopenharmony_ci                    self.label_multi_bottom_caret(severity, label_style, source, *range, message)?
590e73685ebSopenharmony_ci                }
591e73685ebSopenharmony_ci            }
592e73685ebSopenharmony_ci        }
593e73685ebSopenharmony_ci
594e73685ebSopenharmony_ci        Ok(())
595e73685ebSopenharmony_ci    }
596e73685ebSopenharmony_ci
597e73685ebSopenharmony_ci    /// An empty source line, for providing additional whitespace to source snippets.
598e73685ebSopenharmony_ci    ///
599e73685ebSopenharmony_ci    /// ```text
600e73685ebSopenharmony_ci    /// │ │ │
601e73685ebSopenharmony_ci    /// ```
602e73685ebSopenharmony_ci    pub fn render_snippet_empty(
603e73685ebSopenharmony_ci        &mut self,
604e73685ebSopenharmony_ci        outer_padding: usize,
605e73685ebSopenharmony_ci        severity: Severity,
606e73685ebSopenharmony_ci        num_multi_labels: usize,
607e73685ebSopenharmony_ci        multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
608e73685ebSopenharmony_ci    ) -> Result<(), Error> {
609e73685ebSopenharmony_ci        self.outer_gutter(outer_padding)?;
610e73685ebSopenharmony_ci        self.border_left()?;
611e73685ebSopenharmony_ci        self.inner_gutter(severity, num_multi_labels, multi_labels)?;
612e73685ebSopenharmony_ci        writeln!(self)?;
613e73685ebSopenharmony_ci        Ok(())
614e73685ebSopenharmony_ci    }
615e73685ebSopenharmony_ci
616e73685ebSopenharmony_ci    /// A broken source line, for labeling skipped sections of source.
617e73685ebSopenharmony_ci    ///
618e73685ebSopenharmony_ci    /// ```text
619e73685ebSopenharmony_ci    /// · │ │
620e73685ebSopenharmony_ci    /// ```
621e73685ebSopenharmony_ci    pub fn render_snippet_break(
622e73685ebSopenharmony_ci        &mut self,
623e73685ebSopenharmony_ci        outer_padding: usize,
624e73685ebSopenharmony_ci        severity: Severity,
625e73685ebSopenharmony_ci        num_multi_labels: usize,
626e73685ebSopenharmony_ci        multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
627e73685ebSopenharmony_ci    ) -> Result<(), Error> {
628e73685ebSopenharmony_ci        self.outer_gutter(outer_padding)?;
629e73685ebSopenharmony_ci        self.border_left_break()?;
630e73685ebSopenharmony_ci        self.inner_gutter(severity, num_multi_labels, multi_labels)?;
631e73685ebSopenharmony_ci        writeln!(self)?;
632e73685ebSopenharmony_ci        Ok(())
633e73685ebSopenharmony_ci    }
634e73685ebSopenharmony_ci
635e73685ebSopenharmony_ci    /// Additional notes.
636e73685ebSopenharmony_ci    ///
637e73685ebSopenharmony_ci    /// ```text
638e73685ebSopenharmony_ci    /// = expected type `Int`
639e73685ebSopenharmony_ci    ///      found type `String`
640e73685ebSopenharmony_ci    /// ```
641e73685ebSopenharmony_ci    pub fn render_snippet_note(
642e73685ebSopenharmony_ci        &mut self,
643e73685ebSopenharmony_ci        outer_padding: usize,
644e73685ebSopenharmony_ci        message: &str,
645e73685ebSopenharmony_ci    ) -> Result<(), Error> {
646e73685ebSopenharmony_ci        for (note_line_index, line) in message.lines().enumerate() {
647e73685ebSopenharmony_ci            self.outer_gutter(outer_padding)?;
648e73685ebSopenharmony_ci            match note_line_index {
649e73685ebSopenharmony_ci                0 => {
650e73685ebSopenharmony_ci                    self.set_color(&self.styles().note_bullet)?;
651e73685ebSopenharmony_ci                    write!(self, "{}", self.chars().note_bullet)?;
652e73685ebSopenharmony_ci                    self.reset()?;
653e73685ebSopenharmony_ci                }
654e73685ebSopenharmony_ci                _ => write!(self, " ")?,
655e73685ebSopenharmony_ci            }
656e73685ebSopenharmony_ci            // Write line of message
657e73685ebSopenharmony_ci            writeln!(self, " {}", line)?;
658e73685ebSopenharmony_ci        }
659e73685ebSopenharmony_ci
660e73685ebSopenharmony_ci        Ok(())
661e73685ebSopenharmony_ci    }
662e73685ebSopenharmony_ci
663e73685ebSopenharmony_ci    /// Adds tab-stop aware unicode-width computations to an iterator over
664e73685ebSopenharmony_ci    /// character indices. Assumes that the character indices begin at the start
665e73685ebSopenharmony_ci    /// of the line.
666e73685ebSopenharmony_ci    fn char_metrics(
667e73685ebSopenharmony_ci        &self,
668e73685ebSopenharmony_ci        char_indices: impl Iterator<Item = (usize, char)>,
669e73685ebSopenharmony_ci    ) -> impl Iterator<Item = (Metrics, char)> {
670e73685ebSopenharmony_ci        use unicode_width::UnicodeWidthChar;
671e73685ebSopenharmony_ci
672e73685ebSopenharmony_ci        let tab_width = self.config.tab_width;
673e73685ebSopenharmony_ci        let mut unicode_column = 0;
674e73685ebSopenharmony_ci
675e73685ebSopenharmony_ci        char_indices.map(move |(byte_index, ch)| {
676e73685ebSopenharmony_ci            let metrics = Metrics {
677e73685ebSopenharmony_ci                byte_index,
678e73685ebSopenharmony_ci                unicode_width: match (ch, tab_width) {
679e73685ebSopenharmony_ci                    ('\t', 0) => 0, // Guard divide-by-zero
680e73685ebSopenharmony_ci                    ('\t', _) => tab_width - (unicode_column % tab_width),
681e73685ebSopenharmony_ci                    (ch, _) => ch.width().unwrap_or(0),
682e73685ebSopenharmony_ci                },
683e73685ebSopenharmony_ci            };
684e73685ebSopenharmony_ci            unicode_column += metrics.unicode_width;
685e73685ebSopenharmony_ci
686e73685ebSopenharmony_ci            (metrics, ch)
687e73685ebSopenharmony_ci        })
688e73685ebSopenharmony_ci    }
689e73685ebSopenharmony_ci
690e73685ebSopenharmony_ci    /// Location focus.
691e73685ebSopenharmony_ci    fn snippet_locus(&mut self, locus: &Locus) -> Result<(), Error> {
692e73685ebSopenharmony_ci        write!(
693e73685ebSopenharmony_ci            self,
694e73685ebSopenharmony_ci            "{name}:{line_number}:{column_number}",
695e73685ebSopenharmony_ci            name = locus.name,
696e73685ebSopenharmony_ci            line_number = locus.location.line_number,
697e73685ebSopenharmony_ci            column_number = locus.location.column_number,
698e73685ebSopenharmony_ci        )?;
699e73685ebSopenharmony_ci        Ok(())
700e73685ebSopenharmony_ci    }
701e73685ebSopenharmony_ci
702e73685ebSopenharmony_ci    /// The outer gutter of a source line.
703e73685ebSopenharmony_ci    fn outer_gutter(&mut self, outer_padding: usize) -> Result<(), Error> {
704e73685ebSopenharmony_ci        write!(self, "{space: >width$} ", space = "", width = outer_padding)?;
705e73685ebSopenharmony_ci        Ok(())
706e73685ebSopenharmony_ci    }
707e73685ebSopenharmony_ci
708e73685ebSopenharmony_ci    /// The outer gutter of a source line, with line number.
709e73685ebSopenharmony_ci    fn outer_gutter_number(
710e73685ebSopenharmony_ci        &mut self,
711e73685ebSopenharmony_ci        line_number: usize,
712e73685ebSopenharmony_ci        outer_padding: usize,
713e73685ebSopenharmony_ci    ) -> Result<(), Error> {
714e73685ebSopenharmony_ci        self.set_color(&self.styles().line_number)?;
715e73685ebSopenharmony_ci        write!(
716e73685ebSopenharmony_ci            self,
717e73685ebSopenharmony_ci            "{line_number: >width$}",
718e73685ebSopenharmony_ci            line_number = line_number,
719e73685ebSopenharmony_ci            width = outer_padding,
720e73685ebSopenharmony_ci        )?;
721e73685ebSopenharmony_ci        self.reset()?;
722e73685ebSopenharmony_ci        write!(self, " ")?;
723e73685ebSopenharmony_ci        Ok(())
724e73685ebSopenharmony_ci    }
725e73685ebSopenharmony_ci
726e73685ebSopenharmony_ci    /// The left-hand border of a source line.
727e73685ebSopenharmony_ci    fn border_left(&mut self) -> Result<(), Error> {
728e73685ebSopenharmony_ci        self.set_color(&self.styles().source_border)?;
729e73685ebSopenharmony_ci        write!(self, "{}", self.chars().source_border_left)?;
730e73685ebSopenharmony_ci        self.reset()?;
731e73685ebSopenharmony_ci        Ok(())
732e73685ebSopenharmony_ci    }
733e73685ebSopenharmony_ci
734e73685ebSopenharmony_ci    /// The broken left-hand border of a source line.
735e73685ebSopenharmony_ci    fn border_left_break(&mut self) -> Result<(), Error> {
736e73685ebSopenharmony_ci        self.set_color(&self.styles().source_border)?;
737e73685ebSopenharmony_ci        write!(self, "{}", self.chars().source_border_left_break)?;
738e73685ebSopenharmony_ci        self.reset()?;
739e73685ebSopenharmony_ci        Ok(())
740e73685ebSopenharmony_ci    }
741e73685ebSopenharmony_ci
742e73685ebSopenharmony_ci    /// Write vertical lines pointing to carets.
743e73685ebSopenharmony_ci    fn caret_pointers(
744e73685ebSopenharmony_ci        &mut self,
745e73685ebSopenharmony_ci        severity: Severity,
746e73685ebSopenharmony_ci        max_label_start: usize,
747e73685ebSopenharmony_ci        single_labels: &[SingleLabel<'_>],
748e73685ebSopenharmony_ci        trailing_label: Option<(usize, &SingleLabel<'_>)>,
749e73685ebSopenharmony_ci        char_indices: impl Iterator<Item = (usize, char)>,
750e73685ebSopenharmony_ci    ) -> Result<(), Error> {
751e73685ebSopenharmony_ci        for (metrics, ch) in self.char_metrics(char_indices) {
752e73685ebSopenharmony_ci            let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
753e73685ebSopenharmony_ci            let label_style = hanging_labels(single_labels, trailing_label)
754e73685ebSopenharmony_ci                .filter(|(_, range, _)| column_range.contains(&range.start))
755e73685ebSopenharmony_ci                .map(|(label_style, _, _)| *label_style)
756e73685ebSopenharmony_ci                .max_by_key(label_priority_key);
757e73685ebSopenharmony_ci
758e73685ebSopenharmony_ci            let mut spaces = match label_style {
759e73685ebSopenharmony_ci                None => 0..metrics.unicode_width,
760e73685ebSopenharmony_ci                Some(label_style) => {
761e73685ebSopenharmony_ci                    self.set_color(self.styles().label(severity, label_style))?;
762e73685ebSopenharmony_ci                    write!(self, "{}", self.chars().pointer_left)?;
763e73685ebSopenharmony_ci                    self.reset()?;
764e73685ebSopenharmony_ci                    1..metrics.unicode_width
765e73685ebSopenharmony_ci                }
766e73685ebSopenharmony_ci            };
767e73685ebSopenharmony_ci            // Only print padding if we are before the end of the last single line caret
768e73685ebSopenharmony_ci            if metrics.byte_index <= max_label_start {
769e73685ebSopenharmony_ci                spaces.try_for_each(|_| write!(self, " "))?;
770e73685ebSopenharmony_ci            }
771e73685ebSopenharmony_ci        }
772e73685ebSopenharmony_ci
773e73685ebSopenharmony_ci        Ok(())
774e73685ebSopenharmony_ci    }
775e73685ebSopenharmony_ci
776e73685ebSopenharmony_ci    /// The left of a multi-line label.
777e73685ebSopenharmony_ci    ///
778e73685ebSopenharmony_ci    /// ```text
779e73685ebSopenharmony_ci    ///  │
780e73685ebSopenharmony_ci    /// ```
781e73685ebSopenharmony_ci    fn label_multi_left(
782e73685ebSopenharmony_ci        &mut self,
783e73685ebSopenharmony_ci        severity: Severity,
784e73685ebSopenharmony_ci        label_style: LabelStyle,
785e73685ebSopenharmony_ci        underline: Option<LabelStyle>,
786e73685ebSopenharmony_ci    ) -> Result<(), Error> {
787e73685ebSopenharmony_ci        match underline {
788e73685ebSopenharmony_ci            None => write!(self, " ")?,
789e73685ebSopenharmony_ci            // Continue an underline horizontally
790e73685ebSopenharmony_ci            Some(label_style) => {
791e73685ebSopenharmony_ci                self.set_color(self.styles().label(severity, label_style))?;
792e73685ebSopenharmony_ci                write!(self, "{}", self.chars().multi_top)?;
793e73685ebSopenharmony_ci                self.reset()?;
794e73685ebSopenharmony_ci            }
795e73685ebSopenharmony_ci        }
796e73685ebSopenharmony_ci        self.set_color(self.styles().label(severity, label_style))?;
797e73685ebSopenharmony_ci        write!(self, "{}", self.chars().multi_left)?;
798e73685ebSopenharmony_ci        self.reset()?;
799e73685ebSopenharmony_ci        Ok(())
800e73685ebSopenharmony_ci    }
801e73685ebSopenharmony_ci
802e73685ebSopenharmony_ci    /// The top-left of a multi-line label.
803e73685ebSopenharmony_ci    ///
804e73685ebSopenharmony_ci    /// ```text
805e73685ebSopenharmony_ci    ///  ╭
806e73685ebSopenharmony_ci    /// ```
807e73685ebSopenharmony_ci    fn label_multi_top_left(
808e73685ebSopenharmony_ci        &mut self,
809e73685ebSopenharmony_ci        severity: Severity,
810e73685ebSopenharmony_ci        label_style: LabelStyle,
811e73685ebSopenharmony_ci    ) -> Result<(), Error> {
812e73685ebSopenharmony_ci        write!(self, " ")?;
813e73685ebSopenharmony_ci        self.set_color(self.styles().label(severity, label_style))?;
814e73685ebSopenharmony_ci        write!(self, "{}", self.chars().multi_top_left)?;
815e73685ebSopenharmony_ci        self.reset()?;
816e73685ebSopenharmony_ci        Ok(())
817e73685ebSopenharmony_ci    }
818e73685ebSopenharmony_ci
819e73685ebSopenharmony_ci    /// The bottom left of a multi-line label.
820e73685ebSopenharmony_ci    ///
821e73685ebSopenharmony_ci    /// ```text
822e73685ebSopenharmony_ci    ///  ╰
823e73685ebSopenharmony_ci    /// ```
824e73685ebSopenharmony_ci    fn label_multi_bottom_left(
825e73685ebSopenharmony_ci        &mut self,
826e73685ebSopenharmony_ci        severity: Severity,
827e73685ebSopenharmony_ci        label_style: LabelStyle,
828e73685ebSopenharmony_ci    ) -> Result<(), Error> {
829e73685ebSopenharmony_ci        write!(self, " ")?;
830e73685ebSopenharmony_ci        self.set_color(self.styles().label(severity, label_style))?;
831e73685ebSopenharmony_ci        write!(self, "{}", self.chars().multi_bottom_left)?;
832e73685ebSopenharmony_ci        self.reset()?;
833e73685ebSopenharmony_ci        Ok(())
834e73685ebSopenharmony_ci    }
835e73685ebSopenharmony_ci
836e73685ebSopenharmony_ci    /// Multi-line label top.
837e73685ebSopenharmony_ci    ///
838e73685ebSopenharmony_ci    /// ```text
839e73685ebSopenharmony_ci    /// ─────────────^
840e73685ebSopenharmony_ci    /// ```
841e73685ebSopenharmony_ci    fn label_multi_top_caret(
842e73685ebSopenharmony_ci        &mut self,
843e73685ebSopenharmony_ci        severity: Severity,
844e73685ebSopenharmony_ci        label_style: LabelStyle,
845e73685ebSopenharmony_ci        source: &str,
846e73685ebSopenharmony_ci        start: usize,
847e73685ebSopenharmony_ci    ) -> Result<(), Error> {
848e73685ebSopenharmony_ci        self.set_color(self.styles().label(severity, label_style))?;
849e73685ebSopenharmony_ci
850e73685ebSopenharmony_ci        for (metrics, _) in self
851e73685ebSopenharmony_ci            .char_metrics(source.char_indices())
852e73685ebSopenharmony_ci            .take_while(|(metrics, _)| metrics.byte_index < start + 1)
853e73685ebSopenharmony_ci        {
854e73685ebSopenharmony_ci            // FIXME: improve rendering of carets between character boundaries
855e73685ebSopenharmony_ci            (0..metrics.unicode_width)
856e73685ebSopenharmony_ci                .try_for_each(|_| write!(self, "{}", self.chars().multi_top))?;
857e73685ebSopenharmony_ci        }
858e73685ebSopenharmony_ci
859e73685ebSopenharmony_ci        let caret_start = match label_style {
860e73685ebSopenharmony_ci            LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
861e73685ebSopenharmony_ci            LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
862e73685ebSopenharmony_ci        };
863e73685ebSopenharmony_ci        write!(self, "{}", caret_start)?;
864e73685ebSopenharmony_ci        self.reset()?;
865e73685ebSopenharmony_ci        writeln!(self)?;
866e73685ebSopenharmony_ci        Ok(())
867e73685ebSopenharmony_ci    }
868e73685ebSopenharmony_ci
869e73685ebSopenharmony_ci    /// Multi-line label bottom, with a message.
870e73685ebSopenharmony_ci    ///
871e73685ebSopenharmony_ci    /// ```text
872e73685ebSopenharmony_ci    /// ─────────────^ expected `Int` but found `String`
873e73685ebSopenharmony_ci    /// ```
874e73685ebSopenharmony_ci    fn label_multi_bottom_caret(
875e73685ebSopenharmony_ci        &mut self,
876e73685ebSopenharmony_ci        severity: Severity,
877e73685ebSopenharmony_ci        label_style: LabelStyle,
878e73685ebSopenharmony_ci        source: &str,
879e73685ebSopenharmony_ci        start: usize,
880e73685ebSopenharmony_ci        message: &str,
881e73685ebSopenharmony_ci    ) -> Result<(), Error> {
882e73685ebSopenharmony_ci        self.set_color(self.styles().label(severity, label_style))?;
883e73685ebSopenharmony_ci
884e73685ebSopenharmony_ci        for (metrics, _) in self
885e73685ebSopenharmony_ci            .char_metrics(source.char_indices())
886e73685ebSopenharmony_ci            .take_while(|(metrics, _)| metrics.byte_index < start)
887e73685ebSopenharmony_ci        {
888e73685ebSopenharmony_ci            // FIXME: improve rendering of carets between character boundaries
889e73685ebSopenharmony_ci            (0..metrics.unicode_width)
890e73685ebSopenharmony_ci                .try_for_each(|_| write!(self, "{}", self.chars().multi_bottom))?;
891e73685ebSopenharmony_ci        }
892e73685ebSopenharmony_ci
893e73685ebSopenharmony_ci        let caret_end = match label_style {
894e73685ebSopenharmony_ci            LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
895e73685ebSopenharmony_ci            LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
896e73685ebSopenharmony_ci        };
897e73685ebSopenharmony_ci        write!(self, "{}", caret_end)?;
898e73685ebSopenharmony_ci        if !message.is_empty() {
899e73685ebSopenharmony_ci            write!(self, " {}", message)?;
900e73685ebSopenharmony_ci        }
901e73685ebSopenharmony_ci        self.reset()?;
902e73685ebSopenharmony_ci        writeln!(self)?;
903e73685ebSopenharmony_ci        Ok(())
904e73685ebSopenharmony_ci    }
905e73685ebSopenharmony_ci
906e73685ebSopenharmony_ci    /// Writes an empty gutter space, or continues an underline horizontally.
907e73685ebSopenharmony_ci    fn inner_gutter_column(
908e73685ebSopenharmony_ci        &mut self,
909e73685ebSopenharmony_ci        severity: Severity,
910e73685ebSopenharmony_ci        underline: Option<Underline>,
911e73685ebSopenharmony_ci    ) -> Result<(), Error> {
912e73685ebSopenharmony_ci        match underline {
913e73685ebSopenharmony_ci            None => self.inner_gutter_space(),
914e73685ebSopenharmony_ci            Some((label_style, vertical_bound)) => {
915e73685ebSopenharmony_ci                self.set_color(self.styles().label(severity, label_style))?;
916e73685ebSopenharmony_ci                let ch = match vertical_bound {
917e73685ebSopenharmony_ci                    VerticalBound::Top => self.config.chars.multi_top,
918e73685ebSopenharmony_ci                    VerticalBound::Bottom => self.config.chars.multi_bottom,
919e73685ebSopenharmony_ci                };
920e73685ebSopenharmony_ci                write!(self, "{0}{0}", ch)?;
921e73685ebSopenharmony_ci                self.reset()?;
922e73685ebSopenharmony_ci                Ok(())
923e73685ebSopenharmony_ci            }
924e73685ebSopenharmony_ci        }
925e73685ebSopenharmony_ci    }
926e73685ebSopenharmony_ci
927e73685ebSopenharmony_ci    /// Writes an empty gutter space.
928e73685ebSopenharmony_ci    fn inner_gutter_space(&mut self) -> Result<(), Error> {
929e73685ebSopenharmony_ci        write!(self, "  ")?;
930e73685ebSopenharmony_ci        Ok(())
931e73685ebSopenharmony_ci    }
932e73685ebSopenharmony_ci
933e73685ebSopenharmony_ci    /// Writes an inner gutter, with the left lines if necessary.
934e73685ebSopenharmony_ci    fn inner_gutter(
935e73685ebSopenharmony_ci        &mut self,
936e73685ebSopenharmony_ci        severity: Severity,
937e73685ebSopenharmony_ci        num_multi_labels: usize,
938e73685ebSopenharmony_ci        multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
939e73685ebSopenharmony_ci    ) -> Result<(), Error> {
940e73685ebSopenharmony_ci        let mut multi_labels_iter = multi_labels.iter().peekable();
941e73685ebSopenharmony_ci        for label_column in 0..num_multi_labels {
942e73685ebSopenharmony_ci            match multi_labels_iter.peek() {
943e73685ebSopenharmony_ci                Some((label_index, ls, label)) if *label_index == label_column => match label {
944e73685ebSopenharmony_ci                    MultiLabel::Left | MultiLabel::Bottom(..) => {
945e73685ebSopenharmony_ci                        self.label_multi_left(severity, *ls, None)?;
946e73685ebSopenharmony_ci                        multi_labels_iter.next();
947e73685ebSopenharmony_ci                    }
948e73685ebSopenharmony_ci                    MultiLabel::Top(..) => {
949e73685ebSopenharmony_ci                        self.inner_gutter_space()?;
950e73685ebSopenharmony_ci                        multi_labels_iter.next();
951e73685ebSopenharmony_ci                    }
952e73685ebSopenharmony_ci                },
953e73685ebSopenharmony_ci                Some((_, _, _)) | None => self.inner_gutter_space()?,
954e73685ebSopenharmony_ci            }
955e73685ebSopenharmony_ci        }
956e73685ebSopenharmony_ci
957e73685ebSopenharmony_ci        Ok(())
958e73685ebSopenharmony_ci    }
959e73685ebSopenharmony_ci}
960e73685ebSopenharmony_ci
961e73685ebSopenharmony_ciimpl<'writer, 'config> Write for Renderer<'writer, 'config> {
962e73685ebSopenharmony_ci    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
963e73685ebSopenharmony_ci        self.writer.write(buf)
964e73685ebSopenharmony_ci    }
965e73685ebSopenharmony_ci
966e73685ebSopenharmony_ci    fn flush(&mut self) -> io::Result<()> {
967e73685ebSopenharmony_ci        self.writer.flush()
968e73685ebSopenharmony_ci    }
969e73685ebSopenharmony_ci}
970e73685ebSopenharmony_ci
971e73685ebSopenharmony_ciimpl<'writer, 'config> WriteColor for Renderer<'writer, 'config> {
972e73685ebSopenharmony_ci    fn supports_color(&self) -> bool {
973e73685ebSopenharmony_ci        self.writer.supports_color()
974e73685ebSopenharmony_ci    }
975e73685ebSopenharmony_ci
976e73685ebSopenharmony_ci    fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
977e73685ebSopenharmony_ci        self.writer.set_color(spec)
978e73685ebSopenharmony_ci    }
979e73685ebSopenharmony_ci
980e73685ebSopenharmony_ci    fn reset(&mut self) -> io::Result<()> {
981e73685ebSopenharmony_ci        self.writer.reset()
982e73685ebSopenharmony_ci    }
983e73685ebSopenharmony_ci
984e73685ebSopenharmony_ci    fn is_synchronous(&self) -> bool {
985e73685ebSopenharmony_ci        self.writer.is_synchronous()
986e73685ebSopenharmony_ci    }
987e73685ebSopenharmony_ci}
988e73685ebSopenharmony_ci
989e73685ebSopenharmony_cistruct Metrics {
990e73685ebSopenharmony_ci    byte_index: usize,
991e73685ebSopenharmony_ci    unicode_width: usize,
992e73685ebSopenharmony_ci}
993e73685ebSopenharmony_ci
994e73685ebSopenharmony_ci/// Check if two ranges overlap
995e73685ebSopenharmony_cifn is_overlapping(range0: &Range<usize>, range1: &Range<usize>) -> bool {
996e73685ebSopenharmony_ci    let start = std::cmp::max(range0.start, range1.start);
997e73685ebSopenharmony_ci    let end = std::cmp::min(range0.end, range1.end);
998e73685ebSopenharmony_ci    start < end
999e73685ebSopenharmony_ci}
1000e73685ebSopenharmony_ci
1001e73685ebSopenharmony_ci/// For prioritizing primary labels over secondary labels when rendering carets.
1002e73685ebSopenharmony_cifn label_priority_key(label_style: &LabelStyle) -> u8 {
1003e73685ebSopenharmony_ci    match label_style {
1004e73685ebSopenharmony_ci        LabelStyle::Secondary => 0,
1005e73685ebSopenharmony_ci        LabelStyle::Primary => 1,
1006e73685ebSopenharmony_ci    }
1007e73685ebSopenharmony_ci}
1008e73685ebSopenharmony_ci
1009e73685ebSopenharmony_ci/// Return an iterator that yields the labels that require hanging messages
1010e73685ebSopenharmony_ci/// rendered underneath them.
1011e73685ebSopenharmony_cifn hanging_labels<'labels, 'diagnostic>(
1012e73685ebSopenharmony_ci    single_labels: &'labels [SingleLabel<'diagnostic>],
1013e73685ebSopenharmony_ci    trailing_label: Option<(usize, &'labels SingleLabel<'diagnostic>)>,
1014e73685ebSopenharmony_ci) -> impl 'labels + DoubleEndedIterator<Item = &'labels SingleLabel<'diagnostic>> {
1015e73685ebSopenharmony_ci    single_labels
1016e73685ebSopenharmony_ci        .iter()
1017e73685ebSopenharmony_ci        .enumerate()
1018e73685ebSopenharmony_ci        .filter(|(_, (_, _, message))| !message.is_empty())
1019e73685ebSopenharmony_ci        .filter(move |(i, _)| trailing_label.map_or(true, |(j, _)| *i != j))
1020e73685ebSopenharmony_ci        .map(|(_, label)| label)
1021e73685ebSopenharmony_ci}
1022