Most recent terminals now support hyperlink thanks to OSC 8. Embedding hyperlinks in compiler error messages would allows users to quickly jump back to their code and fix the error they've just read in their terminal.
Some terminals/text editors (at least Zed and VSCode) already support this in a hack-ish way by trying to guess what could be a path and opening a file with the same name when the path is clicked. Embedding hyperlinks in annotate-snippet output would make this much more reliable and make it available to other terminals/text editors as well!
Hyperlinks probably need to be opt-in, as I'm pretty sure there are some situations in which it is not desirable to embed hyperlinks :D
We also need to make sure we embed the right URIs, as different applications expect different URI format. rg embeds a few presets (source):
| Alias |
Expands to |
default |
Platform-aware file:// scheme (see below) |
file |
file://{host}{path} |
vscode |
vscode://file/{path}:{line}:{column} |
vscode-insiders |
vscode-insiders://file/{path}:{line}:{column} |
vscodium |
vscodium://file/{path}:{line}:{column} |
cursor |
cursor://file/{path}:{line}:{column} |
macvim |
mvim://open?url=file://{path}&line={line}&column={column} |
textmate |
txmt://open?url=file://{path}&line={line}&column={column} |
kitty |
file://{host}{path}#{line} |
grep+ |
grep+://{path}:{line} |
none |
Explicitly disable hyperlinks |
I think it'd be reasonable to do the same thing and let the program who uses annotate-snippets pick which URI scheme should be used. In the future we may want to let the user supply custom URI formats, but the list above is good enough as a first start.
In order to generate a valid URI, we need a few things:
- (1) The hostname of the machine the file originates from.
- (2) The absolute path to the file the diagnostic originates from.
- (3) The line/column an annotation refers to.
For (1), we have a bunch of default values:
"" (the empty string) and "localhost", which would break when the diagnostic is displayed on a device that is not the device the file originates to (compiler run over an SSH connexion)
- The value returned by
gethostname() (the POSIX C function). This would require FFI and unsafe code, which I don't really want to find in a diagnostic rendering library.
- The value returned by
std::net::hostname, which is nightly-only (tracking issue), which requires bumping the MSRV from 1.66 to nightly which is a big no no.
- Reading the content of the
HOSTNAME environment variable. This environment variable is not mandated by POSIX, and apparently not set in sh (source).
- The output of the
hostname command. Running it takes around 30ms on my machine but I guess it's ok to be slow when rendering diagnostics?
I think spawning hostname and reading its output is the most robust thing to do and would prefer doing this. In any case, we should encourage annotate-snippets users to provide a hostname if there's an easy way to get it and use this value over any of the default value listed above.
For (2), we could call Path::canonicalize on the snippet path and use the returned path. This has some downsides (some are listed in the rg code):
- Symlinks are removed.
- This touches the filesystem quite a lot, but once again I think we could allow ourselves to be slow when rendering diagnostics.
Here again, we should allow annotate-snippet users to provide their own absolute path and call Path::canonicalize only when we don't have anything else.
For (3), we could use the line/column we compute from the annotation span and not include any line/column when the annotation is not spanned.
We should also be treat absolute paths and hostnames as untrusted inputs and perform all the normalizations needed before turning them into hyperlinks. I hope it is reasonable to expect that no sane computer user uses special characters in their hostname/file paths.
Finally, we should stay as conservative as possible and fallback to non-hyperlink rendering when there's not enough information available to do it.
Given all the requirements and ideas listed above, hyperlinks could be added to the example displayed in the README with something like:
fn main() {
let source = r#" annotations: vec![SourceAnnotation {
label: "expected struct `annotate_snippets::snippet::Slice`, found reference"
,
range: <22, 25>,"#;
let report = &[Level::ERROR
.primary_title("expected type, found `22`")
.element(
Snippet::source(source)
.line_start(26)
.path("examples/footer.rs")
.full_path(
"/home/scrabsha/git/github/scrabsha/annotate-snippets-rs/examples/footer.rs",
)
.annotation(
AnnotationKind::Primary.span(193..195).label(
"expected struct `annotate_snippets::snippet::Slice`, found reference",
),
)
.annotation(
AnnotationKind::Context
.span(34..50)
.label("while parsing this struct"),
),
)];
let renderer = Renderer::styled()
.decor_style(DecorStyle::Unicode)
.hyperlink_format(HyperlinkFormat::Kitty)
.hostname("localhost");
anstream::println!("{}", renderer.render(report));
}
This would require adding the following APIs:
impl<'a, T: Clone> Snippet<'a, T> {
pub fn full_path(self, full_path: impl Into<OptionCow<'a>>) -> Self { ... }
}
impl<'a> Origin<'a> {
pub fn full_path(mut self, uri: impl Into<Cow<'a, str>>) -> Self { ... }
}
impl<'a> Renderer<'a> {
pub const fn hyperlink_format(mut self, hyperlink_format: HyperlinkFormat) -> Self { ... }
pub const fn hostname(mut self, hostname: &'a str) -> Self { ... }
}
pub enum HyperlinkFormat {
Default,
File,
VsCode,
Kitty,
// ...
}
Most recent terminals now support hyperlink thanks to OSC 8. Embedding hyperlinks in compiler error messages would allows users to quickly jump back to their code and fix the error they've just read in their terminal.
Some terminals/text editors (at least Zed and VSCode) already support this in a hack-ish way by trying to guess what could be a path and opening a file with the same name when the path is clicked. Embedding hyperlinks in
annotate-snippetoutput would make this much more reliable and make it available to other terminals/text editors as well!Hyperlinks probably need to be opt-in, as I'm pretty sure there are some situations in which it is not desirable to embed hyperlinks :D
We also need to make sure we embed the right URIs, as different applications expect different URI format.
rgembeds a few presets (source):I think it'd be reasonable to do the same thing and let the program who uses
annotate-snippetspick which URI scheme should be used. In the future we may want to let the user supply custom URI formats, but the list above is good enough as a first start.In order to generate a valid URI, we need a few things:
For (1), we have a bunch of default values:
""(the empty string) and"localhost", which would break when the diagnostic is displayed on a device that is not the device the file originates to (compiler run over an SSH connexion)gethostname()(the POSIX C function). This would require FFI and unsafe code, which I don't really want to find in a diagnostic rendering library.std::net::hostname, which is nightly-only (tracking issue), which requires bumping the MSRV from 1.66 to nightly which is a big no no.HOSTNAMEenvironment variable. This environment variable is not mandated by POSIX, and apparently not set insh(source).hostnamecommand. Running it takes around 30ms on my machine but I guess it's ok to be slow when rendering diagnostics?I think spawning
hostnameand reading its output is the most robust thing to do and would prefer doing this. In any case, we should encourageannotate-snippetsusers to provide a hostname if there's an easy way to get it and use this value over any of the default value listed above.For (2), we could call
Path::canonicalizeon the snippet path and use the returned path. This has some downsides (some are listed in thergcode):Here again, we should allow
annotate-snippetusers to provide their own absolute path and callPath::canonicalizeonly when we don't have anything else.For (3), we could use the line/column we compute from the annotation span and not include any line/column when the annotation is not spanned.
We should also be treat absolute paths and hostnames as untrusted inputs and perform all the normalizations needed before turning them into hyperlinks. I hope it is reasonable to expect that no sane computer user uses special characters in their hostname/file paths.
Finally, we should stay as conservative as possible and fallback to non-hyperlink rendering when there's not enough information available to do it.
Given all the requirements and ideas listed above, hyperlinks could be added to the example displayed in the README with something like:
This would require adding the following APIs: