Get in touch
Thank you
We will get back to you as soon as possible
.pdf, .docx, .odt, .rtf, .txt, .pptx (max size 5 MB)

18.1.2022

10 min read

Developing VS Code extension to resolve your dev team pains

VS Code extension is a really powerful tool that can add any functionality to your IDE which isn't pre built-in. The extensions marketplace contains many solutions for different purposes. From ones that can make work with your git repository more efficient like GitLense to some that were made just for fun like Instagram-like stories for developers. But does it make sense to develop VS Code extension just for your team to resolve developer pains that are relevant only for your project? That’s what I’m going to talk about in this article.

Why do we need a special tool?

To explain how this idea came to my mind, I should briefly describe to you the project that I was working on for the last year. It is a cross-platform desktop data modeling application that uses React.js and Electron under the hood. For the purpose of handling data from many DB types, clouds, and schema registries, we deliver application plugins for each supported vendor. In our case, each plugin is a directory with configs and js files that are stored on the GitHub repository. To install a plugin means to clone or to download the directory from the app repo and put it into the plugin's home folder. It has over 30 plugins for now and this number is growing. Unlike users, we always need all plugins to be installed for development purposes. This makes a kind of a mess.

When you try to open a certain plugin development folder through the VS Code “open recent…” it’s common that you don’t have one that you are searching for there. And then you surf the folder where all plugins are located trying to find the one you need. Needless to say, it's a painful job.

Another case is updating plugins. It’s uncommon for users to have installed many plugins, which is why we don’t have a button like “update all” and have just “update” for each plugin, but it usually gets annoying to us to update 5 plugins one by one after a release wave.

The last inconvenient thing that I noticed after a couple of months on the project is plugins testing. It’s a usual case when we need a certain version of a plugin to reproduce a bug or one from a teammate fork and so on and so forth. This requires you to go to your browser, find the plugin repository, clone or just download its files and then place them into the correct directory. In other words, there was no way to obtain the required version of the code in a smooth way.

These three points pushed me to start developing my pet project which is now converted into a tool that is used by my teammates.

Why VS Code extension?

I started by choosing the technologies for the tool. The first thing I thought about was some kind of desktop app. However, one of the most valuable points for me was that it should be so handy that you could access the tool with a click and it must be one that doesn't interrupt your development process. Unfortunately, an app in a separate window couldn’t satisfy this purpose. The second point was cross-platform accessibility. Most of our team code using Ubuntu machines, but some of us use macOS and Windows. At the same time, all of us use VS Code as the main IDE. That was the point when VS Code extension popped into my head. It seemed to me so integrated into the coding process and so powerful that I decided to use it for an MVP.

MVP

Unfortunately, I didn't find any good external tutorial on how to start developing my own extension, so I started from the guide on the VS code website. There is an extension guides section that has a bunch of usage examples of different extension APIs. The section contains enough information to start and get acquainted with extension anatomy. I browsed different APIs and found a TreeView API which became the core thing that allowed me to list all plugins and action buttons.

To become acquainted with the TreeView API, let's build a sample extension that will use a tree view to display a simple JSON structure. By the way, it is a simplified version of the documentation browser in our team's extension. You can find the complete source code of this sample extension on my GitHub.

Here is a documentation config.

{
    "root": [
        {
            "title": "App configuration chapter",
            "link": "https://www.google.com.ua/",
            "links": [
                {
                    "title": "First link",
                    "link": "https://www.google.com.ua/"
                },
                {
                    "title": "Second link",
                    "link": "https://www.google.com.ua/"
                }
            ]
        },
        {
            "title": "App development chapter",
            "link": "https://www.google.com.ua/",
            "links": [
                {
                    "title": "Third link",
                    "link": "https://www.google.com.ua/"
                }
            ]
        }
    ]
}

It contains chapters in the root array. And links related to each chapter.

