What do you want to learn?
Skip to main content
Hacking VS Code: Write Your First Extension for Visual Studio Code
by Jeff Ammons
All software developers need a robust text editor, like Visual Studio Code, to modify and extend to perfectly meet their specific needs. This course will teach you how extend your text editor to help you become a better developer.
Start CourseBookmarkAdd to Channel
Table of contents
I'm Jeff Ammons of jeffa.tech and codecareeracademy.com. And I'm here to show how to get started hacking VS Code by writing your first extension for Visual Studio Code. Anyone who builds things for a living depends on their tools to help them get the job done. It's also a rare craftsman who can rely on just a single tool. Although I spend most of my in Visual Studio, I always like to keep one or two other tools on hand for specific tasks. No tool is more important for programming than a really good text editor and the ability to modify to suit your needs is critical. In this module, I'm going to walk you through why you shouldn't rely on just one tool, why you should use an editor you can modify and extend, and what kind of problems can extensions solve. In later modules, we'll learn to build and publish extensions to Visual Studio Code. We'll start simple with Snippets instead of a full extension. Then we'll create a hello world extension to see how an extension actually works. After that, we'll create a real world extension that fills a real need. Then finally, we'll learn about distributing our extension. If that sounds interesting, then let's take a look at why you need a tool that you can customize.
Why You Need Tools You Can Customize
The goal of this module was simple. Show you why you might want to build and distribute your own extensions to Visual Studio Code. We've learned with Visual Studio Code is and why you might want to extend it. In the next module, we'll start with the simplest way to create helpers and code, Snippets. These aren't sophisticated, but they can cover a bit of ground quickly and easily.
Before we try to build a jet engine, let's start with some simpler options first. In this module we'll take a look at easy ways to customize code without writing an extension. First we'll look at how you can make basic configuration changes like font family and size. Next we'll dive into snippets which will speed your development by popping in boiler plate code. Finally we'll check out an extension that lets you sync your settings, snippets, and even other extensions across computers and even operating systems. That makes sure that wherever you need to work, code will be tailored to your preferences.
Let's start with basic configuration. If you've been using text editors like Sublime Text, this is going to be pretty familiar. But if you're mostly a Visual Studio user it might seem a bit obscure. We'll fire up Visual Studio code and then look under the File menu for Preferences and then User Settings. If you're a Visual Studio user you probably expected a dialog box with lots of tabs, sub-tabs, and sub-sub-dialogs. Instead what we have are two panes, one with a Default Settings file, and the other with the user specific settings dot json file. The premise here is that any settings you put into your settings dot json file will override the same setting in the default file. For example, in the Default pane you can see the editor dot font size is set to zero, or maybe you can't see that because it's so small. Zero in this case means that we aren't specifying a size, not an actual size of zero. Over here in the settings dot json file, you want to paste or type in the editor dot font size and then whatever point size works for you. Since I'm recording this and lots of Pluralsight subscribers watch on mobile devices, I'll go really large and put in 20. As soon as I save, you can see the font size increase. Next let's set the font family to the one true font, Comic Sans. Or maybe you prefer Consolas or RobotoMono. You can see in the left pane that the text isn't wrapping so let's set the editor dot wrapping column to zero. This will wrap the text to whatever size the viewport is. Now that you can read the comments in the left pane, you can see that the default wrapping is at column 300. Let's scroll down through the editor settings to the window configuration settings. Here's one that's really handy if you give talks and presentations. If we bring over window dot zoom level and set it to three, you can see that not only the font size increases, it also enlarges the icons. The zoom level increases by 20% with each step, with zero meaning no zoom. I keep this in my settings file set to zero so I can quickly switch into presentation mode. Let's scroll through some of the other options. Here's one I like: files dot trim trailing white space. If we set this to true, then every time we save, code will trim off any trailing white space. One more I want to show you is back at the top of the default settings. This one is pretty obvious. Editor dot line numbers, true or false. You can see that there are lots more settings you can tweak and they are pretty well documented right in the default settings file. The last thing we'll look at right now is how to set up custom settings per project. I'll close the user settings and then open a folder. We have one file. Now I'm going to go to File, Preferences and this time Workspace Settings. As you can see, this gives us a new settings dot json file. I'll just put editor dot line numbers and set it to false so that we get rid of the line numbers. Let's see where that file lives. In our project directory, you can see that we now have a dot vs code folder that has the settings dot json file. To see the difference, I'll load up this other project and now we have our line numbers back. That covers the most basic customizations you can do to code. In the next clip, we'll take a look at inserting boiler plate code via snippets.
In this module, we've taken our first steps towards hacking Visual Studio code to work the way we want it to. First we looked at how to change our basic settings. Next we looked at snippets and how we can both install sets of them from the marketplace and create our own. Finally we installed and configured the Visual Studio Settings Sync extension so that we could use a GitHub gist to share our settings, keybinding snippets, and extensions across all the places that we use code. We synced up our Windows, Mac, and Linux versions so that no that no matter which environment we're working in, code will look and act the way we want. In the next module, we'll see how to create a simple extension ourselves.
Creating a Hello World Extension
Now that we've covered the basics of changing your settings, writing some snippets and syncing across operating systems. Let's dive right in to creating an extension. We'll start slow with the ever popular Hello World. In this module, we'll start by Assembling our Tools and Generating the Skeleton of an extension. Next, we'll take a Tour of the Project structure and look at the important files. After that, we'll try running our extension and make some changes to it. We'll finish up by taking our first look at the debugger, this is going to give us the foundation we need to make a real world extension in the next module. So let's get started.
Assembling Our Tools
Tour of the Hello World Extension
Modifying the Hello World Extension
Now let's really dig into our hello world extension. We took a brief look at it in the last clip but we'll start really playing around with it in this one. As you'll recall, the extension dot js file here is where the actual code lives. The vs code variable is our library of vs code functionality. There are a number of ways our extension could be trigger but in all cases when vs code detects that our extension has been called for the first time, it will call this activate method and inject this context object. I'll quickly explore icon here to close up the sidebar and let us see more code. So on activation, we write to the console that the extension is active. Next, we call the vs code object and register a new command in its commands list. The parameters here are an identifying name and the function we want to execute. Let's jump over intpo the node module's vs code module and take a look at the vs code dot d dot ts file. Here you can see in the comments, a command is a function with a unique identifier. The function is sometimes also called command handler. If we slide down a bit, you can also see an example that looks a lot like our sample. You can see down here that it says it will bind the identifier to a title under which it will show up in the palette based on what's in our package dot json file. Now let's jump over to package dot json file. Here we have the basic metadata like the name, version and et cetera and if we scroll on down just a bit, we'll see a contributes object that has an array of commands. In this case, it's just one and it's command properties is extension dot say hello which matches the unique identifier we passed in over here. As I mention in the last clip, that function is going to access the window object in the vs code object and call it show information message method. So let's actually run it now. First, we hit f five. Now you can see that we switched into debug mode and launched a new instance of code. I'll switch back to the original instance here so you can see what the debug mode looks like. We have the side bar, then the editor and finally the debug console. Up on the top, we have some debug controls for stepping through code if we set a break point. I'll close the sidebar and the editor window so that we can concentrate on the console. Now try to put this side by side so we can see both. Back over here on the debug instance, I'll hit f one, open the command palette and type in hello. There we see the title hello world that was in our package dot json. Now, I'll select the command and there's our information message. We can also see over in the console that our console log call made it. Let's close that and then stop the debugger by clicking the stop button here. I'll close the debug console and switch back to the explorer. Let's change this to show error message and then rerun it. We also have a warning message available. How about if instead of showing a message, we want to actually insert hello world into the current document. Let's comment this out. First, we need to get the active text editor from the vs code dot window. We'll assign it to a variable to keep things simple. Now we'll call the edit function and pass in a new function that takes in edit builder parameter. On that edit builder, we'll call the insert function and tell 'em we want our insertion to be at the editor's current selection start and insert the text hello world, so let's run that. I'll add some text here, I'll highlight that and then call the extension again and then search it right at the beginning. What if we wanted to actually replace the current selection? Let's comment this out and then start out the same way with editor dot edit function edit builder and then, edit builder dot delete, editor dot selection. Now we call then and pass in a new function that also takes an edit builder and then the same call we made before. Let's try that. That's looking good but what happens if I close the document and try it? Uh oh, I'll put a break point right here. Now, we'll step over. (exclaims) See here the editor is null so when we try to access the edit function, it's going to throw an exception. Let's prevent that from happening. We can wrap this up in a try catch like this. We run it and well, well nothing. If we look over here on the console, we can see our message. I think we can do better than this. We'll say if not editor then show a warning message and return so that we can skip the rest of the functions. Why catch an exception we could just prevent? Let's run it and there's our warning. Before we wrap up this clip, I'll make one more change. See how often the word function shows up. How about if we use the fancy new error functions here, I'll delete the word function and put a fat error right there. Uh oh, if we hover over the dreaded reg squiggly, it tells us that it only works with es six, but it also tells us that we can specific es six in the js config dot json file. So, let's do that and no more squigglies. I'll just do that with the others here as well and we're done.
Building a Real World Extension
In the last module, we built a simple extension divisual studio code that didn't do much. In this module, we'll tackle building a module that solves a real-world problem. First, we going to define the problem we want to solve. Then we'll create our project and add it to GitHub. Next, we'll define the functions we plan to build and set up tracer bullets to highlight the basic pathways we need. Then we'll flesh out our first function, and move on to learning how we can write some unit tests. After that, we'll tackle the second larger function, and finally, we'll learn how to install our extension locally so that we can actually use it.
What’s the Problem?
So what problem do we want to solve? My last course for Pluralsight was Build a Better Blog with a Static Site Generator, where I show you how to do just that. If you go that route, your text editor becomes vitally important to your blogging, it can really make or break your experience of writing and markdown. Let's take a quick look at the source for my blog, so I can show you the pain point. The posts are here in the _posts directory. I've created one that we can use for the demo called Test Post For Hacking VS Code. The bit at the top is YAML and it has the metadata for the posts like title tags and pub date. Below the three dashes, this is where we write our content and markdown. I'll put a couple of hash signs here, which are going to convert into a heading level two in HTML and then a bit of text. Over at the command line, I'll say hexo server to fire up the no-JS server and we'll take a look at our post in the browser. Nothing terribly exciting, but we can scroll down here and see that I like to start my posts with an image. Back in the editor we can see that I keep my images in folders sorted by year and month. Let's stick this one into our new post. The syntax in markdown is an exclamation point followed by the alt text in square brackets and the URL in parentheses. And there's our image. Personally, I like to put my images inside an HTML figure tag with a figcaption like this. Here's the image again, but this time it has a caption. That's great, but here's the problem. It's a pain to type in all that boilerplate. So why not just use a snippet? Snippets are great for completely static text, but it sure would be nice if it could fill in the current year and month for us. This is a job for an extension. Coming up next, we'll create the project and save it to GitHub.
Create the Project
Define Our Functions
Insert File or Image Links
Write Unit Tests
Interactively Build and Insert Figure Tag
Now that we've finished writing and testing our first real function it's time to make one that's a bit more complex. This time our function is going to guide the user through a series of questions wizard-style to fill out an HTML figure tag with a caption. Let's just dive right in. So what does a figure tag need? Obviously we need an opening figure tag, and we'll add a class L on it, but leave it empty for now. Next is the image link itself in markdown format. Then we need the figcaption to hold the caption. We'll close the figcaption and the figure tags and then we're done. I'll just turn this whole thing into a constant string. Let's scroll down to our figureDisposable and get rid of the show information message. For our first step, let's just send our new constant to the insertText method. I'll check that in the debugger and there it is. Now we can start fleshing this out. Since we need an image, let's call the getImageTemplate method we created earlier. We'll pass that template text into the updateTemplateWithDate method next. We're going to be chaining together several calls, so I'll create a figOptions object to hold the result of each step. I'll add a path property to hold the template that's been updated with the date. Now I'll add an ImageName property. We need to ask for the file name so let's call showInputBox and give it a prompt of Image File Name. Since each of these show methods return a promise wrapped up in a thenable top, we'll add a then to handle the results. It's worth taking a moment to talk about thenable. Essentially, Microsoft has created an abstraction to wrap around different kinds of promises. You probably expect ES6 promises here, but since work on the Monaco Editor predates ES6 promises, it was based on WinJS promises instead. The biggest difference you're going to notice is that you can't chain a .catch at the end of your chain of thens. This may change in the future but for now you need to handle exceptions inside of your thens. If you aren't familiar with promises at all, you just need to think of them as callbacks. Instead of nesting a bunch of callbacks, we can use .then to write the code that we want to execute when the previous step is complete. Since showInput box here is returning a promise itself, we can call the .then method on that promise. This means that we can asynchronously handle the results. So let's assign the figOptions imageName to the value returned by the input box. We'll add another then so that after that step we can call insertText and pass in the figOptions imageName. Let's test that. I'll hit shift control F and now I'll type in test and hit enter. There's our text, so we're good so far. Now let's make a new function that takes in the figOptions and replaces the right bits of our figure template. I'll say we want to replace the text dollar sign figOptions dot imageName on the figOptions that we just passed in. Once we've done that, we'll return the new figure text. We need to put that replaceable text in our figure. I'll copy it and then up here we'll paste it in in place of the slash images. Before that I'll put in dollar sign, open curly brace, figOptions.path closing brace. Back down in our fill figure template function, we can add another replacement line. This time we'll replace the path with the one from figOptions. We'll scroll back down to the then where we inserted the text, and replace this with a call to our fill figure template and pass in the whole figOption object. I'll run the test and this time say Test.jpg. So there it is, so far so good. Now we can just chain together the rest of our wizard. I'll put another then here and inside of it I'll put another call to showInputBox. This time the prompt will be Figure Caption. Let's add an alt tag and a figCaption property to the figOptions object. I'll assign the result here to both the alt text and the figCaption properties. We could ask for these separately, but I think that would get a little too fiddly for the user. Now for a super important detail. The code we currently have will not behave the way we want. It will not run through our thens sequentially. If we want that we have to return the thenable return by the showInputBox method. Now we are passing the new thenable to the next then. Think of it this way. The first showInputBox goes off to get the user's text, handles it, and then passes a thenable to the next then. We want the second showInputBox to consume that thenable, and then pass along its own thenable. The InsertText is working from the second showInputBox's thenable. That means it won't happen until the callback from the second showInputBox is resolved. Without the return what we get is the first showInputBox handling its callback, then passing its thenable to the second showInputBox and then onto the InsertText without waiting for the user to input their text in the second box. The result is that the InsertText is called with only the text from the first input box while the UI pops up the second input box and waits for its result. When the user does enter their text, it's too late. It sounds complicated, but once you do a few, you'll get the hang of it and it sure beats a deeply nested bunch of callbacks. Okay so now that we're getting our alt text and caption text, let's modify our figure template to include them. Now let's stick in the figOptions alt text and figCaption. We'll need a replacement line for each of them here. Now I'll stick in the figOptions here. Let's test that. I'll enter the file name and then caption and there they are, perfect. Now we'll tackle the CSS width and alignment QuickPicks. I'll chain another then here. We're going to return the thenable from the showQuickPick and handle its callback. We'll set the figOptions CSS width class to the result. I'll copy this so that we can add it to the figOptions object. Next we'll update the figure template. Since we know that we're going to add both width and alignment, I'll just add one item here called CSS class. For now, I'll just make it the width class. Now we need another replacement line here. Back down here we'll insert an array of strings into the QuickPick with full, half, and quarter width options. I'll add a placeholder with width class. Just a quick test and there's our width class QuickPick. And there's our width class. So let's add our CSS alignment class to our figOptions. This will look just like the width code. I'll put in left and right and call it alignment class. Let's go back up to our fill figure template function and add a space, and then our alignment class. I'll test that. And it looks good. Now we should put these options in our configuration here in the package.json. I'll add a new configuration property called width CSS Classes. I'll give it a type of array, and I'll put in our three widths. For the description I'll say, Array of strings representing possible CSS Classes for width. I'll do the same for the aiignment CSS classes. Next I'll come back over to extension.js and add a new variable called CSS width class list, which I'm going to load up from the configuration we just created. Likewise I'll do the same thing for alignment. So now the last thing we need to do is delete our hard-coded string arrays and replace them with our new variables. Let's do one last test. That looks great. In the next clip, we'll install our extension into our VS code instance so that we can start using it outside the debugger.
Install Our Extension Locally
Now that we have an extension, we can install it locally. Over here in the docks at code.visualstudio.com, we can read about how to install on all three supported operating systems. In all three cases, all we need to do is copy our extension directory into our extensions directory for code. On Windows, you go to your user profile directory and find the .vscode directory, and then the extensions directory in that. On both Mac and Linux, you'll just go to your home directory and go to the .vscode and then the extensions directory. I'm recording this on Linux, so I'll go to my home directory and look for my project directory which I called courses. And then copy the static Site Hero directory. Now I'll go back to my home directory and then into the .vscode directory. There's extensions, so I'll just go into it. Now I just paste the directory I copied. And that's it. Now back to the terminal where I'll cd over to my blog directory in startup code. Here, below the figure that we added by hand, I'll run our command. I'll say BuildaBetterBlogThumbnail.jpg. Then for the caption I'll put Build a Better Blog. I'll pick half width and left alignment. There we have it. Now we've built version one of our extension and we installed it locally.
Wow, we certainly have covered a lot of ground in this module. We defined the problem we wanted to solve, which was making it easier to work with a static website generator. Next, we walked through creating our project, versioning it with Git, and then publishing our project on GitHub. Then we defined the two commands we need, inserting files and images, and then building a figure tag. We built up the file and image link function first, and then wrote a unit test. After that we worked through the function that gives us wizard-like ability to build up a figure tag and insert it. Finally we saw how easy it is to install our extension locally and use it. In the next module we'll learn how to package up our extension and publish it in the official marketplace.
Distributing Our Extension
In the last module, we created a real world extension to help with writing content for a static website generator. In this module, we'll see how to package it up, and publish it at the official extensions marketplace. You can reach the marketplace from code.visualstudio.com, by clicking the Extensions link. Here we can see lots of extensions, themes, and snippet packs that have been submitted. Let's take a look at what one of the extension pages looks like. Up here we have the banner that includes the name and description, plus the basic installation instructions. Down here, we see the documentation. Over here, we have links back to the support page, license, etc. In the next clip, we're going to download the publishing tool, update our README file, add those links we saw, style our banner, add an icon, and finally publish our extension to the marketplace.
Packaging and Submitting Our Extension
This clip is going to move fast, so don't blink. We're going to go from installing the publishing tool, all the way through publishing our extension to the marketplace. If you don't already have one, you'll need to create a free account at visualstudio.com. I'm not going to walk you through creating an account, since it's just a basic website registration. At that site, you'll generate yourself a personal access token that the published tool can use to authenticate you with the marketplace. Once you've generated and copied your personal access token, you'll need to use NPM to download and install the publish tool, which is called the VS code extension manager, or VSCE for short. After you've installed VSCE, you'll use it to set up your publisher account on the marketplace. Let's walk through that process. The first thing we need is a personal access token from Visual Studio Team Services. If you don't have an account, you can sign up for free at visualstudio.com. Once you've logged in, click your name up here, and then click my profile. Here you'll click the Security tab, and then over here, on the personal access tokens area, you just want to click add. The personal access token, or PAT, is going to serve as your credentials when publishing to the marketplace. You will only need this one token to publish as many extensions as you like. It will expire and have t be replaced, but we'll set it to expire in one year. You can put any description you want, but I'll put VSCE, since that's the name of the publishing tool we're going to use. Now I'll set the token to expire in one year, and change the accounts to all accessible accounts. I'll click the Create Token button, and here's our token. I'm blurring it out since I'm going to really use this token, but it's just a randomized string. I'll copy that, and then switch over to the terminal. This is an NPM install, so I'll just type npm install -g vsce. I'll speed up the video here. Now I'll clear the screen, and test to see if it installed. Yep, there it is. The first thing that we need to do here is create our publisher account. I'll just say vsce create-publisher jeffaTech. I think jeffaTech's a pretty friendly name, so I'll just use that. It wants the personal access token, so I'll just paste that in. Now we're good to go. If I were working on a different system and wanted to publish, I could say vsce login jeffaTech. You don't need to login on the same machine you just created a publisher on, so I'll say no here. Okay, so now I'm going to spruce up the project, in preparation for publishing. First, I'll clean up the README.md file. I'll delete the default text, and write up the documentation for our extension. Once we have our README squared away, we can improve our metadata. If you'll remember the metadata lives in the package.json file. The first thing we'll add is a repository property with the type of git, and the URL or our github repository. Next I'll put in bugs, and point that to our github issues page. Now, I'll add homepage, and point that directly at the README file. For the icon, I'll start with images, and then go over here, so you can see the sad little drawing I made. I'm obviously not an artist, but it'll do. Now I'll put in the file name, StaticSiteHeroLogo.png. Next comes the gallery banner. I'll set the background color to a pale blue, and set the theme to dark. That should give us white text on our blue background. We should commit those changes and push the to github. Let's take a look at the github page. I haven't refreshed yet, so there's our old text. I'll refresh, and there's our new version. Now we could publish at this point, by typing vsce publish, but instead, let's do a test by typing vsce package. Now it's created a vsix file. I'll copy that file to the same name with a .zip extension. Not really necessary on Linux or Mac, but for Windows, that would make the next bit easier. I'll tell it to open with the archive manager. Any zip tool would work as well. Here we have a couple of metadata files and our extension folder. Inside there, we have our images directory, extension.js file, license file, package.json, and README files. Before we publish, I'll go back to the package.json file, and change the version to 1.0.0. Now if we package, we'll get the vsix file with 1.0.0 in the name. Let's publish now. I'll just say vsce publish, and it's away. Back on the marketplace page, we can scroll down, and there's our extension. I'll just click it and there's our page. Hmm, the white page is too light against the pale blue, so I'll go change that. Back in the package.json file, I'll change the theme from dark to light, so that we'll get dark text. Now when vsce publish, I can add patch, to tell the tool I want it to increment the version for me. That gives us 1.0.1. You could also tell it major or minor, to increment one of those other levels. It's going to update the package, that json file for you, when you do that. You could just update the version yourself directly in the file instead. Ah, that's much better. I'll check out the links now. They all look good, so we're done.
I hope you've enjoyed this course on building your first extension for visual studio code. We started simple, with some snippets instead of extensions, to see that you don't necessarily have to write and extension for everything. Next we created a simple Hello World Extension that didn't really do much. After that, we created an honest to goodness Real World Extension that helps us write content for a static web site generator. Finally, we published our extension to the official marketplace. If you would like to learn more about using Visual Studio Code, check out John Papa's course, which is simply titled Visual Studio Code. If you want to learn more about Static Website Generators, check out my course, Build a Better Blog with a Static Site Generator. If you publish an extension, be sure to let me know in the comments so I can check it out. I'm Jeff Ammons at jeffa.tech, and CodeCareerAcademy.com. I thank you for watching my course.
Jeff has been passionate about and active in software for over 25 years. Currently he is CEO and Chief Instructor at Code Career Academy (codecareeracademy.com) and runs the Gwinnett Georgia,...
Released10 Jun 2016