What I Learned by Building My Own VS Code Extension

Avatar of Atishay Jain
Atishay Jain on (Updated on )

VS Code is slowly closing the gap between a text editor and an integrated development environment (IDE). At the core of this extremely versatile and flexible tool lies a wonderful API that provides an extensible plugin model that is relatively easy for JavaScript developers to build on. With my first extension, VS Code All Autocomplete, reaching 25K downloads, I wanted to share what I learned from the development and maintenance of it with all of you.

Trivia! Visual Studio Code does not share any lineage with the Visual Studio IDE. Microsoft used the VS brand for their enterprise audience which has led to a lot of confusion. The application is just Code in the command line and does not work at all like Visual Studio. It takes more inspiration from TextMate and Sublime Text than Visual Studio. It shares the snippet format of TextMate (Mac only) and forgoes the XML based format used in Visual Studio.

Why you should create an extension

On the surface, VS Code does not seem to provide many reasons for why anyone would create extensions for it. The platform has most of the same features that other editors have. The snippet format is powerful, and with extensions like Settings Sync, it is easy to share them over a Gist. Code (pun intended) is open source and the team is fairly responsive to requests. Basic support can be provided without a plugin by creating a “typings” file in the npm module.

Still, creating extensions for VS Code is something all web developers should try for the following reasons:

  • Playing with TypeScript: Many developers are still on the edge with TypeScript. Writing an extension in VS Code gives you a chance to witness how TypeScript works in action and how much its type safety and autocomplete features can help with your next JavaScript project.
  • Learning: The web gets a lot of slack for its performance. VS Code demonstrates how you can develop a performance-sensitive applications in Electron and some of the techniques — the multi-process and cluster-oriented architecture is so good that all Electron apps should just steal it for their own use.
  • Fun: Most important, it is a lot of fun developing VS Code extensions. You can scratch an itch and end up giving back to the community and saving time for so many developers.

Trivia! Even though TypeScript is an independent programming language with many popular uses including the Angular (2+) framework, VS Code is the sister project with the most impact on the language. Being developed in TypeScript as a TypeScript editor, VS Code has a strong symbiotic relationship with TypeScript. The best way to learn the language is by looking at the source code of Visual Studio Code.

What an extension can do

VS Code exposes multiple areas where an extension can create an impact. The exposed touch points could easily fill a book. But there’s unlikely a situation where you need all of them, so I’ve put together a listing here.

These outline the various places that can be extended as of version 1.25:

Area Description
Language grammar Variables, expressions, reserved words, etc. for highlighting
Code Snippets Items in autocomplete with tab-based navigation to replace certain items
Language configuration Auto-close and indent items like quotes, brackets, etc.
Hover action item Documentation tooltip on hover
Code Completion Autocomplete items when typing
Error Diagnostics Squiggly red underlines to indicate potential errors
Signature Helper Method signature tooltip while typing
Symbol Definition Location of the code where the symbol is defined in the inline editor and go to the definition (including a ⌘+Hover tooltip)
Reference For the inline editor with links, all files, and places associated with the symbol
Document Highlighter List of all positions where the selected symbol exists for highlighting
Symbol List of symbols navigable from the Command menu with an @ modifier
Workspace Symbol Symbol provider for an entire workspace
Code Action Squiggly green underline to indicate fixable errors with a fix action on click
Code Lens Always present inline metadata with clickable action items
Rename Support for renaming symbols used in multiple places
Document Formatting Fix indentation and formatting for the entire document
Document Range Formatting Fix indentation and formatting for selected text
On Type Formatting Fix formatting and indentation in real time
Color Provider Color popup menu alternative
Configuration Defaults Override settings and save as a new set of defaults
Command Commands in the ⌘+P menu
Menu Menu items in the top level menu, any menus alongside the document tab bar, and context menus
Key bindings Keyboard shortcuts
Debugger Debugger settings for debugging a new or existing language
Source Control Overrides to support a custom source control system
Theme Color theme
Snippet Same as code snippets above
View A new section in one of the docked panels on the left
View Containers A new docked panel on the left bar
TypeScript server plugins Overrides for the built-in TypeScript language server
Webview Alternative page in parallel to a document to show a custom rendering of a document or any custom HTML (unlike a View Container that is docked to a side)
Text Decoration Decoration on the gutter of the text area
Messages Popups for error, warning and informational messages on the bottom-right
Quick Pick Multi-select option selector menu
Input Box Text box for the user to input values
Status Bar Item Icon, button, or text in the status bar
Progress Show a progress indicator in the UI
Tree View Create a tree like the one used to define the workspace (which can be put inside a View or a View Container)
Folding Range Custom code folding into the plus button on the gutter
Implementation The implementation provider (languages like and TypeScript can have declaration and implementation as separate)
Diff Provider Diff view in source control mode
Commit Template For commits in source control mode

Apart from this, you can invoke any functionality that the user can invoke in any of the menus with whatever parameters the user can pass. There are events on almost every functionality as well as a lot of file systems and text file-related utility methods.

Let’s start building

Alright, enough with the preamble — let’s start putting an extension together with what we’ve just learned.

Creating the extension

To build an extension, start with the VS Code extension generator.

npm install -g yo generator-code
yo code

Next up, configure the options. Here’s how I set things up:

Extension Generator for VS Code

TypeScript is optional, but highly recommended. Just another way to remind you that VS Code works really well with TypeScript.

