Hyperlinks

Æsh Readline supports clickable hyperlinks in terminal output using the OSC 8 escape sequence. When a supported terminal renders OSC 8 sequences, text is displayed as a clickable link — similar to HTML anchor tags. On terminals that don’t recognize OSC 8, the sequences are silently ignored and only the visible text is shown.

How It Works

OSC 8 uses two escape sequences to bracket the clickable text:

SequencePurpose
ESC ] 8 ; params ; URL STStart hyperlink (associate URL with following text)
ESC ] 8 ; ; STEnd hyperlink (stop linking)

Everything written between the start and end sequences is rendered as a clickable link. The params field is optional and can contain an id=VALUE parameter to group multiple link segments (e.g., a link that wraps across lines).

The String Terminator (ST) can be either ESC \ or BEL (\u0007). Æsh uses ESC \ by default.

Quick Start

import org.aesh.terminal.tty.TerminalConnection;
import org.aesh.terminal.utils.ANSI;

TerminalConnection conn = new TerminalConnection();

// Check if terminal supports hyperlinks
if (conn.supportsHyperlinks()) {
    // Write a clickable link
    conn.writeHyperlink("https://aeshell.github.io", "Æsh Documentation");
    conn.write("\n");
}

Terminal Support

TerminalSupported
iTerm2Yes
KittyYes
GhosttyYes
WezTermYes
footYes
ContourYes
RioYes
WarpYes
WaveYes
HyperYes
TabbyYes
ExtratermYes
GNOME TerminalYes
KonsoleYes
MinttyYes
xtermYes
JetBrainsYes
VS CodeYes
AlacrittyYes
Windows TerminalYes
Apple TerminalNo
rxvtNo
ConEmuNo
tmuxNo
GNU ScreenNo
Linux ConsoleNo

On unsupported terminals the OSC 8 sequences are silently ignored, so it is always safe to send them.

Connection API

The Connection interface provides methods for writing hyperlinks:

Checking Support

// Heuristic check based on terminal type detection
boolean supported = connection.supportsHyperlinks();

This uses environment-based terminal detection via TerminalEnvironment and the Device.TerminalType enum. No terminal query is sent.

Writing Hyperlinks

// Simple hyperlink
connection.writeHyperlink("https://example.com", "Click here");

// Hyperlink with grouping ID (for links that span multiple lines)
connection.writeHyperlink("https://example.com", "Click here", "link1");

Both methods return the Connection for chaining:

connection
    .writeHyperlink("https://example.com", "Example")
    .write(" | ")
    .writeHyperlink("https://aeshell.github.io", "Æsh Docs")
    .write("\n");

ANSI Utility Methods

The ANSI class provides static methods for building hyperlink escape sequences directly:

Wrapping Text

import org.aesh.terminal.utils.ANSI;

// Full hyperlink: opening sequence + text + closing sequence
String link = ANSI.hyperlink("https://example.com", "click here");
connection.write(link);

// With grouping ID
String link = ANSI.hyperlink("https://example.com", "click here", "myid");

Building Sequences Manually

For more control, build the opening and closing sequences separately:

// Opening sequence
String start = ANSI.buildHyperlinkStart("https://example.com", null);
// Result: ESC ] 8 ; ; https://example.com ST

// Opening sequence with ID
String start = ANSI.buildHyperlinkStart("https://example.com", "link1");
// Result: ESC ] 8 ; id=link1 ; https://example.com ST

// Closing sequence
String end = ANSI.buildHyperlinkEnd();
// Result: ESC ] 8 ; ; ST

// Combine manually
connection.write(start + "visible text" + end);

Grouping with IDs

The id parameter allows multiple disjoint text segments to refer to the same hyperlink. This is useful when a link wraps across lines — hovering over any segment highlights all segments with the same ID:

// Two segments of the same link on different lines
String id = "doc-link";
connection.write(ANSI.buildHyperlinkStart("https://example.com/long-page", id));
connection.write("This link continues");
connection.write(ANSI.buildHyperlinkEnd());
connection.write("\n");
connection.write(ANSI.buildHyperlinkStart("https://example.com/long-page", id));
connection.write("on the next line");
connection.write(ANSI.buildHyperlinkEnd());

Security

The implementation sanitizes URLs and IDs to prevent OSC injection. Control characters that could terminate the escape sequence prematurely — specifically ESC (\u001B) and BEL (\u0007) — are stripped from both URL and ID values. Passing a null URL throws an IllegalArgumentException.

Stripping Hyperlinks

The Parser.stripAwayAnsiCodes() method removes OSC 8 sequences while preserving the visible text. This is useful for measuring display width or logging plain text:

import org.aesh.terminal.utils.Parser;

String linked = ANSI.hyperlink("https://example.com", "click here");
String plain = Parser.stripAwayAnsiCodes(linked);
// Result: "click here"

Both ESC \ and BEL terminators are handled correctly.

Complete Example

import org.aesh.terminal.tty.TerminalConnection;
import org.aesh.terminal.utils.ANSI;

public class HyperlinkDemo {
    public static void main(String[] args) throws Exception {
        TerminalConnection conn = new TerminalConnection();

        if (conn.supportsHyperlinks()) {
            conn.write("Terminal supports hyperlinks!\n\n");

            // Simple link
            conn.write("Visit: ");
            conn.writeHyperlink("https://aeshell.github.io", "Æsh Documentation");
            conn.write("\n");

            // Link with styled text (combine with ANSI formatting)
            conn.write("Source: ");
            conn.write(ANSI.BOLD);
            conn.writeHyperlink("https://github.com/aeshell", "GitHub");
            conn.write(ANSI.RESET);
            conn.write("\n");

            // Multiple links on one line
            conn.write("\nLinks: ");
            conn.writeHyperlink("https://example.com/one", "One")
                .write(" | ")
                .writeHyperlink("https://example.com/two", "Two")
                .write(" | ")
                .writeHyperlink("https://example.com/three", "Three")
                .write("\n");
        } else {
            conn.write("This terminal does not support hyperlinks.\n");
            conn.write("Try running in: Kitty, iTerm2, WezTerm, or VS Code\n");
        }

        conn.close();
    }
}

Specification

The OSC 8 hyperlink protocol is documented by the Contour terminal:

Contour VT Extension: Clickable Links

See Also