Skip to content

Adds 'Link' View providing OSC 8 Hyperlink Support#4741

Closed
ccoulioufr wants to merge 31 commits intogui-cs:v2_developfrom
ccoulioufr:v2_develop
Closed

Adds 'Link' View providing OSC 8 Hyperlink Support#4741
ccoulioufr wants to merge 31 commits intogui-cs:v2_developfrom
ccoulioufr:v2_develop

Conversation

@ccoulioufr
Copy link
Copy Markdown
Collaborator

New Feature: Link View (OSC 8 Hyperlink Support)

Summary

This PR introduces a new feature to Terminal.Gui: a Link view.

The Link view allows associating a visible text with a URL using the OSC 8 escape sequence when supported by the host terminal.

Example:

var link = new Link()
{
    Text = "Documentation",
    Url = "https://example.com/docs"
};

@ccoulioufr ccoulioufr requested a review from tig as a code owner February 22, 2026 01:46
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 22, 2026

Codecov Report

❌ Patch coverage is 84.32836% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.60%. Comparing base (eb6f93e) to head (30f0348).

Files with missing lines Patch % Lines
Terminal.Gui/Views/Link.cs 83.33% 8 Missing and 8 partials ⚠️
Terminal.Gui/Drivers/Output/OutputBase.cs 78.26% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@              Coverage Diff               @@
##           v2_develop    #4741      +/-   ##
==============================================
+ Coverage       77.54%   77.60%   +0.05%     
==============================================
  Files             461      462       +1     
  Lines           46088    46222     +134     
  Branches         6835     6864      +29     
==============================================
+ Hits            35741    35869     +128     
+ Misses           8350     8347       -3     
- Partials         1997     2006       +9     
Files with missing lines Coverage Δ
Terminal.Gui/Drivers/DriverImpl.cs 71.14% <100.00%> (+0.39%) ⬆️
Terminal.Gui/Drivers/Output/OutputBufferImpl.cs 89.61% <100.00%> (+0.79%) ⬆️
Terminal.Gui/Drivers/Output/OutputBase.cs 91.51% <78.26%> (-1.52%) ⬇️
Terminal.Gui/Views/Link.cs 83.33% <83.33%> (ø)

... and 4 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update eb6f93e...30f0348. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@tznind
Copy link
Copy Markdown
Collaborator

tznind commented Feb 22, 2026

Really nice, I've often wanted to do this (have a hyperlink but shorter text representation). Didn't know it was possible!

Are there any older terminals where the output escape sequences are not supported or worse break output rendering?

Is it worth adding a configuration manager setting for disabling the output?

Copy link
Copy Markdown
Collaborator

@tig tig left a comment

Choose a reason for hiding this comment

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

See my ask. This is very cool. Windows Terminal automatically makes things hat look like URLs be clickable. But other terminals don't. I think this is a great add to the library.

Comment thread Terminal.Gui/Views/Link.cs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new Link view to Terminal.Gui that enables OSC 8 hyperlink support, allowing developers to create clickable links in console applications when supported by the host terminal.

Changes:

  • Adds a new Link view that displays text with an associated URL using OSC 8 escape sequences
  • Extends the rendering infrastructure (Cell, IOutputBuffer, IDriver, OutputBase) to track and render URL metadata
  • Provides comprehensive test coverage with 14 unit tests covering various scenarios
  • Includes a UICatalog scenario demonstrating Link functionality

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Terminal.Gui/Views/Link.cs New Link view implementation with URL property, hyperlink rendering, and hotkey support
Terminal.Gui/Drawing/Cell.cs Adds optional Url property to Cell record struct for OSC 8 support
Terminal.Gui/Drivers/IDriver.cs Adds CurrentUrl property to IDriver interface
Terminal.Gui/Drivers/DriverImpl.cs Implements CurrentUrl property by delegating to OutputBuffer
Terminal.Gui/Drivers/Output/IOutputBuffer.cs Adds CurrentUrl property to IOutputBuffer interface
Terminal.Gui/Drivers/Output/OutputBufferImpl.cs Implements CurrentUrl property and sets it on cells during drawing
Terminal.Gui/Drivers/Output/OutputBase.cs Handles OSC 8 escape sequence generation during ANSI rendering
Tests/UnitTestsParallelizable/Views/LinkTests.cs Comprehensive test suite with 14 tests covering Link functionality
Examples/UICatalog/Scenarios/Links.cs Interactive demonstration scenario for the Link view
docfx/apispec/namespace-views.md Updates documentation to include Link in the navigation controls category
Comments suppressed due to low confidence (2)

