Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions frontend/app/view/term/termwrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,36 @@ export class TermWrap {
this.lastCommandAtom = jotai.atom(null) as jotai.PrimitiveAtom<string | null>;
this.claudeCodeActiveAtom = jotai.atom(false);
this.webglEnabledAtom = jotai.atom(false) as jotai.PrimitiveAtom<boolean>;
// Custom linkHandler for OSC 8 hyperlinks. Without this, xterm.js falls
// back to window.confirm() + window.open(), which Electron's renderer
// blocks — so the confirmation dialog appears but clicking OK silently
// fails. Route OSC 8 clicks through the same Cmd/Ctrl-click path that
// WebLinksAddon uses for regex-detected URLs.
options.linkHandler = {
activate: (event: MouseEvent, uri: string) => {
event.preventDefault();
switch (PLATFORM) {
case PlatformMacOS:
if (event.metaKey) {
fireAndForget(() => openLink(uri));
}
break;
default:
if (event.ctrlKey) {
fireAndForget(() => openLink(uri));
}
break;
}
},
hover: (event: MouseEvent, uri: string) => {
this.hoveredLinkUri = uri;
this.onLinkHover?.(uri, event.clientX, event.clientY);
},
leave: () => {
this.hoveredLinkUri = null;
this.onLinkHover?.(null, 0, 0);
},
Comment on lines +167 to +174
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

xterm.js linkHandler hover interaction with WebLinksAddon link provider ordering OSC 8

💡 Result:

In xterm.js, link detection and hover/click interactions for both WebLinksAddon (pattern-matched URLs) and OSC 8 hyperlinks are handled through the unified link provider system via terminal.registerLinkProvider. Providers are queried in registration order for links on a line; higher-priority (earlier registered) providers can suppress lower ones if ranges overlap. OSC 8 links are detected by the core OscLinkProvider (always active, detects via extended cell attributes set by OSC 8 escape sequences). WebLinksAddon registers a custom ILinkProvider for regex-matched web URLs. To control provider ordering: - Load WebLinksAddon first if you want pattern links to take priority over OSC 8. - Load custom providers or WebLinksAddon after core initialization to place them after OscLinkProvider. - Use terminal.options.linkHandler = {..., hover: (event, text, range) => {...}} for shared hover/activate callbacks across all providers (used by OscLinkProvider if set). Hover interaction: Implement linkHandler.hover(event, text, range) to show tooltips (e.g., full OSC 8 URI on hover, as display text may differ). Add 'xterm-hover' class to DOM elements to block event passthrough. Example from docs shows popup with URI and modifier hint. Setup example for both: const linkHandler = { hover: showLinkPopup, // Custom hover tooltip leave: removeLinkPopup, activate: (e, text) => { /* handle click */ }, allowNonHttpProtocols: true // For non-http OSC 8 }; terminal.options.linkHandler = linkHandler; terminal.loadAddon(new WebLinksAddon(undefined, { hover: linkHandler.hover, leave: linkHandler.leave })); This ensures consistent hover behavior. Registration order determines which provider "wins" overlapping ranges.

Citations:


🏁 Script executed:

# Search for WebLinksAddon and linkHandler usage in the termwrap file and related files
rg -A 5 -B 5 "WebLinksAddon|linkHandler" frontend/app/view/term/termwrap.ts

Repository: wavetermdev/waveterm

Length of output: 2054


🏁 Script executed:

# Look for the broader context around the hover/leave code
sed -n '150,190p' frontend/app/view/term/termwrap.ts

Repository: wavetermdev/waveterm

Length of output: 1720


🏁 Script executed:

# Search for onLinkHover definition and usage
rg "onLinkHover" frontend/app/view/term/

Repository: wavetermdev/waveterm

Length of output: 774


🏁 Script executed:

# Get more context around the WebLinksAddon initialization
sed -n '180,220p' frontend/app/view/term/termwrap.ts

Repository: wavetermdev/waveterm

Length of output: 1714


🏁 Script executed:

# Search for the complete WebLinksAddon constructor call
rg -A 20 "new WebLinksAddon" frontend/app/view/term/termwrap.ts

Repository: wavetermdev/waveterm

Length of output: 882


🌐 Web query:

xterm WebLinksAddon hover leave options vs terminal linkHandler both fire

💡 Result:

Yes, both the WebLinksAddon hover/leave options and the terminal.options.linkHandler hover/leave callbacks can fire for the same link interaction. This occurs when using both mechanisms together, as recommended in the official xterm.js documentation for handling both pattern-matched web links (via WebLinksAddon) and OSC 8 hyperlinks (via terminal.options.linkHandler). The documentation provides an example where the same linkHandler object (with shared hover and leave functions) is passed to WebLinksAddon(activateLink, linkHandler) and set as xterm.options.linkHandler = linkHandler. In this setup: - WebLinksAddon uses its ILinkProviderOptions.hover/leave (from the options parameter) for URL pattern links. - terminal.options.linkHandler (ILinkHandler.hover/leave) handles OSC 8 links. If separate handlers are implemented with their own hover/leave logic (e.g., both showing popups), the callbacks will execute independently for applicable links, causing both to fire on hover/leave events. This is by design for comprehensive link support across link types, but requires careful implementation to avoid duplicate effects like multiple popups. To prevent duplication: - Use a single shared handler object for both, as in the docs. - Implement idempotent hover/leave logic (e.g., remove previous popup before creating new). - The docs example demonstrates popup management in shared hover/leave functions. No bugs reported for duplicate firing; it's expected behavior when both are active.

Citations:


🌐 Web query:

@xterm/addon-web-links constructor hover leave options override terminal linkHandler

💡 Result:

The @xterm/addon-web-links provides the WebLinksAddon class with a constructor that accepts an optional click handler and options for customizing hover, leave, and regex behaviors. There is no direct "linkHandler" parameter in the constructor; instead, customization is done via ILinkProviderOptions or by setting terminal.options.linkHandler separately for broader link handling (including OSC 8 links). ## Constructor typescript new WebLinksAddon(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkProviderOptions) - handler: Optional callback fired on link activation (click). Defaults to opening the URI. - options: Optional ILinkProviderOptions object. ## ILinkProviderOptions Interface typescript interface ILinkProviderOptions { hover?: (event: MouseEvent, text: string, location: IViewportRange) => void; leave?: (event: MouseEvent, text: string) => void; urlRegex?: RegExp; } - hover: Callback when mouse hovers over a detected link. Use to show tooltips (create elements inside Terminal.element with class 'xterm-hover' to block events). - leave: Callback when mouse leaves a link (may fire before hover). - urlRegex: Custom RegExp to override default URL detection. ## Usage Example typescript import { Terminal } from '@xterm/xterm'; import { WebLinksAddon } from '@xterm/addon-web-links'; const term = new Terminal; const addon = new WebLinksAddon( (event, uri) => console.log('Clicked:', uri), // Custom click handler { hover: (event, text, location) => { // Show tooltip example from docs console.log('Hover:', text); }, leave: (event, text) => { console.log('Leave:', text); }, urlRegex: /custom-url-pattern/ // Override regex } ); term.loadAddon(addon); ## Overriding/Advanced linkHandler For comprehensive control (web-links + OSC 8), implement ILinkHandler and set term.options.linkHandler: typescript term.options.linkHandler = { activate: (event, text) => { /* handle click / }, hover: (event, text, range) => { / hover / }, leave: (event, text, range) => { / leave */ }, allowNonHttpProtocols: true // Optional, security risk if misused }; This works alongside the addon. Use modifier keys (Ctrl/Cmd+click) for safety. The addon focuses on regex-detected web links; terminal.options.linkHandler covers all links including explicit hyperlinks.

Citations:


Remove duplicate hover/leave callbacks from WebLinksAddon options.

The same hover and leave logic is implemented both in terminal.options.linkHandler and in the WebLinksAddon options. Per xterm.js documentation, for WebLinksAddon links, both will fire independently—causing the same state updates (hoveredLinkUri, onLinkHover) to be called twice. The recommended pattern is to pass a single shared handler object to both: set terminal.options.linkHandler = linkHandler, then pass that same handler to WebLinksAddon(activateCallback, linkHandler). This avoids duplication and ensures consistent behavior across link types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/term/termwrap.ts` around lines 167 - 174, The hover/leave
callbacks are duplicated in WebLinksAddon options and
terminal.options.linkHandler causing double calls; create a single shared
handler object (e.g., const linkHandler = { hover: (event, uri) => {
this.hoveredLinkUri = uri; this.onLinkHover?.(uri, event.clientX,
event.clientY); }, leave: () => { this.hoveredLinkUri = null;
this.onLinkHover?.(null, 0, 0); } }) then set terminal.options.linkHandler =
linkHandler and pass that same linkHandler to new
WebLinksAddon(activateCallback, linkHandler) instead of providing inline
hover/leave functions in two places (references: hoveredLinkUri, onLinkHover,
terminal.options.linkHandler, WebLinksAddon, hover, leave).

};
this.terminal = new Terminal(options);
this.fitAddon = new FitAddon();
this.serializeAddon = new SerializeAddon();
Expand Down