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