feat: Add support for hyperlinks in origin#386
feat: Add support for hyperlinks in origin#386scrabsha wants to merge 5 commits intorust-lang:mainfrom
Conversation
96c9b6b to
90d3121
Compare
| ); | ||
| } | ||
|
|
||
| let str = match (&origin.line, &origin.char_column) { |
There was a problem hiding this comment.
@Muscraft can speak better to the renderer but my instinct is str should have the url in it. You can do that by having a
let (url_open, url_close) = if let Some(url) = ... {
...
} else {
(Default::default(), Default::default())
};There was a problem hiding this comment.
for what it's worth, i tried to mimic what we already do for error code URL
annotate-snippets-rs/src/renderer/render.rs
Lines 362 to 372 in a8b61d7
happy to change my code and/or the error code logic though!
There was a problem hiding this comment.
I would agree with @epage here, in that str should probably have the URL in it. Most of my reasoning comes from the fact that I am not sure whether putting the URL over the whole path:line:col or just the path is the correct course of action.
There was a problem hiding this comment.
The intent with this is to allow liking to the location with special editor links.
There was a problem hiding this comment.
[...] I am not sure whether putting the URL over the whole
path:line:color just thepathis the correct course of action.
for file:// URLs, some setups allow embedding both a line and a column, some just a line, some don't allow anything other than the path to the file. we can't always assume that clicking on a link opens the file at the right line, or line/column. that is the cruel reality we have to deal with :/
the current design of this API (aka "let the caller tell us which URL to use") is great because it allows to embed URLs that are not file:// URLs, and these other URLs very much could point to a specific line/line:column (for instance, the github permalink to a file) and it'd make perfect sense for the hyperlink to span on path:line:col in that case.
one could argue that the hyperlink should span over path when it merely links to a path, to path:line when it includes a line number and to path:line:col when it embeds a column as well. i think it'd greatly hurt muscle memory, as the chunk of clickable text would constantly change. in addition to this, path, line and col are currently displayed with the exact same style and inserting a hyperlink to only some of this "block" would look counter intuitive, in my opinion.
for all these reasons, i would clearly lean towards spanning the hyperlink over path:line:col entirely rather than anything else. happy to hear other arguments though.
anyways i'm going to change the code as suggested by @epage and add another commit on top that does the same thing for the error ID hyperlink logic.
9d4c4e3 to
0f52d34
Compare
|
quick summary of what i am about to push:
|
03ba113 to
63dd827
Compare
| /// | ||
| /// [OSC-8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda | ||
| /// [OSC-8-file-URIs]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#file-uris-and-the-hostname | ||
| pub fn path_url(mut self, url: impl Into<Cow<'a, str>>) -> Self { |
There was a problem hiding this comment.
Should we be consistent in handling of path urls?
There was a problem hiding this comment.
Origins don't have a primary span, so there's not really a line/byte column to call the formatter with. here's an example
let report = &[Level::ERROR
.primary_title("Linker exited with error status code")
.element(Origin::path("input.rs"))];
let render = Renderer::plain().render(report);(i think this is how rustc renders linker-related error messages, by the way)
There was a problem hiding this comment.
Origin does have a line/column, see https://docs.rs/annotate-snippets/latest/annotate_snippets/struct.Origin.html#method.line
I see that works in char_column. We likely should be consistent on how column count.
There was a problem hiding this comment.
then the second someone needs the byte column (ie: not byte column) they are stuck. it looks like a great way to paint ourselves in a corner.
There was a problem hiding this comment.
this makes me realize the formatter should be Fn(usize, usize, usize) -> String so that users are free to use either the char or byte column.
There was a problem hiding this comment.
When using editor URLs that take a column, how do they measure columns?
I don't think we need to be worried about a perfectly flexible solution for anything anyone might do. If there are standard conventions, we can focus on that. Generally, column likely won't make much of a difference anyways.
| } | ||
| } | ||
|
|
||
| pub trait PathUrlFormatter { |
| } | ||
|
|
||
| pub trait PathUrlFormatter { | ||
| fn format_url(&self, line: usize, byte_col: usize) -> String; |
There was a problem hiding this comment.
I still think we should pass in the path
There was a problem hiding this comment.
what if the user does not call Snippet::path, or passes None to it?
There was a problem hiding this comment.
Should we even provide a path url when there is no path?
There was a problem hiding this comment.
aren't we using "<input>" (or something else, i don't remember) as a fallback when no path is specified? if so, i want the user to be able to stick a link there too.
There was a problem hiding this comment.
When no path is specified, it looks like we don't print an Origin, so this should be fine
| // Allows to provide a consistent `Debug` implementation for `String`, | ||
| // `&str` and `Cow<str>`. Not exposed to the user. |
There was a problem hiding this comment.
Please make this a doc comment and separate out the one line summary from additional details
|
|
||
| // Wrapper over `Rc<dyn PathUrlFormatter>` that implements both `Clone` and `Debug`. | ||
| #[derive(Clone)] | ||
| pub(crate) struct PathUrlFormatterWrapper<'a>(pub(crate) Rc<dyn PathUrlFormatter + 'a>); |
There was a problem hiding this comment.
Use of Rc is a breaking change. We'd need to use Arc.
There was a problem hiding this comment.
this is causing CI issues on thumbv6m, which makes CI complain. apparently there is no Arc on this platform. not sure how we could work around that. i'll revert my change until a solution is found.
There was a problem hiding this comment.
the only solution i see would be to use Arc on platforms that support it and to fallback to Rc, but i am not thrilled by that at all. suggestions are welcome.
There was a problem hiding this comment.
using the approach described in the dyn-clone trait could help, though i am not sure we are ok with (a) adding unsafe code in this codebase or (b) adding a third party dependency to annotate_snippets just for hyperlink handling is worth it.
There was a problem hiding this comment.
We can add a clone method to our trait and we only support closures that are cloneable. I think closures get that implicitly where supported.
|
|
||
| // Wrapper over `Rc<dyn PathUrlFormatter>` that implements both `Clone` and `Debug`. | ||
| #[derive(Clone)] | ||
| pub(crate) struct PathUrlFormatterWrapper<'a>(pub(crate) Rc<dyn PathUrlFormatter + 'a>); |
There was a problem hiding this comment.
If we're boxing for the Rc, do we still need to allow lifetimes?
There was a problem hiding this comment.
dyn Trait has an implicit + 'static when no + 'lifetime is specified. this results in the following code not compiling:
use std::sync::Arc;
struct Snippet<'a> {
source: &'a str,
url: Option<WithoutLifetime>,
}
impl<'a> Snippet<'a> {
fn source(source: &'a str) -> Self {
Self {
source,
url: None,
}
}
fn url(mut self, url: impl PathUrlFormatter) -> Self {
self.url = Some(WithoutLifetime(Arc::new(url)));
self
}
}
struct WithoutLifetime(Arc<dyn PathUrlFormatter>);
trait PathUrlFormatter {}The exact error message is:
error[E0310]: the parameter type `impl PathUrlFormatter` may not live long enough
--> src/lib.rs:17:41
|
17 | self.url = Some(WithoutLifetime(Arc::new(url)));
| ^^^^^^^^^^^^^
| |
| the parameter type `impl PathUrlFormatter` must be valid for the static lifetime...
| ...so that the type `impl PathUrlFormatter` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound
|
16 | fn url(mut self, url: impl PathUrlFormatter + 'static) -> Self {
| +++++++++
For more information about this error, try `rustc --explain E0310`.
forcing the formatter to be 'static would prevent formatters from borrowing anything for less than 'static, for instance:
let hostname = std::env::var("HOSTNAME").unwrap();
let snippet = Snippet::source("hello, world")
.url(|line, _col| format!("file://{hostname}/path/to/file:line"));There was a problem hiding this comment.
Wouldn't that be fixed by
let hostname = std::env::var("HOSTNAME").unwrap();
let snippet = Snippet::source("hello, world")
.url(move |line, _col| format!("file://{hostname}/path/to/file:line"));This is an interesting trade off of lifetime complexity vs api flexibility.
fixes #366
over:
should be reviewable commit by commit.