The steps for adding a tree view are to contribute the view in your [package.json](https://github.com/MaksymSlobodianyk/vscode-extension-tree-view-example/blob/main/package.json), create a [TreeDataProvider](https://github.com/MaksymSlobodianyk/vscode-extension-tree-view-example/blob/main/src/providers/DocumentationProvider.ts), and register the [TreeDataProvider](https://github.com/MaksymSlobodianyk/vscode-extension-tree-view-example/blob/main/src/providers/DocumentationProvider.ts)

First, you have to let VS Code know that you are contributing a View Container using the contributes.viewsContainers Contribution Point in [package.json](https://github.com/MaksymSlobodianyk/vscode-extension-tree-view-example/blob/main/package.json).

"viewsContainers": {
    "activitybar": [
        {
            "id": "simple-tree-view",
            "title": "Simple tree view",
            "icon": "resources/logo.svg"
        }
    ]
}

Also, you have to contribute a view using the contributes.viewsContainers Contribution Point in [package.json](https://github.com/MaksymSlobodianyk/vscode-extension-tree-view-example/blob/main/package.json).

"views": {
    "simple-tree-view": [ //viewsContainer views belog to
        {
            "id": "documentation-browser", 
            "name": "documentation browser ?",
            "when": "config.SimpleTreeView.showDocumentationBrowser"
        }
    ]
}

The second step is to provide data to the view you registered so that VS Code can display the data in the view. To do so, you should first implement the TreeDataProvider. Our TreeDataProvider will provide documentation config data. There are two necessary methods in this API that you need to implement:

  • getChildren(element?: T): ProviderResult<T[]> - Implement this to return the children for the given element or root (if no element is passed). When the user opens the Tree View, the getChildren method will be called without an element and if they expand the Tree View item the getChildren method will be called with this item as an element.
  • getTreeItem(element: T): TreeItem | Thenable<TreeItem> - Implement this to return the UI representation (TreeItem) of the element that gets displayed in the view.
export class DocumentationProvider implements TreeDataProvider<BasicTreeItem> {
        getTreeItem(element: Chapter): TreeItem {
        return element;
    }

    getChildren(element?: BasicTreeItem): Thenable<BasicTreeItem[]> {
        if (element instanceof Chapter) {
            return getChapterLinksItems(element.nestedLinks); //root element child (link)
        }
        return getChaptersItems(); //root element (chapter)
    }
}
</BasicTreeItem>

Also, we need a treeItemService that will fetch data from the config hosted on GitHub Pages and convert it into TreeItems.

export const getChapterLinksItems = async (nestedLinks: LinkNode[]): Promise<TreeItem[]> => {
    const links: Link[] = convertLinksItems(nestedLinks);
    return Promise.resolve(links);
};

export const getChaptersItems = async (): Promise<Chapter[]> => {
    try {
        const config = await getDocumentationConfig(); //fetching documentation config
        return Promise.resolve(convertChaptersItems(config.root));
    } catch (e) {
        window.showErrorMessage("Error occurred while fetching documentation config ?");
        return Promise.resolve([]);
    }
};

const convertChaptersItems = (chapters: ChapterNode[]): Chapter[] =>
    chapters.map(
        (chapter) =>
            new Chapter( //extends TreeItem
                chapter.title,
                chapter.link,
                chapter.links,
                TreeItemCollapsibleState.Collapsed //means that this tree item can have children
            )
    );

const convertLinksItems = (links: LinkNode[]): Link[] =>
    links.map((link) => 
        new Link( //extends TreeItem
            link.title, 
            link.link, 
            TreeItemCollapsibleState.None //means that this tree item can't have children
        )
    );

The third step is to register the above data provider to your view in the extension.ts. This can be done in the following way:

export async function activate(context: ExtensionContext) {
    const documentationProvider = new DocumentationProvider();
    window.registerTreeDataProvider('documentation-browser', documentationProvider); //bind documentation-browser to documentationProvider
}

extension.ts is the main file of the extension. It contains a view to provider bindings and commands registrations .

Here is the result? :

VS Code extension

Finally, let's add controls to be able to open listed links in the browser.

To implement this, you should follow three steps: contribute the command in your [package.json](https://github.com/MaksymSlobodianyk/vscode-extension-tree-view-example/blob/main/package.json) , implement business logic in the [DocumentationProvider](https://github.com/MaksymSlobodianyk/vscode-extension-tree-view-example/blob/main/src/providers/DocumentationProvider.ts) , and bind the handler with the command in the [extension.ts](https://github.com/MaksymSlobodianyk/vscode-extension-tree-view-example/blob/main/src/extension.ts).

First, you have to contribute a command and set its place using the contributes.commands and contributes.menus Contribution Points in [package.json](https://github.com/MaksymSlobodianyk/vscode-extension-tree-view-example/blob/main/package.json).

"commands": [ // creating command
    { 
        "command": "documentation-browser.openLink", //command name
        "title": "Go to documentation",
        "icon": {
            "light": "resources/light/goByLink.svg", //icon for the control (light theame)
            "dark": "resources/dark/goByLink.svg" //icon for the control (dark theame)
        }
    }
],
"menus": { // setting a place for created command
    "view/item/context": [
        {
            "command": "documentation-browser.openLink", //related command name
            "when": "view == documentation-browser", //will be visible only on documentation-browser view
            "group": "inline"
        }
    ]
}

Next, let's create a method to handle this command in DocumentationProvider :

openExternalURI(link: string) {
      env.openExternal(Uri.parse(link)); // opens link in your browser
}

Finally, you have to bind your command and a handler in extension.ts.

export async function activate(context: ExtensionContext) {
    ...
    registerCommands(documentationProvider);
}

const registerCommands = (documentationProvider: DocumentationProvider) => {
    commands.registerCommand('documentation-browser.openLink', node =>
        documentationProvider.openExternalURI(node.link)
    )
}

As result, we are able to open every item link by clicking the control on the item.

Developing VS Code extension to resolve your dev team pains-1

Now that you have a basic idea of how an extension works under the hood, let me show you how 0.1.0 (the MVP) looked like.

I implemented six main features:

  • Listing of installed plugins and their current versions;
  • Ability to open the certain plugin in a new VS Code or Sublime window;
  • Ability to open a folder that contains all plugins in a new VS Code or Sublime window;
  • Ability to update all outdated plugins by a single button;
  • Highlighting outdated plugins.

Developing VS Code extension to resolve your dev team pains-2

As you might have guessed, plugins were listed in the Tree view. I added a vendor logo as a tree item icon to simplify the visual search of the needed plugin. Also, VS Code provides a built-in highlighting and filtering of items that have entered symbols in their title.

Developing VS Code extension to resolve your dev team pains-3

Controls that are related to a single plugin were placed on each plugin item and ones related to all plugins were placed on the top of the plugin's browser view. Also, I added a bunch of settings that allows each developer to leave only controls that they really needed. As you can see, Sublime related buttons are hidden on the description map illustration.

Developing VS Code extension to resolve your dev team pains-4

The prototype allowed me to check whether it is convenient to use these controls placed in such a way or not.

How does it look like now?

MVP solved many pains and proved that it's pretty convenient to manage plugins in a tree view, but we still had some pains to solve, so I continued releasing new versions. Now, 5 months after the MVP, the extension evolved to version 0.4.1. Many significant features have been released and some colleagues have joined the development.

Unfortunately, VS code documentation is not really filled with examples of complex features and VS code API capabilities, so the next step that helped me a lot in my development journey was examining the code base of other extensions to find out how they implemented any of their features. Lots of pretty popular extensions keep repositories public, like GitLens. There are also two important documentation chapters: VS Code API reference - you can always refer to it in case you need to find a way how to interact with IDE or OS features from your extension. Contribution points reference - it contains documentation on how to set up extension appearance, key bindings, controls, configs, etc.

By this time, we’ve added more functionality to plugins items, so you have the possibility to view all forks, branches of forks, opened pull requests, branches, and tags of each plugin repo. Moreover, you can install plugins from any of these sources. Also, using an OctoCat button on a fork item you can do a git clone of the forked repo. In other words, just fork a repo on the GitHub site then switch to VS Code, and with a single click replace the installed plugin with the ready for development cloned one.

Developing VS Code extension to resolve your dev team pains-5

After solving the known pains of the team I thought about what else could be integrated into the extension. Recently, we started the development of in-project documentation and I decided to add it as a documentation browser. It’s a convenient way to collect and group important project documentation links. You can easily add new chapters and links or edit existing ones just by modifying the JSON config which is hosted with GitHub pages. Your changes will be instantly visible for every extension user.

Here is how documentation browser looks like:

Developing VS Code extension to resolve your dev team pains-6

We used many VS Code API features to cover over needs, but the most common thing the user faces are notifications. We use them to notify developers about, errors, task progress, and to ask some actions which could be required during plugins update or clone processes.

Developing VS Code extension to resolve your dev team pains-7

Developing VS Code extension to resolve your dev team pains-8

Is it worth it?

Absolutely, yes! Using the extension, we improved our plugins management and development experience quite a bit. Moreover, we are on the way to converting the plugins management extension to an entire project hub that will contain all the information and tools required for development. By the way, the documentation browser is the first milestone of this trip.

It seems to me that if you develop a long-term support product with some project-specific frequent actions that could be automatized, VS Code extension may be a good way to implement this. Also, as in our team's case, an extension development initiative can spread over the team and result in a team PET project.

I hope this blog post provides you with some ideas on how such an extension could improve your work experience. Just keep in mind that VS Code extension is an extremely powerful tool, which is mostly just limited by your needs and imagination!

0 Comments
name *
email *