Skip to content

Commit 5c1f43d

Browse files
stalepclaude
andcommitted
Added documentation to describe hyperlink support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f25df32 commit 5c1f43d

2 files changed

Lines changed: 231 additions & 0 deletions

File tree

content/docs/readline/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Think of it this way: Æsh is built **on top of** Æsh Readline. Æsh provides t
4848
- **[Terminal Colors](terminal-colors)** - RGB/HSL colors, theme-aware styling, and color depth adaptation
4949
- **[Device Attributes](device-attributes)** - DA1/DA2 terminal capability queries
5050
- **[Terminal Images](terminal-images)** - Sixel, Kitty, and iTerm2 inline image support
51+
- **[Hyperlinks](hyperlinks)** - Clickable OSC 8 hyperlinks in terminal output
5152

5253
## Architecture
5354

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
date: '2026-02-26T15:00:00+01:00'
3+
draft: false
4+
title: 'Hyperlinks'
5+
weight: 14
6+
---
7+
8+
Æ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.
9+
10+
## How It Works
11+
12+
OSC 8 uses two escape sequences to bracket the clickable text:
13+
14+
| Sequence | Purpose |
15+
|----------|---------|
16+
| `ESC ] 8 ; params ; URL ST` | Start hyperlink (associate URL with following text) |
17+
| `ESC ] 8 ; ; ST` | End hyperlink (stop linking) |
18+
19+
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).
20+
21+
The String Terminator (ST) can be either `ESC \` or `BEL` (`\u0007`). Æsh uses `ESC \` by default.
22+
23+
## Quick Start
24+
25+
```java
26+
import org.aesh.terminal.tty.TerminalConnection;
27+
import org.aesh.terminal.utils.ANSI;
28+
29+
TerminalConnection conn = new TerminalConnection();
30+
31+
// Check if terminal supports hyperlinks
32+
if (conn.supportsHyperlinks()) {
33+
// Write a clickable link
34+
conn.writeHyperlink("https://aeshell.github.io", "Æsh Documentation");
35+
conn.write("\n");
36+
}
37+
```
38+
39+
## Terminal Support
40+
41+
| Terminal | Supported |
42+
|----------|-----------|
43+
| iTerm2 | Yes |
44+
| Kitty | Yes |
45+
| Ghostty | Yes |
46+
| WezTerm | Yes |
47+
| foot | Yes |
48+
| Contour | Yes |
49+
| Rio | Yes |
50+
| Warp | Yes |
51+
| Wave | Yes |
52+
| Hyper | Yes |
53+
| Tabby | Yes |
54+
| Extraterm | Yes |
55+
| GNOME Terminal | Yes |
56+
| Konsole | Yes |
57+
| Mintty | Yes |
58+
| xterm | Yes |
59+
| JetBrains | Yes |
60+
| VS Code | Yes |
61+
| Alacritty | Yes |
62+
| Windows Terminal | Yes |
63+
| Apple Terminal | No |
64+
| rxvt | No |
65+
| ConEmu | No |
66+
| tmux | No |
67+
| GNU Screen | No |
68+
| Linux Console | No |
69+
70+
On unsupported terminals the OSC 8 sequences are silently ignored, so it is always safe to send them.
71+
72+
## Connection API
73+
74+
The `Connection` interface provides methods for writing hyperlinks:
75+
76+
### Checking Support
77+
78+
```java
79+
// Heuristic check based on terminal type detection
80+
boolean supported = connection.supportsHyperlinks();
81+
```
82+
83+
This uses environment-based terminal detection via `TerminalEnvironment` and the `Device.TerminalType` enum. No terminal query is sent.
84+
85+
### Writing Hyperlinks
86+
87+
```java
88+
// Simple hyperlink
89+
connection.writeHyperlink("https://example.com", "Click here");
90+
91+
// Hyperlink with grouping ID (for links that span multiple lines)
92+
connection.writeHyperlink("https://example.com", "Click here", "link1");
93+
```
94+
95+
Both methods return the `Connection` for chaining:
96+
97+
```java
98+
connection
99+
.writeHyperlink("https://example.com", "Example")
100+
.write(" | ")
101+
.writeHyperlink("https://aeshell.github.io", "Æsh Docs")
102+
.write("\n");
103+
```
104+
105+
## ANSI Utility Methods
106+
107+
The `ANSI` class provides static methods for building hyperlink escape sequences directly:
108+
109+
### Wrapping Text
110+
111+
```java
112+
import org.aesh.terminal.utils.ANSI;
113+
114+
// Full hyperlink: opening sequence + text + closing sequence
115+
String link = ANSI.hyperlink("https://example.com", "click here");
116+
connection.write(link);
117+
118+
// With grouping ID
119+
String link = ANSI.hyperlink("https://example.com", "click here", "myid");
120+
```
121+
122+
### Building Sequences Manually
123+
124+
For more control, build the opening and closing sequences separately:
125+
126+
```java
127+
// Opening sequence
128+
String start = ANSI.buildHyperlinkStart("https://example.com", null);
129+
// Result: ESC ] 8 ; ; https://example.com ST
130+
131+
// Opening sequence with ID
132+
String start = ANSI.buildHyperlinkStart("https://example.com", "link1");
133+
// Result: ESC ] 8 ; id=link1 ; https://example.com ST
134+
135+
// Closing sequence
136+
String end = ANSI.buildHyperlinkEnd();
137+
// Result: ESC ] 8 ; ; ST
138+
139+
// Combine manually
140+
connection.write(start + "visible text" + end);
141+
```
142+
143+
### Grouping with IDs
144+
145+
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:
146+
147+
```java
148+
// Two segments of the same link on different lines
149+
String id = "doc-link";
150+
connection.write(ANSI.buildHyperlinkStart("https://example.com/long-page", id));
151+
connection.write("This link continues");
152+
connection.write(ANSI.buildHyperlinkEnd());
153+
connection.write("\n");
154+
connection.write(ANSI.buildHyperlinkStart("https://example.com/long-page", id));
155+
connection.write("on the next line");
156+
connection.write(ANSI.buildHyperlinkEnd());
157+
```
158+
159+
## Security
160+
161+
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`.
162+
163+
## Stripping Hyperlinks
164+
165+
The `Parser.stripAwayAnsiCodes()` method removes OSC 8 sequences while preserving the visible text. This is useful for measuring display width or logging plain text:
166+
167+
```java
168+
import org.aesh.terminal.utils.Parser;
169+
170+
String linked = ANSI.hyperlink("https://example.com", "click here");
171+
String plain = Parser.stripAwayAnsiCodes(linked);
172+
// Result: "click here"
173+
```
174+
175+
Both `ESC \` and `BEL` terminators are handled correctly.
176+
177+
## Complete Example
178+
179+
```java
180+
import org.aesh.terminal.tty.TerminalConnection;
181+
import org.aesh.terminal.utils.ANSI;
182+
183+
public class HyperlinkDemo {
184+
public static void main(String[] args) throws Exception {
185+
TerminalConnection conn = new TerminalConnection();
186+
187+
if (conn.supportsHyperlinks()) {
188+
conn.write("Terminal supports hyperlinks!\n\n");
189+
190+
// Simple link
191+
conn.write("Visit: ");
192+
conn.writeHyperlink("https://aeshell.github.io", "Æsh Documentation");
193+
conn.write("\n");
194+
195+
// Link with styled text (combine with ANSI formatting)
196+
conn.write("Source: ");
197+
conn.write(ANSI.BOLD);
198+
conn.writeHyperlink("https://github.com/aeshell", "GitHub");
199+
conn.write(ANSI.RESET);
200+
conn.write("\n");
201+
202+
// Multiple links on one line
203+
conn.write("\nLinks: ");
204+
conn.writeHyperlink("https://example.com/one", "One")
205+
.write(" | ")
206+
.writeHyperlink("https://example.com/two", "Two")
207+
.write(" | ")
208+
.writeHyperlink("https://example.com/three", "Three")
209+
.write("\n");
210+
} else {
211+
conn.write("This terminal does not support hyperlinks.\n");
212+
conn.write("Try running in: Kitty, iTerm2, WezTerm, or VS Code\n");
213+
}
214+
215+
conn.close();
216+
}
217+
}
218+
```
219+
220+
## Specification
221+
222+
The OSC 8 hyperlink protocol is documented by the Contour terminal:
223+
224+
[Contour VT Extension: Clickable Links](https://contour-terminal.org/vt-extensions/clickable-links/)
225+
226+
## See Also
227+
228+
- [Connection](connection) — Full `Connection` API reference
229+
- [Terminal Environment](terminal-environment) — Terminal type detection
230+
- [Synchronized Output](synchronized-output) — Tear-free rendering

0 commit comments

Comments
 (0)