Handle links in the terminal from your Visual Studio Code extension

June 16, 2022

For a while now, I have been thinking about a way to handle links shown in the Visual Studio Code terminal. By default, when you control/command + click on a link, you navigate to them, but in my case, I wanted to be able to handle the link in the extension.

To look for a solution, I opened the Visual Studio Code editor its code and found the registerTerminalLinkProvider API, which is also available for extensions.

The terminal link provider allows you to register a provider that enables the detection and handling of links within the terminal. This API is exactly what I have been looking for, and it is also reasonably easy to start using it.

The solution

The registerTerminalLinkProvider method requires two properties:

  • provideTerminalLinks: The provider that detects the links;
  • handleTerminalLink: The handler when you click on the link.

The solution I came up with is to check if the terminal line contains a link, and if it does, provide the Terminal Link action.

In the terminal link handler, I let Visual Studio Code show an information message to allow the user to choose between navigating to the link or running it in the extension.

The code for this looks as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import * as vscode from 'vscode';

export const UrlRegex = /(http|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+)|(localhost))([\w.,@?^=%&:\/~+#-]*[\[email protected]?^=%&\/~+#-])/gi;

interface CustomTerminalLink extends vscode.TerminalLink {
  data: string;
}

export function activate(context: vscode.ExtensionContext) {
  vscode.window.registerTerminalLinkProvider({
    provideTerminalLinks: (context: vscode.TerminalLinkContext, token: vscode.CancellationToken) => {
      // Detect the first instance of the word "link" if it exists and linkify it
      const matches = [...context.line.matchAll(UrlRegex)];

      if (matches.length === 0) {
        return [];
      }

      return matches.map(match => {
        const line = context.line;

        const startIndex = line.indexOf(match[0]);

        return {
          startIndex,
          length: match[0].length,
          tooltip: 'Handle link',
          data: match[0],
        } as CustomTerminalLink;
      })
    },
    handleTerminalLink: (link: CustomTerminalLink) => {
      vscode.window.showInformationMessage(`How would you like to handle the link?`, 'Navigate', 'Run in extension').then(action => {
        if (action === 'Navigate') {
          vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(link.data));
        } else if (action === 'Run in extension') {
          // Do something else with the link
        }
      });
    }
  });
}

// this method is called when your extension is deactivated
export function deactivate() {}

The result of the code can be seen here:

Handle the link in the terminal
Handle the link in the terminal

Comments

comments powered by Disqus