Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub(crate) struct LabelDisplay {
pub color: Option<Color>,
pub order: i32,
pub priority: i32,
pub collapse_when: LabelCollapseLines,
}

/// A type that represents a labelled section of source code.
Expand All @@ -37,6 +38,7 @@ impl<S: Span> Label<S> {
color: None,
order: 0,
priority: 0,
collapse_when: LabelCollapseLines::Always,
},
}
}
Expand Down Expand Up @@ -81,6 +83,14 @@ impl<S: Span> Label<S> {
self.display_info.priority = priority;
self
}

/// Specify when the label should hide multiline spans
///
/// If unspecified, defaults to always collapsing multiline labels with `LabelCollapseLines::Always`
pub fn with_collapse_lines_when(mut self, collapse_when: LabelCollapseLines) -> Self {
self.display_info.collapse_when = collapse_when;
self
}
}

/// The attachment point of inline label arrows
Expand All @@ -93,6 +103,18 @@ pub enum LabelAttach {
/// Arrows should attach to the end of the label span.
End,
}

/// Whether a multiline label should hide its contents
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum LabelCollapseLines {
/// Hides the contents of the label with if it's over the line limit
MaxLines(usize),
/// Always hides contents of multiline labels (only show first and last lines)
Always,
/// Always show the entire label contents (always show full span)
Never,
}

#[test]
#[should_panic]
#[allow(clippy::reversed_empty_ranges)]
Expand Down
57 changes: 56 additions & 1 deletion src/report/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
use insta::assert_snapshot;

use crate::{
Cache, Config, FnCache, IndexType, Label, Report, ReportKind, ReportStyle, Source, Span,
Cache, Config, FnCache, IndexType, Label, LabelCollapseLines, Report, ReportKind, ReportStyle,
Source, Span,
};

impl<S: Span, K: ReportStyle> Report<S, K> {
Expand Down Expand Up @@ -498,6 +499,60 @@ fn multiple_multilines_same_span() {
");
}

#[test]
fn multiline_label_show_3() {
let source = "pear\napple\n==\norange\nbanana";
let msg = remove_trailing(
Report::build(ReportKind::Error, 0..0)
.with_config(no_color())
.with_label(
Label::new(5..20)
.with_message("illegal comparison")
.with_collapse_lines_when(LabelCollapseLines::MaxLines(3)),
)
.finish()
.write_to_string(Source::from(source)),
);
assert_snapshot!(msg, @r"
Error:
╭─┤ <unknown>:1:1 │
2 │ ╭─▶ apple
3 │ │ ==
4 │ ├─▶ orange
│ │
│ ╰──────────── illegal comparison
───╯
");
}

#[test]
fn multiline_label_longer_than_max_span_line_count() {
let source = "pear\napple\n==\norange\nbanana";
let msg = remove_trailing(
Report::build(ReportKind::Error, 0..0)
.with_config(no_color())
.with_label(
Label::new(5..source.len())
.with_message("illegal comparison")
.with_collapse_lines_when(LabelCollapseLines::MaxLines(3)),
)
.finish()
.write_to_string(Source::from(source)),
);
assert_snapshot!(msg, @r"
Error:
╭─┤ <unknown>:1:1 │
2 │ ╭─▶ apple
┆ ┆
5 │ ├─▶ banana
│ │
│ ╰─────────── illegal comparison
───╯
");
}

#[test]
fn multiline_context_label() {
let source = "apple\nbanana\ncarrot\ndragonfruit\negg\nfruit\ngrapes";
Expand Down
17 changes: 15 additions & 2 deletions src/report/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::io;
use std::ops::Range;

use crate::source::Location;
use crate::{Config, IndexType, LabelDisplay, Source};
use crate::{Config, IndexType, LabelCollapseLines, LabelDisplay, Source};

use super::draw::{self, StreamAwareFmt, StreamType, WrappedWriter};
use super::{Cache, CharSet, LabelAttach, Report, ReportStyle, Rept, Show, Span, Write};
Expand Down Expand Up @@ -601,7 +601,20 @@ impl<S: Span, K: ReportStyle> Report<S, K> {
.iter()
.any(|label| label.char_span.contains(&line.span().start()));
if !is_ellipsis && within_label {
is_ellipsis = true;
// Check to see if all the multiline labels containing this line should be hidden
let should_collapse = multi_labels
.iter()
.filter(|label| label.char_span.contains(&line.span().start()))
.all(|label| match label.display_info.collapse_when {
LabelCollapseLines::Always => true,
LabelCollapseLines::MaxLines(max_lines) => {
label.end_line - label.start_line >= max_lines
}
LabelCollapseLines::Never => false,
});
if should_collapse {
is_ellipsis = true;
}
} else {
if !self.config.compact && !is_ellipsis {
write_margin(&mut w, idx, false, is_ellipsis)?;
Expand Down