Now, you can open the folder in VS Code. I would recommend checking the initial commit. You can look at the quick start guide that gets generated in the project to understand the file structure. Take a quick stab at seeing it live. Go to the debug panel and launch the extension in debug mode. Place a breakpoint inside the activate method in extension.ts to walk through this in action. The “Hello world” application already registers a command that can launch an informational message in the footer. Go to the commands menu with ⌘+⇧+P (Ctrl+Shift+P on Windows) and select hello world.

Hitting the debugger

You can put another breakpoint in the registerCommand callback to get the command event.

Open package.json for a description of the plugin’s configuration. Change activationEvents to a more specific one based on what type of language or file protocol you want to support. All Autocomplete supports all file formats, indicated by an *. You should also look at the contributes section if you want to contribute to things like settings, commands, menu items, snippets, etc.

All Autocomplete in the contributes section with contribution points

Many of these contributions have additional JSON in the same package.json file with all the information. Some APIs require code that has to be used in the activate call like vscode.commands.registerCommand for creating a command. For an autocomplete extension, the contributes section is not required and can be removed.

To use the same All Autocomplete options in extension.ts, replace the activate function with the following:

export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.languages.registerCompletionItemProvider('*', {
    provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) {
        return [new vscode.CompletionItem("Hello")];
    }
    }));
}

You can specify further details about the completion item, like attached documentation using more options in the object. Now, if you debug this and type H you should see Hello in the completion menu. The code to register most of the language-based providers is nearly the same.

The All Autocomplete menu

You can see the All Autocomplete menu in vscode.languages, which provides options to register providers. Each provider has its own set of parameters that we can fill up similar to the completion item provider.

All Autocomplete with the list of language-specific providers

The document object provides access to the document with utility methods to access text at specific positions and ranges. It is strongly encouraged to use the APIs to access all documents instead of the raw Node.js APIs.

You can parse the document on demand or keep a data structure (like the trie used in All Autocomplete) optimized to search for inputs as the user is typing.

Tip: If you are looking for some text selection/manipulation APIs, there will most likely be an API already available. No need to reinvent the wheel. You can precisely get text with document.getText(document.getWordRangeAtPosition(position)). Alt+Click on any VS Code object to get to the class structure and JSDoc documentation.

Publishing the extension

Once the extension is complete, it is time to publish it to the marketplace. VS Code has a command line tool (vsce) for publishing but it does require creating an account.

Here’s how to prep the extension for submission:

  • Clean up the package: The package.json and README.md files provide the description and details about your extension that get displayed in the marketplace. It is essential to spruce up those files and fill all missing information so that the documentation comes out clean. Good to add some badges and a self-describing GIF to the repo.
  • Create an account: You need to create a Visual Studio Team Services (VSTS) account. This is the only place where VS Code ties up with Visual Studio. You need to sign up and get an access token. The VSTS interface is slightly confusing, but you don’t need to learn a new code management tool to publish. Go to the security section to get the access token. (Don’t make the same mistake as me and confuse the gear icon in the menu with security.)
  • Install: Use the vsce command line tool to publish extensions. It is available in npm and is extremely easy to use.
Security Settings in VSTS

Tip: The access token for VSTS expires every year and therefore the account information is extremely important. It is also required to reply to the comments on the marketplace, though most users are active on GitHub and that is where you are more likely to get bugs and feature requests.

npm install -g vsce # One time installation
vsce create-publisher <name> # One time create publisher
vsce login # One time login. Asks for access token.
vsce publish <version> # Publish or update the extension

VS Code does not compile extensions on the server. Make sure the output folder created by compiling your extension is up to date. Also be sure to check case sensitivity of your file names because incorrect file paths will break in Linux. Native modules in Node are a huge pain and should not be used. It is impossible to compile and provide all platform variants for specific Electron versions. (Someone needs to create a PhoneGap build for npm!) This will get better over time with WebAssembly and N-API.

Support and Maintenance

The VS Code team is super active on GitHub and StackOverflow. GitHub is the right place to file bugs that you might discover in the API. The team is fairly responsive though you need to make the context extremely clear as you would with any helpful bug report.

You should have a GitHub repository for your extension and expect users to file issues directly on GitHub. Expect VS Code users to be proficient with tools and technology (some of them may have a lot more knowledge that you). Even though it’s a free endeavor, keeping humility and treating issue reporters as customers is the right behavior.

Tips on performance

VS Code has good performance because it is built with an architecture that isolates things like extensions that can cause slowness. If your extension does not return in time, your output might be ignored.

A few things that can help maintaining the performance of the editor include:

  • Using the official APIs: It is easy to ignore them and build your own. The “typings” file is wonderful and has documentation for all of the available APIs. A five minute search there can save a lot of time. If you need some files, it is better in most cases to request VS Code to open it in an editor than it is to read it from a disk (unless you are reading thousands of files and not leaving them open).
  • Expose options: Ensure there is a way for users to turn off capabilities that rely on heavy events, like every keystroke. It may not seem noticeable on your machines, but this is not the place for that. Developers maintain their dot files forever and they spend time going through options if there is an issue they to work around. There is no harm in exposing a way to gracefully degrade in case every keystroke is not possible.
  • Child process: Developer tools — especially on the command line — are extremely fast and well optimized. If you need something that involves a lot of files that could choke the JavaScript thread, call into a native tool and politely ask the users to install the dependency. Electron has limitations and we should accept them.

Wrapping up

VS Code is a very flexible application and provides almost all of its guts to extension developers. The workflow to develop and debug extensions is easy and every JavaScript developer should feel at home trying them out. The biggest advantage of developing extensions outside of improving our own lives is the possibility to see a huge TypeScript project in action. For that alone, I would recommend all users to definitely give it a go and share your extensions in the comments.

My extension is live on the VS Code marketplace and I would love to get your feedback on that as well. 🙂