Link Handling

Clickable links may appear in the terminal output two ways:

  • Emitted using an explicit escape sequence (OSC 8).
  • Implicitly using something that looks a URL in the output, recognized using pattern matching. This requires the web-links addon.

Setup

This is one way to handle both kinds of links.

function activateLink(event, uri) {
  doHandleLink(); // example: open link in new browser window
}
let linkHandler = {
  activate: (event, text, range) => { activateLink(event, text); } ,
  hover: (event, text, range) => { /* nothing, by default */},
  leave: (event, text, range) => { /* nothing, by default */},
  allowNonHttpProtocols: true
};

/* Detect links because of URL patterns. */
webLinksAddon = new WebLinksAddon(activateLink, linkHandler);
/* Handle explicit links using USC 8 escape sequences. */
xterm.options.linkHandler = linkHandler;

Require modifier key

Terminal emulators that handle clicking on a link usually require a modifier to be pressed, to avoid unintentional window-opening. Commonly the Ctrl modifier (or on macOS the Cmd modifier) must be pressed.

function linkRequiresModifier() { return true; }
function isMac() {
  return typeof navigator != "undefined" ? /Mac/.test(navigator.platform)
      : typeof os != "undefined" ? os.platform() == "darwin" : false;
}

// Replace activateLink above
function activateLink(event, uri) {
  if ((isMac() ? event.metaKey : event.ctrlKey)
    || ! linkRequiresModifier()) {
      doHandleLink(); // example: open link in new browser window
  }
}

Display URL on hovering

It might be helpful to show the full URL when hovering over a link, especially for links created by OSC 8 (which might not show the actual URL). This is safety feature commonly implemented in web browsers and mail readers.

let _linkPopup;
function removeLinkPopup = (event, text, range) {
  if (_linkPopup) {
     _linkPopup.remove();
     _linkPopup = undefined;
  }
}
function showLinkPopup(event, text, range) {
  removeLinkPopup(event, text, range);
  let popup = document.createElement('div');
  popup.classList.add('xterm-link-popup');
  popup.style.position = 'absolute';
  popup.style.top = (event.clientY + 25) + 'px';
  popup.style.right = '0.5em';
  popup.innerText = text;
  if (linkRequiresModifier()) {
    popup.appendChild(document.createElement('br'));
    const e2 = document.createElement('i');
    e2.innerText = `(${isMac() ? 'Cmd' : 'Ctrl'}+Click to open link)`;
    popup.appendChild(e2);
  }
  const topElement = event.target.parentNode;
  topElement.appendChild(popup);
  const popupHeight = popup.offsetHeight;
  if (event.clientY + 25 + popupHeight > topNode.clientHeight) {
    let y = event.clientY - 25 - popupHeight;
    popup.style.top = (y < 0 ? 0 : y) + 'px';
  }
  _linkPopup = popup;
};

linkHandler.hover = showLinkPopup;
linkHandler.leave = removeLinkPopup;

Possible CSS styling for the hover popup:

div.xterm-link-popup {
  font-size: small;
  line-break: normal; /* auto doesn't work for WebKit */
  padding: 4px;
  min-width: 15em;
  max-width: 80%;
  border: thin solid;
  border-radius: 6px;
  background: #6c4c4c;
  border-color: #150262;
}