How to Start Visual Studio Code Extension Development
This guide will walk you through the basics of getting started with Visual Studio (VS) Code extension development.
Join the DZone community and get the full member experience.
Join For FreeWhat Is a VS Code Extension?
Before we jump into coding, we should clear up what a VS Code extension is on a technical level. Extensions are basically programs, written in JavaScript or TypeScript, which hook into various parts of VS Code. They provide functions for VS Code to call when certain events happen, and can programmatically interact with (some parts of) VS Code in those functions.
Extensions are distributed as ZIP files with a specific file and folder structure inside. The files contained in this structure are usually very verbose and not friendly for humans to read or write so there’s an official build tool to generate such ZIP files from source code: vsce
. Its usage will be explained later on in this post.
Development is best done in VS Code itself. It supports TypeScript out of the box and comes with special tools to run and debug your extension in another instance. In principle, other editors would work as well, but you should have VS Code ready for running and testing your extension either way.
Getting Started With VS Code Extensions
For starters, let’s install some command-line tools for development:
npm install --global yo generator-code vsce
…and set up our project.
$ yo code
_-----_ ╭──────────────────────────╮
| | │ Welcome to the Visual │
|--(o)--| │ Studio Code Extension │
`---------´ │ generator! │
( _´U`_ ) ╰──────────────────────────╯
/___A___\ /
| ~ |
__'.___.'__
´ ` |° ´ Y `
? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? hello-world
? What's the identifier of your extension? hello-world
? What's the description of your extension?
? Initialize a git repository? Yes
? Bundle the source code with webpack? No
? Which package manager to use? npm
Writing in /src/hello-world...
[...]
Choose “New Extension (TypeScript)
” and enter your extension’s details. You can always change these settings later. Optionally, initialize a Git repository and accept the default of “No” for “Bundle the source code with webpack?” Select the package manager on your system (most likely “npm”). After that, open the newly created folder in your editor of choice and open src/extension.ts
.
This is the entry point of your extension. VS Code will evaluate this file when loading your extension — but make sure you don’t put your initialization code directly in the top-level scope of the script!
A special function called activate
is intended for setup code, and is called by VS Code when an extension is first “needed” after being deactivated, freshly installed, or after VS Code was started. “Needed” in this case means that one of several Activation Events has been triggered. The generated example code demonstrates this with a command Activation Event, but we’ll also explore another way to start your extension later.
Running a VS Code Extension in Development Mode
Let’s have a look at the generated demo code in action! As mentioned before, it registers a command which can be run in the command launcher (Ctrl+Shift+P by default), so let’s try that now.
If you’re already in VS Code, go to the “Run & Debug” tab in the leftmost sidebar. Select the “Run Extension” launch configuration in the dropdown next to the green “Run” button. Then press the “Run” button (or F5).
If you’re not working from VS Code, run:
code --extensionDevelopmentPath=$PWD
…from your shell. Note that the path given to --extensionDevelopmentPath
has to be absolute.
VS Code will open, either with no workspace folder at all or with a recently opened workspace. Next, just press Ctrl+Shift+P and type “hello world”. A new command called “Hello World” should show up. Select it, hit Enter and a notification should appear.
Checking back with the code, we can clearly see how this is implemented. The call to registerCommand
tells VS Code what to do when the “Hello World” command is executed. However, this just provides the implementation. The definition of our command lives in the package.json
file, under the contributes
section.
"contributes": {
"commands": [
{
"command": "hello-world.helloWorld",
"title": "Hello World"
}
]
},
A lot of extension functionality is defined in contributes
: language support, settings, commands, and more. These definitions are referred to as “Contribution Points”.
Back in extension.ts
, we can see that the return value from registerCommand
is pushed onto context.subscriptions
. What’s that all about? “Subscriptions” might be a bit misleading here. More commonly, VS Code uses the term “Disposable”. Let’s check the docs.
“Represents a type which can release resources, such as event listening or a timer.”
Okay cool. TL;DR: most of the time, Disposables represent something that can be “stopped” or canceled (for example, providing a function to call when a command is invoked, as shown in the demo code). When your extension deactivates, context.subscriptions
calls dispose
on the Disposables pushed onto it, which makes it a handy tool for managing lifetime-scoped Disposables (like command handlers).
Exploring the VS Code Extension API
Time to add some features. Let’s display a notification when a file is saved. It’s pretty simple: We just have to register an event listener. Since the event is related to workspaces (think editors and files), we find its handle in vscode.workspaces
. onDidSaveTextDocument
seems appropriate, so let’s just call it from inside the activate
function:
disposable = vscode.workspace.onDidSaveTextDocument((evt) => {
vscode.window.showInformationMessage(`Saved ${evt.fileName}`);
});
context.subscriptions.push(disposable);
Since the event listener — much like a command handler — is a “continuous thing” that can be “stopped” the registration function returns a Disposable which we have to handle. Pushing it into context.subscriptions
is a good fit here since we never want to stop listening for save events while our extension is active.
Alright, let’s run that. Just press F5 to launch the last configuration again, open a text document, save, and… oh no. Nothing’s happening! The problem is an easy one: our extension has not been activated yet. Remember Activation Events? As mentioned before, our extension is currently only command-activated. If you run the “Hello World” command, then try saving again, a notification should appear as expected.
We can see the configuration responsible for that in the package.json
file under activationEvents
.
"activationEvents": [
"onCommand:hello-world.helloWorld"
],
Currently, only one Activation Event is registered called onCommand:hello-world.helloWorld
. This event fires when the “Hello World” command is executed. Since we would like to listen to all file save events without first having to run a command, let’s replace the whole onCommand[…]
string with onStartupFinished
, which fires right after VS Code has started.
"activationEvents": [
"onStartupFinished"
],
In general, you should aim for more specific Activation Events. Fewer extensions to start at once makes VS Code start up faster.
Now, let’s restart our launch configuration, open a file in the development host, and save it. Our extension finally displays a notification! By the way, if you leave the “Extension Development” instance of VS Code open while making changes, you can also press Ctrl+R to reload the window and try your changes instantly.
Let’s add a status bar item. TL;DRtD (too long, didn’t read the docs) this is the code:
disposable = vscode.window.setStatusBarMessage('Never saved anything');
context.subscriptions.push(disposable);
disposable = vscode.workspace.onDidSaveTextDocument((evt) => {
const disposable = vscode.window.setStatusBarMessage(`Saved ${evt.fileName} at ${Date.now()}`);
context.subscriptions.push(disposable);
});
context.subscriptions.push(disposable);
Just replace what we added for onDidSaveTextDocument
before.
The status bar is part of the window, so we find its functionality in vscode.window
. Makes sense! Status bar items are Disposables. Why? If you think about it: Status bar items can disappear, so it makes sense to use the Disposable interface here. We’ll just handle them via context.subscriptions
again.
One thing to note from the docs:
Note that status bar messages stack and that they must be disposed when no longer used.
They stack? Well, if we add a timeout to the “saved” status bar messages only, we can see this in action. Just pass a number as the second parameter to the call.
vscode.window.setStatusBarMessage(`Saved ${evt.fileName} at ${Date.now()}`, 1000);
“Saved” messages will disappear after one second to reveal the message below (down to “Never saved anything”). This function pushes status bar messages onto a stack.
Building and Installing a VS Code Extension
Okay, that was enough about development workflows and general concepts. Let’s finally build that special ZIP file mentioned in the beginning so you can actually install and use your extension. Open your extension’s source directory in a terminal and run vsce package
.
vsce package
Executing prepublish script 'npm run vscode:prepublish'...
> hello-world@0.0.1 vscode:prepublish /src/hello-world
> npm run compile
> hello-world@0.0.1 compile /src/hello-world
> tsc -p ./
ERROR Make sure to edit the README.md file before you package or publish your extension.
…
Okay, apparently vsce
thinks we intended to publish the extension and forgot to change the default generated README. Thanks. I like to resolve this situation with a quick echo this is not useful > README.md
but you’re welcome to write a more useful README.
After this, we just re-run vsce package
. This command will also display some actually helpful warnings (which you can just ignore and continue anyways). Afterward, you get a *.vsix
file. That’s the special ZIP file we mentioned, which you can open with a ZIP archive browser to explore its contents.
Installing it into your main copy of VS Code is also pretty easy: On the command line, run code --install-extension ./hello-world-0.0.1.vsix
. In the GUI, go to “Extensions” and click the three horizontal dots at the top of the left sidebar. Click “Install from VSIX…” and select your VSIX file.
And that’s it! You’re now a Visual Studio Code extension author. For more in-depth information about developing extensions and API references, check out the official docs.
Published at DZone with permission of Markus Zimmermann. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments