Skip to content

Use hyperlinks directly in the terminal

This recipe is an example on how to leverage hyperlinks directly in the terminal. Hyperlinks are generated by terminal applications by using the OSC-8 escape sequences.

Requirements

By default, ls doesn't generate hyperlinks, here is an example of aliases to enable them in some common tools:

alias ls='ls --hyperlink --color=auto'
alias delta="delta --hyperlinks --hyperlinks-file-link-format='file://{path}#{line}'"
alias rg='rg --hyperlink-format=kitty'

On macOS, you'll need to install ls from coreutils. For example using homebrew, or use a modern alternative like eza.

Configuration

With this snippet, you can:

  • Click on an hyperlinked directory to navigate into that directory and list its contents
  • Click on an hyperlinked file and if its MIME type is 'text', open it directly in Neovim
  • Other hyperlinks like URLs remain unchanged and follow WezTerm's default behavior
local wezterm = require 'wezterm'
local act = wezterm.action
local config = wezterm.config_builder()

local function is_shell(foreground_process_name)
  local shell_names = { 'bash', 'zsh', 'fish', 'sh', 'ksh', 'dash' }
  local process = string.match(foreground_process_name, '[^/\\]+$')
    or foreground_process_name
  for _, shell in ipairs(shell_names) do
    if process == shell then
      return true
    end
  end
  return false
end

wezterm.on('open-uri', function(window, pane, uri)
  local editor = 'nvim'

  if uri:find '^file:' == 1 and not pane:is_alt_screen_active() then
    -- We're processing an hyperlink and the uri format should be: file://[HOSTNAME]/PATH[#linenr]
    -- Also the pane is not in an alternate screen (an editor, less, etc)
    local url = wezterm.url.parse(uri)
    if is_shell(pane:get_foreground_process_name()) then
      -- A shell has been detected. Wezterm can check the file type directly
      -- figure out what kind of file we're dealing with
      local success, stdout, _ = wezterm.run_child_process {
        'file',
        '--brief',
        '--mime-type',
        url.file_path,
      }
      if success then
        if stdout:find 'directory' then
          pane:send_text(
            wezterm.shell_join_args { 'cd', url.file_path } .. '\r'
          )
          pane:send_text(wezterm.shell_join_args {
            'ls',
            '-a',
            '-p',
            '--group-directories-first',
          } .. '\r')
          return false
        end

        if stdout:find 'text' then
          if url.fragment then
            pane:send_text(wezterm.shell_join_args {
              editor,
              '+' .. url.fragment,
              url.file_path,
            } .. '\r')
          else
            pane:send_text(
              wezterm.shell_join_args { editor, url.file_path } .. '\r'
            )
          end
          return false
        end
      end
    else
      -- No shell detected, we're probably connected with SSH, use fallback command
      local edit_cmd = url.fragment
          and editor .. ' +' .. url.fragment .. ' "$_f"'
        or editor .. ' "$_f"'
      local cmd = '_f="'
        .. url.file_path
        .. '"; { test -d "$_f" && { cd "$_f" ; ls -a -p --hyperlink --group-directories-first; }; } '
        .. '|| { test "$(file --brief --mime-type "$_f" | cut -d/ -f1 || true)" = "text" && '
        .. edit_cmd
        .. '; }; echo'
      pane:send_text(cmd .. '\r')
      return false
    end
  end

  -- without a return value, we allow default actions
end)

return config

Optional configuration

By default, hyperlinks can be opened with a simple mouse click, without any modifier keys. If you find yourself accidentally clicking hyperlinks, you can customize this behavior.

Check the mouse bindings section for examples on modifying the OpenLinkAtMouseCursor action.

For example, to require holding CTRL before opening a hyperlink, use the following configuration:

config.mouse_bindings = {
  {
    event = { Up = { streak = 1, button = 'Left' } },
    mods = 'CTRL',
    action = act.OpenLinkAtMouseCursor,
  },
  -- Disable the 'Down' event of CTRL-Click to avoid weird program behaviors
  {
    event = { Down = { streak = 1, button = 'Left' } },
    mods = 'CTRL',
    action = act.Nop,
  },
}

Drawbacks

This setup sends the text of the commands directly into the active pane which has some drawbacks:

  • Does not work inside of any interactive terminal program that is not a shell; the pane must be in a shell prompt
  • Requires an empty command prompt before clicking a hyperlink; otherwise, the command may not execute correctly
  • When a shell is not detected (see the is_shell function), WezTerm falls back to a long shell command, which may clutter the prompt slightly. This happens because WezTerm cannot directly determine a file's MIME type when not connected to a local shell.

If you're using tmux, you'll need to enable the hyperlinks terminal feature and, depending in your configuration (see bypass mouse reporting modifiers), add Shift when clicking an hyperlink.

set -sa terminal-features ",*:hyperlinks"