Skip to content

Commit 96c9b6b

Browse files
committed
feat: Add support for hyperlinks in snippet origin
1 parent a8b61d7 commit 96c9b6b

5 files changed

Lines changed: 170 additions & 0 deletions

File tree

src/renderer/render.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,23 @@ fn render_origin(
488488
_ => origin.path.to_string(),
489489
};
490490

491+
if let Some(url) = &origin.url {
492+
let url = normalize_whitespace(url);
493+
buffer.append(
494+
buffer_msg_line_offset,
495+
&format!("\x1B]8;;{url}\x1B\\"),
496+
ElementStyle::LineAndColumn,
497+
);
498+
}
491499
buffer.append(buffer_msg_line_offset, &str, ElementStyle::LineAndColumn);
500+
if origin.url.is_some() {
501+
buffer.append(
502+
buffer_msg_line_offset,
503+
"\x1B]8;;\x1B\\",
504+
ElementStyle::LineAndColumn,
505+
);
506+
}
507+
492508
if !renderer.short_message {
493509
for _ in 0..max_line_num_len {
494510
buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle);
@@ -555,6 +571,11 @@ fn render_snippet_annotations(
555571
}
556572
}
557573
}
574+
575+
if let Some(url) = &snippet.url {
576+
origin.url = Some(Cow::Borrowed(url));
577+
}
578+
558579
let buffer_msg_line_offset = buffer.num_lines();
559580
render_origin(
560581
renderer,

src/snippet.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ pub struct Snippet<'a, T> {
227227
pub(crate) line_start: usize,
228228
pub(crate) source: Cow<'a, str>,
229229
pub(crate) markers: Vec<T>,
230+
pub(crate) url: Option<Cow<'a, str>>,
230231
pub(crate) fold: bool,
231232
}
232233

@@ -246,6 +247,7 @@ impl<'a, T: Clone> Snippet<'a, T> {
246247
line_start: 1,
247248
source: source.into(),
248249
markers: vec![],
250+
url: None,
249251
fold: true,
250252
}
251253
}
@@ -271,6 +273,20 @@ impl<'a, T: Clone> Snippet<'a, T> {
271273
self
272274
}
273275

276+
/// The URL to the [`source`][Self::source]. Can be a `file://` URL.
277+
///
278+
/// <div class="warning">
279+
///
280+
/// Text passed to this function is considered "untrusted input", as such
281+
/// all text is passed through a normalization function. Pre-styled text is
282+
/// not allowed to be passed to this function.
283+
///
284+
/// </div>
285+
pub fn url(mut self, url: impl Into<OptionCow<'a>>) -> Self {
286+
self.url = url.into().0;
287+
self
288+
}
289+
274290
/// Control whether lines without [`Annotation`]s are shown
275291
///
276292
/// The default is `fold(true)`, collapsing uninteresting lines.
@@ -487,6 +503,7 @@ pub struct Origin<'a> {
487503
pub(crate) path: Cow<'a, str>,
488504
pub(crate) line: Option<usize>,
489505
pub(crate) char_column: Option<usize>,
506+
pub(crate) url: Option<Cow<'a, str>>,
490507
}
491508

492509
impl<'a> Origin<'a> {
@@ -502,6 +519,7 @@ impl<'a> Origin<'a> {
502519
path: path.into(),
503520
line: None,
504521
char_column: None,
522+
url: None,
505523
}
506524
}
507525

@@ -522,6 +540,20 @@ impl<'a> Origin<'a> {
522540
self.char_column = Some(char_column);
523541
self
524542
}
543+
544+
/// The URL to the [`source`][Self::source]. Can be a `file://` URL.
545+
///
546+
/// <div class="warning">
547+
///
548+
/// Text passed to this function is considered "untrusted input", as such
549+
/// all text is passed through a normalization function. Pre-styled text is
550+
/// not allowed to be passed to this function.
551+
///
552+
/// </div>
553+
pub fn url(mut self, uri: impl Into<Cow<'a, str>>) -> Self {
554+
self.url = Some(uri.into());
555+
self
556+
}
525557
}
526558

527559
impl<'a> From<Cow<'a, str>> for Origin<'a> {

tests/hyperlink.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use annotate_snippets::{renderer::DecorStyle, AnnotationKind, Level, Renderer, Snippet};
2+
use snapbox::{assert_data_eq, file};
3+
4+
#[test]
5+
fn simple() {
6+
// Most basic test: check that the a hyperlink shows up in the error message displayed in the
7+
// README.
8+
let source = r#" annotations: vec![SourceAnnotation {
9+
label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
10+
,
11+
range: <22, 25>,"#;
12+
let report =
13+
&[Level::ERROR
14+
.primary_title("expected type, found `22`")
15+
.element(
16+
Snippet::source(source)
17+
.line_start(26)
18+
.path("examples/footer.rs")
19+
.url("file://localhost/home/user/rust/file.rs")
20+
.annotation(AnnotationKind::Primary.span(193..195).label(
21+
"expected struct `annotate_snippets::snippet::Slice`, found reference",
22+
))
23+
.annotation(
24+
AnnotationKind::Context
25+
.span(34..50)
26+
.label("while parsing this struct"),
27+
),
28+
)];
29+
30+
let expected_ascii = file!["hyperlink_expected_type.ascii.term.svg": TermSvg];
31+
let renderer = Renderer::styled().decor_style(DecorStyle::Ascii);
32+
assert_data_eq!(renderer.render(report), expected_ascii);
33+
34+
let expected_unicode = file!["hyperlink_expected_type.unicode.term.svg": TermSvg];
35+
let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
36+
assert_data_eq!(renderer.render(report), expected_unicode);
37+
}
Lines changed: 40 additions & 0 deletions
Loading
Lines changed: 40 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)