-
Notifications
You must be signed in to change notification settings - Fork 43
Link support (issue #81) #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Extract links from PDF pages - Add interactive links menu with Tab key navigation - Support link actions: Enter (open with system app), c (copy URL) - Display footer status showing link count when present - Implement URL opening and clipboard functionality - Add visual link selection with ">" indicator and truncation for long URLs
- Add copypasta 0.10.2 dependency for cross-platform clipboard support
- Add open 5.3.2 dependency for cross-platform URL opening
removed leftover code trying to handle internal links. If that is a desirable feature, it can be added separately.
itsjunetime
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, I really do like this. Thank you so much for working on it, it looks really good so far and I really appreciate your time.
There are a few questions and nits I would like resolved before we merge this in. Most of those are included in my linked comments, but for those that aren't:
- You mentioned in your comment above that
qshould quit the link menu, but it seems that onlyescquits that menu, whileqstill quits the whole app. Is that intended? - In my quick test, it didn't show the links for the page that was currently displayed - it showed them for the next page. Afaict, your code to load the links looks correct, so I'd guess this is a weird mishandling of the mupdf C api? I don't really know for sure, so I'll try to look into that soon.
Also, I think it could be nice to maybe make this instead highlight the links on the page (instead of just listing them) and show the whole link at the bottom of the page when you have it highlighted, but that's definitely more complicated, so I think that should be a project for the future. And it'll definitely be much easier to do once we get this infrastructure merged in.
|
|
||
| fn extract_page_links(page: &Page) -> Result<Vec<Link>, mupdf::error::Error> { | ||
| let links = page.links()?; | ||
| let mut unique_links = Vec::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imo if a page has multiple of the same link, it's still useful to show them.
For example, imagine if a page has 3 links, where the first and third link to the same site, but the document has the hyperlinks hidden behind normal text so people can't see exactly where each link links to, they can just see that it's a link. In this case, it would be better to display all three so that people can easily correlate what link (on the link menu) corresponds to what text (on the displayed page).
If only two links are shown, that could be confusing and they may not be sure which link they actually want to click on, since they can't tell what link corresponds to what bit of text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I made my initial "link menu design", the repetition of links didn't seem logical to me. However, if we're going from menu -> highlighting of links in the document, this makes total sense.
|
|
||
| for link in links { | ||
| // Only include HTTP/HTTPS URLs, skip internal links and empty URIs | ||
| if link.uri.starts_with("http") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future, we probably want to extend this to support jumping to a different part of the doc, but for now I think this is good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. When we have a good implementation of the basic http-functionality, I'll try to make a pass over internal links
|
|
||
| if self.showing_links_menu { | ||
| self.render_links_menu(frame); | ||
| return KittyDisplay::ClearImages; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly imo it would look better to not clear the images, but rather just show the link menu on top of the doc, clearing the part of the image that's underneath it. I'm not too opinionated, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this will go away if we remove the menu
| let mut to_display = Vec::new(); | ||
|
|
||
| for (idx, (width, img)) in page_widths.into_iter().enumerate() { | ||
| let page_rect = Rect { width, ..img_area }; | ||
| let page_num = idx + self.page; | ||
|
|
||
| // Render the page | ||
| let maybe_img = Self::render_single_page(frame, img, page_rect); | ||
|
|
||
| img_area.x += width; | ||
| if let Some((img, pos)) = maybe_img { | ||
| to_display.push(KittyReadyToDisplay { | ||
| img, | ||
| page_num: idx + self.page, | ||
| page_num, | ||
| pos, | ||
| display_loc: DisplayLocation::default() | ||
| }) | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason for this change? Or is it just stylistic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me check this more thoroughyl. I think there is a reason, but I have forgotten it. Should've answered this immediately when you wrote the question:P
| spans.push(Span::styled( | ||
| format!("Tab: {} links", rendered[page_num].links.len()), | ||
| Style::new().fg(Color::Yellow) | ||
| )); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love how this bottom section looks, great job there.
| if let Some(rendered_info) = self.rendered.get(self.page) { | ||
| if let Some(link) = rendered_info.links.get(self.selected_link_index) { | ||
| if link.uri.starts_with("http") { | ||
| let _ = open::that(&link.uri); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if let Some(rendered_info) = self.rendered.get(self.page) { | |
| if let Some(link) = rendered_info.links.get(self.selected_link_index) { | |
| if link.uri.starts_with("http") { | |
| let _ = open::that(&link.uri); | |
| } | |
| } | |
| } | |
| if let Some(rendered_info) = self.rendered.get(self.page) && | |
| let Some(link) = rendered_info.links.get(self.selected_link_index) && | |
| link.uri.starts_with("http") | |
| { | |
| let _ = open::that(&link.uri); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think the error should be bubbled up to the user here if possible, I just can't remember how to do that off the top of my head
| KeyCode::Tab => { | ||
| if self.showing_links_menu { | ||
| // Cycle through links when menu is open | ||
| if let Some(rendered_info) = self.rendered.get(self.page) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't add another suggestion since they'll probably need to be fixed by rustfmt anyways, but I think collapsing the ifs here with let-chains would be nice. I know they were stabilized after the current msrv, but I'm more than happy to bump that so we can use let chains.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And also, since this if let and if !rendered_info.links.is_empty is present in both branches, maybe we should pull that outside of the if self.showing_links_menu
| frame.render_widget(Clear, frame_area); | ||
|
|
||
| let block = Block::new() | ||
| .title("Links in Current Page (Tab: cycle, Enter: open, c: copy, Esc: close)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm kinda conflicted on having the help text here in the title. I think it would be better to either:
- Move it to the footer (so that it doesn't make the block so wide and doesn't create as much visual noise for someone who already knows the actions), OR
- Make another tab on the help view that just shows these keybindings
What do you think?
| format!("> {}. ", i + 1) // Selected item gets ">" prefix | ||
| } else { | ||
| format!(" {}. ", i + 1) // Normal items get spaces |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use the fill/alignment formatting specifiers to left-pad the number to ensure they're all aligned if there's 10+ links?
| .map(str::len) | ||
| .max() | ||
| .unwrap_or(30) | ||
| .min(80) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm overengineering this, but I think it would be nice to have this be dynamically computed - e.g.
const MAX_ALLOWED_LINK_LEN: usize = 70;
// ... later on
// 5 for `...` and `> .` at beginning, `log(10)` for longest number display, 4 for padding
.min(MAX_ALLOWED_LINK_LEN + 3 + 2 + links_text.len().log(10) + 4)I don't know if log(10) is actually exactly the calculation we want, but I think you get the idea.
|
Let me answer your two main comments before going into the code :)
I actually don't remember, but would guess it's an "advanced typo". It would be silly to flip the key binding logic for this specific feature. Let's rewrite the menu text.
When I did my tests I didn't notice any such problems. I had the correct page<->links mapping the whole time, even when the reader was wide enough to display 3-4 pages in parallell. What operating system do you use? I did my tests on macOS. I guess the debugging has to start on this level :)
That seems like a good idea! I fumbled around testing different approaches and chose the current one for some reason. Let's try your approach! I will address the rest of your comments after making a pass at these high-level issues. Maybe some of them will automagically solve themselves |
Sounds good
Sorry, this one error is on me - I downloaded a test doc that had a confusing mix of intra-doc and internet links and thought it wasn't working correctly. This code seems to do what it was intended to do, so you're all good there.
Sounds good, then - if you'd like to refactor this to do that instead, I'm super chill with it. If you'd rather leave that off for later, though, I totally understand. I think the menu you've added in this PR is really good as it is. |
Implements basic link support for tdf
The link support is intended to be very easy to use when using the keyboard. Just yourself to the right link and open or copy it.
Things in need of consideration: