Skip to content

Add support for autodetecting URLs and making hyperlinks - #7691

Merged
53 commits merged into
mainfrom
dev/pabhoj/auto_link
Oct 28, 2020
Merged

53 commits merged into
mainfrom
dev/pabhoj/auto_link

Conversation

@PankajBhojwani

@PankajBhojwani PankajBhojwani commented Sep 21, 2020 •

Copy link
Copy Markdown
Contributor

This pull request is the initial implementation of hyperlink auto
detection

Overall design:

  • Upon startup, TerminalCore gives the TextBuffer some patterns it
    should know about
  • Whenever something in the viewport changes (i.e. text
    output/scrolling), TerminalControl tells TerminalCore (through a
    throttled function for performance) to retrieve the visible pattern
    locations from the TextBuffer
  • When the renderer encounters a region that is associated with a
    pattern, it paints that region differently

References #5001
Closes #574

@ghost ghost added Area-Extensibility A feature that would ideally be fulfilled by us having an extension model. Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal. labels Sep 21, 2020
@codeofdusk

Copy link
Copy Markdown
Contributor

It doesn't look like this PR adds any custom logic to the UIA text range, so I doubt UIA clients will be able to detect clickable links with this implementation. Have you tested for this (i.e. with Narrator/NVDA)?

As an aside, this PR makes a good case for allowing browse mode in terminals (nvaccess/nvda#11172).
Cc Carlos Zamora (@carlos-zamora), Dickson Tan (@Neurrone).

@carlos-zamora

Copy link
Copy Markdown
Contributor

Regarding accessibility. I haven't had a chance to test this out yet. Especially since it's in draft. But here's some resources and an idea of the work we'll probably have to do (probably in a separate PR):

In UiaTextRange, we'll need to update...

  • GetChildren(): hyperlink ranges count as a child
  • GetEnclosingElement(): if we're on a hyperlink, return the parent text range. Otherwise, normal behavior.
  • FindAttribute() and GetAttribute(): hyperlinks need to have the hyperlink attribute (exposed via GetAttribute()). And FindAttribute() needs to be implemented to find if one exists in the text range.

Another thought. Hyperlink support is just for Windows Terminal, not ConHost, right? That'll complicate things a bit with our accessibility model because UiaTextRange is currently shared. I see the implementation for autodetection is in TextBuffer (which makes this easier), but if ConHost won't expose it, we need to figure out a way to not expose hyperlinks that don't exist in ConHost. Of course, if this functionality is shared, just completely ignore this paragraph haha.

@codeofdusk

Copy link
Copy Markdown
Contributor

Hyperlink support is just for Windows Terminal, not ConHost, right?

If this is the plan, might this be reconsidered? While Windows Terminal is very feature-filled, in my experience conhost has much wider usage.

@DHowett

Copy link
Copy Markdown
Member

Bill Dengler (@codeofdusk), Carlos Zamora (@carlos-zamora) Hyperlink support is Terminal-FIRST, not Terminal-ONLY. Sorry for the confusion.

Since we can move so much faster in Terminal it lets us figure out the interaction model a lot sooner than if we throw something together and hope that people jump on the Windows Insider builds and test our thing out.

Many, many changes that make it into Terminal will eventually flow back. This is especially true for anything that deals with accessibility, standards compliance and text rendering.

We'd like to figure out the interaction model for formatted text and browseable regions in Terminal before we bring anything like this back into conhost, absolutely.

Comment thread src/buffer/out/textBuffer.cpp Outdated
Comment thread src/buffer/out/textBuffer.hpp Outdated
Comment thread src/cascadia/TerminalCore/Terminal.cpp Outdated
Comment thread src/buffer/out/textBuffer.cpp Outdated
Comment thread src/buffer/out/textBuffer.cpp Outdated
Comment thread src/buffer/out/textBuffer.cpp Outdated
Comment on lines +2429 to +2434
const auto startRow = gsl::narrow<SHORT>(start / rowSize);
const auto startCol = gsl::narrow<SHORT>(start % rowSize);
const auto endRow = gsl::narrow<SHORT>(end / rowSize);
const auto endCol = gsl::narrow<SHORT>(end % rowSize);
const COORD startCoord{ startCol, startRow };
const COORD endCoord{ endCol, endRow };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pretty sure this level of math is also done in Find/Selection and it might be using TIL or one of the utilities/types classes to help with it. Consider deduplicating with those.

@PankajBhojwani PankajBhojwani Sep 28, 2020 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm I can't seem to find the similar math - those parts of the code seem to begin with coordinates already (buffer or viewport) whereas here, since we only search after concatenating all the text together, we need to calculate the coordinates from the concatenated string

@miniksa

Michael Niksa (miniksa) commented Sep 25, 2020 •

Copy link
Copy Markdown
Contributor

Now I don't want to go too crazypants here, but I'm thinking about future extensions to some degree.

I'm curious if you considered a design where the text buffer figures out the patterns on its own instead of being pumped to do that action by a thread from way outside itself, in this case from up in the Terminal* and friends.

I'm not saying this one is bad or wrong, but an alternate vision I have is that you would register an object with the TextBuffer that contained a pattern, region of interest, and a callback method/event inside of it. The TextBuffer would just... of its own accord as things are written in on the other methods (and maybe with its own internal, private thread or threadpool worker) grind through the region with the pattern and make callbacks as necessary.

Almost like you have an ITextScanner interface with an ITextMatchFound callback interface... which might lend itself toward easy adaptation into a COM/WinRT-based plug-in model for arbitrary buffer scanners in the future.

@miniksa

Copy link
Copy Markdown
Contributor

Now I don't want to go too crazypants here, but I'm thinking about future extensions to some degree.

I'm curious if you considered a design where the text buffer figures out the patterns on its own instead of being pumped to do that action by a thread from way outside itself, in this case from up in the Terminal* and friends.

I'm not saying this one is bad or wrong, but an alternate vision I have is that you would register an object with the TextBuffer that contained a pattern, region of interest, and a callback method/event inside of it. The TextBuffer would just... of its own accord as things are written in on the other methods (and maybe with its own internal, private thread or threadpool worker) grind through the region with the pattern and make callbacks as necessary.

Almost like you have an ITextScanner interface with an ITextMatchFound callback interface... which might lend itself toward easy adaptation into a COM/WinRT-based plug-in model for arbitrary buffer scanners in the future.

As Dustin and I are musing about this... your ITextScanner could just also contain ITextEffect on it as an optional in lieu of a callback that is just handed straight into the renderer and now you've also managed to design rendering effect extensions at the same time...

@miniksa

Copy link
Copy Markdown
Contributor

Now I don't want to go too crazypants here, but I'm thinking about future extensions to some degree.
I'm curious if you considered a design where the text buffer figures out the patterns on its own instead of being pumped to do that action by a thread from way outside itself, in this case from up in the Terminal* and friends.
I'm not saying this one is bad or wrong, but an alternate vision I have is that you would register an object with the TextBuffer that contained a pattern, region of interest, and a callback method/event inside of it. The TextBuffer would just... of its own accord as things are written in on the other methods (and maybe with its own internal, private thread or threadpool worker) grind through the region with the pattern and make callbacks as necessary.
Almost like you have an ITextScanner interface with an ITextMatchFound callback interface... which might lend itself toward easy adaptation into a COM/WinRT-based plug-in model for arbitrary buffer scanners in the future.

As Dustin and I are musing about this... your ITextScanner could just also contain ITextEffect on it as an optional in lieu of a callback that is just handed straight into the renderer and now you've also managed to design rendering effect extensions at the same time...

OK we all discussed this with Teams. I'm willing to concede this point because Hyperlinks are so interesting here that they push the boundaries of this sort of unified model. And where the threads live is a bit contentious. And Dustin keeps coming up with more ideas why, while this model sort of works, it has sharp edges. So I'm fine with proceeding per what this draft has as a design.

Comment thread oss/interval_tree/MAINTAINER_README.md Outdated
Comment thread oss/interval_tree/LICENSE Outdated
@DHowett

Copy link
Copy Markdown
Member

I edited your PR body down to be a nice commit body :)

@DHowett Dustin L. Howett (DHowett) changed the title Hyperlink auto-detection Add support for autodetecting URLs and making them links Oct 28, 2020
@DHowett Dustin L. Howett (DHowett) changed the title Add support for autodetecting URLs and making them links Add support for autodetecting URLs and making hyperlinks Oct 28, 2020
@PankajBhojwani PankajBhojwani added the AutoMerge Marked for automatic merge by the bot when requirements are met label Oct 28, 2020
@ghost

Copy link
Copy Markdown

Hello PankajBhojwani!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost
ghost merged commit 2bf5d18 into main Oct 28, 2020
@ghost
ghost deleted the dev/pabhoj/auto_link branch October 28, 2020 20:24
@zadjii-msft

Copy link
Copy Markdown
Member

oh-my-god-its-happening

@comzyh

Copy link
Copy Markdown
Contributor

I guess there is something wrong with handling wide characters.
image

ghost pushed a commit that referenced this pull request Nov 3, 2020
… URL detecting (#8124)

Fix #8121
![image](https://user-images.githubusercontent.com/1068203/97811235-2081ca80-1cb4-11eb-82bd-1ddaf15c757c.png)


<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request

When calculating the position of the matched pattern, consider the width of the characters.

However, if there are some wide glyphs in the detected hyperlink(not possible for now, for the existing regex will not match wide-character?). The repeated character in the tooltip is not fixed by this PR.

<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> 
## References

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [ ] Closes #8121
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

When calculating the coordinate of the match in #7691, it simply uses the `prefix.size()` as the total prefix width on the screen.

This PR fixes that behavior.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Manually Verified
@ghost

Copy link
Copy Markdown

🎉Windows Terminal Preview v1.5.3142.0 has been released which incorporates this pull request.:tada:

Handy links:

@ghost

Copy link
Copy Markdown

Scott's wish finally became true: https://youtu.be/veqs2WVou9M?t=797

@xeho91

Mateusz Kadlubowski (xeho91) commented Nov 15, 2020 •

Copy link
Copy Markdown

I love this feature!

I have a question, would it be possible to make this hovering on the link customizeable? I know it gives an underline effects, but in the scenario where I have all of the links already "underlined", I can't see if I am hovering on a link or not.

The example of custom customization would be to give a background color for the text with link or something similar.

@ghost

Copy link
Copy Markdown

I guess that would be another issue

@DHowett

Copy link
Copy Markdown
Member

Mateusz Kadlubowski (@xeho91) today it underlines on hover. Is that not working for you?

@xeho91

Copy link
Copy Markdown

Mateusz Kadlubowski (@xeho91) today it underlines on hover. Is that not working for you?

It does. However, in the example when I am using bat to preview my *.md files. It has a syntax which already recognizes and underlines the links in the output. There's a quick preview below.
image

When I am hovering with the cursor, there's no "visual feedback", except this small dialogue box "Ctrl + Click to follow link.".

What I am asking for to give us ability to define the style effect in settings for hovering on links. Like make the background white, and black text. If that's possible, no pressure, I still love this feature. ❤️

@zadjii-msft

Copy link
Copy Markdown
Member

Mateusz Kadlubowski (@xeho91) I've tossed this request into #5001, since this isn't actually the first time this has come up 😉 Thanks!

@joshuabiddle

Copy link
Copy Markdown

I'd like to use this ability to open an html file stored on a remote computer using the UNC path. I haven't been able to get it to open. Any ideas?

Example: \COMPUTER\UNC\Path\To\File.html

Steffen Heil | secforge (steffen-heil-secforge) added a commit to secforge/terminal that referenced this pull request Jul 30, 2026
is_valid() seeded its bounds accumulators with std::numeric_limits<Scalar>
::max()/min(). We instantiate the tree with Scalar = til::point, which has
no std::numeric_limits specialization, so the primary template returned a
default-constructed til::point{0,0} for *both* sentinels. The right-subtree
constraint check then evaluated {0,0} <= center, which holds for essentially
any real tree, so is_valid() returned false and assert(is_valid().first)
fired.

The tree only splits into subtrees once it holds 64 intervals, its default
minimum bucket size, so the threshold was exactly 64: Terminal::_getPatterns
builds the pattern tree from autodetected URLs over roughly three viewport-
heights of buffer, making 64 matches easy to reach. Any Debug build showing
a screenful of links aborted. Release builds were unaffected, and the tree
itself was structurally sound -- only the checker was wrong.

Track emptiness explicitly so no sentinel value is needed. Also accumulate
the maximum stop with std::max rather than std::min; upstream's std::min
pinned that accumulator at numeric_limits<Scalar>::min() forever, silently
reducing both center checks to no-ops, so a numeric_limits<til::point>
specialization alone would have left the check dead.

This is the second local deviation from ekg/intervaltree, alongside the
struct-key support carried since microsoft#7691 (upstream PR ekg/intervaltree#31,
still unmerged). Both are now recorded in MAINTAINER_README.md, together
with a warning that re-importing the file wholesale breaks the build.

Closes microsoft#20486

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pr3nsYBYoJ66jgRidRHyJF
Steffen Heil | secforge (steffen-heil-secforge) added a commit to secforge/terminal that referenced this pull request Aug 17, 2026
is_valid() seeded its bounds accumulators with std::numeric_limits<Scalar>
::max()/min(). We instantiate the tree with Scalar = til::point, which has
no std::numeric_limits specialization, so the primary template returned a
default-constructed til::point{0,0} for *both* sentinels. The right-subtree
constraint check then evaluated {0,0} <= center, which holds for essentially
any real tree, so is_valid() returned false and assert(is_valid().first)
fired.

The tree only splits into subtrees once it holds 64 intervals, its default
minimum bucket size, so the threshold was exactly 64: Terminal::_getPatterns
builds the pattern tree from autodetected URLs over roughly three viewport-
heights of buffer, making 64 matches easy to reach. Any Debug build showing
a screenful of links aborted. Release builds were unaffected, and the tree
itself was structurally sound -- only the checker was wrong.

Track emptiness explicitly so no sentinel value is needed. Also accumulate
the maximum stop with std::max rather than std::min; upstream's std::min
pinned that accumulator at numeric_limits<Scalar>::min() forever, silently
reducing both center checks to no-ops, so a numeric_limits<til::point>
specialization alone would have left the check dead.

This is the second local deviation from ekg/intervaltree, alongside the
struct-key support carried since microsoft#7691 (upstream PR ekg/intervaltree#31,
still unmerged). Both are now recorded in MAINTAINER_README.md, together
with a warning that re-importing the file wholesale breaks the build.

Closes microsoft#20486

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pr3nsYBYoJ66jgRidRHyJF
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Extensibility A feature that would ideally be fulfilled by us having an extension model. AutoMerge Marked for automatic merge by the bot when requirements are met Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design Clickable Links & Link preview feature/extension

9 participants