Terminal.Gui/Views/Link.cs:5

  • The class summary should be more descriptive and follow the pattern used by other views. Consider: "A view that displays clickable text with an associated URL. When supported by the terminal, the link is rendered using OSC 8 hyperlink sequences." This provides more context about the feature and how it works.
/// <summary>
///     Displays a clickable link with text and url.
/// </summary>

Examples/UICatalog/Scenarios/Links.cs:91

  • Trailing comma on the last property in the object initializer. This line appears to be incomplete or could be cleaned up. If no additional properties are intended, the trailing comma should be removed.
        Button copyButton = new ()
        {
            Title = "_Copy",
            X = Pos.Center (),
            Y = Pos.Bottom (link) + 2,
            

Comment thread Terminal.Gui/Views/Link.cs Outdated
Comment thread Terminal.Gui/Views/Link.cs Outdated
Comment thread Tests/UnitTestsParallelizable/Views/LinkTests.cs Outdated
@tig tig changed the title links with tests Adds 'Link' View providing OSC 8 Hyperlink Support Feb 22, 2026
@ccoulioufr ccoulioufr requested a review from tig February 22, 2026 11:30
ccoulioufr and others added 7 commits February 22, 2026 12:30
en doc translation

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
url doc fix

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@ccoulioufr
Copy link
Copy Markdown
Collaborator Author

Hi there,

All requested changes have now been implemented:

  • Refactored the Url property to follow the Cancellable Work Pattern (CWP).
  • Addressed review comments and adjusted the implementation accordingly.
  • Added additional unit tests to improve code coverage.

Coverage has been improved, although it can likely be increased further if needed.
If there are specific branches or scenarios you would like covered, I’m happy to extend the tests.

Regarding documentation:
I attempted to regenerate the dox documentation but was not able to successfully reproduce the documentation generation locally.
If guidance is provided on the expected process/command, I will gladly update the PR to include the generated documentation.

Please let me know if any further adjustments are required.

@tig
Copy link
Copy Markdown
Collaborator

tig commented Feb 22, 2026

Hi there,

All requested changes have now been implemented:

  • Refactored the Url property to follow the Cancellable Work Pattern (CWP).
  • Addressed review comments and adjusted the implementation accordingly.
  • Added additional unit tests to improve code coverage.

Super great work!

Coverage has been improved, although it can likely be increased further if needed. If there are specific branches or scenarios you would like covered, I’m happy to extend the tests.

Please see AddStr_WideGlyph_Second_Column_Attribute_Outputs_Correctly as an example of a test that verifies the actual output of the drivers is correct. (DriverAssert.AssertDriverOutputIs). Can you build some tests that prove the rendering is working properly in a similar fashion?

Regarding documentation: I attempted to regenerate the dox documentation but was not able to successfully reproduce the documentation generation locally. If guidance is provided on the expected process/command, I will gladly update the PR to include the generated documentation.

Did you try:

 .\docfx\scripts\Build.ps1

This will let you verify any new warnings.

The docs are regenerated by the GH workflows on merge to v2_develop.

@ccoulioufr
Copy link
Copy Markdown
Collaborator Author

Really nice, I've often wanted to do this (have a hyperlink but shorter text representation). Didn't know it was possible!

Are there any older terminals where the output escape sequences are not supported or worse break output rendering?

Is it worth adding a configuration manager setting for disabling the output?

According to my research, it will work in terminals supporting OSC 8:

  • Windows Terminal
  • iTerm2
  • GNOME Terminal
  • kitty
  • WezTerm
  • VS Code integrated terminal
  • PowerShell

So it's not a full support but not bad though.
Currently, the escape characters are already used when an URL is detected.
It may be usefull to replace the output text by the URL on unsupporting platforms.

@ccoulioufr
Copy link
Copy Markdown
Collaborator Author

Please see AddStr_WideGlyph_Second_Column_Attribute_Outputs_Correctly as an example of a test that verifies the actual output of the drivers is correct. (DriverAssert.AssertDriverOutputIs). Can you build some tests that prove the rendering is working properly in a similar fashion?

I'll have a look on it this week.

Regarding documentation: I attempted to regenerate the dox documentation but was not able to successfully reproduce the documentation generation locally. If guidance is provided on the expected process/command, I will gladly update the PR to include the generated documentation.

Did you try:

 .\docfx\scripts\Build.ps1

This will let you verify any new warnings.

Here's the output error when trying to build doc (sorry for th french output):

PS C:\wk\repos\Terminal.Gui> .\docfx\scripts\Build.ps1
Working directory: C:\wk\repos\Terminal.Gui\docfx
Generating source index...
Join-Path : Impossible de trouver un paramètre positionnel acceptant l'argument « INDEX.md ».
Au caractère C:\wk\repos\Terminal.Gui\docfx\scripts\Generate-SourceIndex.ps1:21 : 14

  • $indexFile = Join-Path $rootDir ".tg-docs" "INDEX.md"
    • CategoryInfo : InvalidArgument : (:) [Join-Path], ParameterBindingException
    • FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.JoinPathCommand

@tznind
Copy link
Copy Markdown
Collaborator

tznind commented Feb 22, 2026

So it's not a full support but not bad though.

Not supported is ok as long as it doesn't actively corrupt the output on any terminals. i.e. if a terminal ignores the codes because it doesn't understand them its fine but if its outputting them as literals it would be bad.

@ccoulioufr
Copy link
Copy Markdown
Collaborator Author

ccoulioufr commented Feb 24, 2026

Coverage has been improved, although it can likely be increased further if needed. If there are specific branches or scenarios you would like covered, I’m happy to extend the tests.

Please see AddStr_WideGlyph_Second_Column_Attribute_Outputs_Correctly as an example of a test that verifies the actual output of the drivers is correct. (DriverAssert.AssertDriverOutputIs). Can you build some tests that prove the rendering is working properly in a similar fashion?

@tig A test rendering a link on the driver was implemented using DriverAssert.AssertDriverOutputIs

  • Link_Osc8_Emits_StartTextEnd_And_Outputs_Correctly
    NB: The output contains color informations, so maybe it should test AssertDriverOutputContains...

ccoulioufr and others added 3 commits February 24, 2026 11:25
Update Url property validation to retain previous value on invalid input.
Copy link
Copy Markdown
Collaborator

@tig tig left a comment

Choose a reason for hiding this comment

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

I looked at this again and I have questions. Are we sure this is the most efficient impl of this idea. This basically adds 8 bytes to every Cell and then each IDriver and IOutput impl
hold a string ref too.

All for a feature that will get used very rarely.

I wonder if there's a way to do this in a much less expensive way?

Comment thread Terminal.Gui/Drawing/Cell.cs Outdated
@ccoulioufr
Copy link
Copy Markdown
Collaborator Author

ccoulioufr commented Mar 1, 2026

@tig Thanks for your feedback on the memory overhead issue! I've implemented some optimizations.

1. Removed Cell.Url property (Memory optimization)

  • Before: 8 bytes overhead per cell (~30-160 KB depending on terminal size)
  • After: Zero overhead for cells without URLs

2. Added lightweight Dictionary<Point, string> in OutputBufferImpl

  • Only stores URLs for cells that actually have them
  • Memory impact: ~760 bytes for 5 links (30 cells) vs 30 KB before
  • 99.97% memory savings compared to Cell.Url approach

3. Added GetCellUrl() to IOutputBuffer interface

  • Prevents from casting the buffer as OutputBufferImpl in OutputBase.Write()
  • Better adherence to SOLID principles (Dependency Inversion)
  • Easier to test and extend

4. Changed DEFAULT_URL from "about:blank" to ""

  • Issue: about:blank is a browser-only scheme, causes "PC doesn't know how to open this link" error on Windows
  • Solution: Empty string = no link generated = no system error
  • Kept the condition to skip rendering OSC 8 for default URLs

5. Added SetNeedsDraw() in SetUrl()

  • Changing Link.Url now automatically triggers a redraw
  • Consistent with other View properties (Text, Title, etc.)

6. Updated all tests

  • Fixed Link_Multiple_Links_Each_Get_Their_Own_Url (now renders both links in one draw)
  • Fixed Link_Url_Changes_Update_Hyperlink (automatic redraw on URL change)
  • Fixed Link_With_Focus_Draws_With_Focus_Colors (added dummyView to take initial focus)

Result

  • Zero memory overhead for 99.99% of cells
  • Clean architecture (no casts, interface-based)
  • No system errors with default URLs
  • Better UX (automatic redraw on URL change)
  • All tests pass

I'm not yet at ease with flags to follow your idea.
Let me know if you'd like any adjustments!

@ccoulioufr ccoulioufr requested a review from tig March 5, 2026 11:21
@tig
Copy link
Copy Markdown
Collaborator

tig commented Mar 5, 2026

I'll take a closer look again soon.

tig and others added 4 commits March 6, 2026 08:44
- Added static Link.OpenUrl for platform-specific URL opening; removed OpenUrl from UICatalogRunnable.
- Link now handles mouse clicks and keyboard activation, opening URLs directly.
- Improved drawing: shows Url if Text is empty, disables link if Url is invalid, always clears Driver.CurrentUrl.
- Copy() now copies Text, not Url.
- SetUrl validates URLs; invalid URLs revert to DEFAULT_URL.
- IDesignable.EnableForDesign sets default Title and Text.
- Refactored Links scenario UI for clarity and usability; status bar now shows Link's Text.
- Updated all tests for new Link API and behaviors; improved test clarity.
- Minor code style improvements and use of System.Diagnostics for process launching.
- IDesignable.EnableForDesign now sets Title and Url, not Text.
- Copy() now copies Url to clipboard instead of Text.
- Text, Title, and Url are now independent; setting one does not affect the others.
- TextFormatter displays Url if Text is empty; otherwise uses Text.
- DimAuto sizing uses Text width if set, otherwise Url, including wide chars.
- Url property now accepts any string (no URI validation).
- Setting the same Url does not fire change events.
- UrlChanging event can cancel Url changes.
- Rendering tests ensure invalid URLs do not produce OSC 8 hyperlinks and are styled as disabled.
- Tests updated and expanded to cover all new behaviors and edge cases.
- Minor test code cleanups and clarifications.
Add comprehensive XML docs for the Link class covering the three
independent text properties (Text, Title, Url), OSC 8 hyperlink
rendering, draw-time URL validation, CWP event pattern, HotKey-to-next-peer
behavior, and Dim.Auto sizing. Enhance docs for all public members.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tig
Copy link
Copy Markdown
Collaborator

tig commented Mar 6, 2026

@ccoulioufr I did not mean to push directly to your branch! I meant to submit a PR to your PR but goofed. Hopefully that's ok, and you like the changes I made. Here's what I did:

Refactored Link.cs:

  • Decoupled Text, Title, and Url — These are now three independent properties. Text (from View) is the display text, Title (from View) controls the HotKey, and Url is the hyperlink target. Previously the tests assumed Text and Title were synced, and that Text would return Url when empty — neither is the case.
  • UpdateTextFormatterText fallback — When Text is empty, the TextFormatter displays Url instead (so Dim.Auto sizes correctly to the URL). This is purely a display/layout concern; the Text property getter still returns the actual Text value.
  • URL validation moved to draw timeUrl accepts any string in the setter (with full CWP event support via UrlChanging/UrlChanged). Validation happens in OnDrawingText: invalid URLs render with the Disabled visual role and don't emit OSC 8 sequences.
  • Fixed IDesignable.EnableForDesign() — Now sets Url.
  • Comprehensive API documentation — Added detailed XML docs for the class and all public members covering the three independent properties, OSC 8 rendering, draw-time validation, CWP event pattern, HotKey-to-next-peer behavior, and Dim.Auto sizing.

Rewrote and expanded LinkTests.cs (16 → 26 tests):

  • Added tests for: Dim.Auto width with both Text and Url (including wide Unicode/CJK characters in IRIs), UrlChanging cancellation, same-value-no-event, Text/Title/Url independence, invalid URL rendering with disabled style

@tig
Copy link
Copy Markdown
Collaborator

tig commented Mar 9, 2026

Closing this in lieu of #4815. @ccoulioufr you can now revert your v2_develop branch back to head.

@tig tig closed this Mar 9, 2026
@ccoulioufr
Copy link
Copy Markdown
Collaborator Author

@tig was there a problem with the branch name? should I have named it v2_develop/feat/link?

@tig
Copy link
Copy Markdown
Collaborator

tig commented Mar 9, 2026

@tig was there a problem with the branch name? should I have named it v2_develop/feat/link?

Yea, you should have created a branch off of v2_develop (name doesn't really matter). No biggie though! Thank you for your awesome contribution.

@ccoulioufr
Copy link
Copy Markdown
Collaborator Author

@tig One small point of attention regarding the URI validation.

I noticed that Uri.IsWellFormedUriString is currently evaluated during rendering. While this works functionally, it means the URI will be revalidated every time the view is redrawn.

In most cases this probably has negligible impact, but since rendering can occur frequently in a TUI (e.g., layout invalidation, refresh cycles, animations, etc.), repeatedly parsing the same URI could become unnecessary work if many Link views are present.

An alternative approach could be to validate the URI when the Url property changes and cache the result, keeping the render path as lightweight as possible.

Of course this is not a blocker, just a small performance consideration depending on how often the control might be redrawn.

@tig
Copy link
Copy Markdown
Collaborator

tig commented Mar 12, 2026

Great point. Would you please file this as an issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants