What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
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
Description
Transcript
Exercise files
Discussion
Recommended
Introduction
Introduction
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
If the only tool you have is a hammer, then everything looks like a nail. Sure you can pound a screw into wood, but you'll lose the strength the screw gives when used properly. Odds are good that if you use Visual Studio, you see it as the One True IDE. There's no doubt that it is a fantastically productive tool, but it isn't perfect. The analogy I use from woodworking is that Visual Studio is the super duper multi-purpose machine that includes a table saw, lathe, drill press, router and sander. This fancy machine can be fantastically productive, but even so, you still need some specialty tools, like a good set of chisels. In experienced hands, the difference can go from good furniture to great. That's why even though I use Visual Studio for most of my work, I keep a good set of text editors on hand for specific uses. Experienced woodworkers are constantly building tools that help them. The same is true for programmers. I like to use tools that I can modify and extend to help me be more productive. In this course, we'll be looking at Microsoft's new cross platform text editor, Visual Studio Code. The first thing to understand is that this is not a cross platform version of Visual Studio. The history of VS Code starts with Microsoft's browser-based programmers editor called Monaco. Monaco was created for use on Azure, Visual Studio Online, and some of their other online services. Monaco is a JavaScript text editor that gives you the colorcoding formatting and intellisence you expect in a good text editor. Monaco became a desktop app thanks to GitHub's Electron. Electron is a native shell for Windows, Mac, and Linux that hosts JavaScript-based apps like Monaco. Put Monaco and Electron together, and you get Visual Studio Code. Not only does VS Code have some solid features, it can easily be extended with JavaScript or TypeScript. To get the idea of the kinds of extension that have been published, you can go on out to code.visualstudio.com and then click on the Extensions link. As you can see, there are add-ons for different programming languages, then emulation tools, debuggers, formatting tools, and a few odd ones like a pomodoro timer that works inside the editor. Other than this course, if you want to dig deeper into writing extensions for VS Code, you can check out the documentation at code.visualstudio.com/docs. Once you've watched this course and read through the docs, I'd recommend going through the Marketplace. Most of the extensions there are hosted on GitHub so you can directly check out how they work. Of course, once you start publishing your own extensions to GitHub and to the Marketplace, you will be advertising your skills to potential employers. Hopefully by now, you're ready to take the plunge, and start building your own extensions you can share with the VS Code community. So let's dive right in.
Summary
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.
Starting Simple
Introduction
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.
Basic Configuration
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.
Snippets
Now it's time to look at snippets. Snippets are common to many text editors and allow you to insert boiler plate code with just a short trigger. Let's start with an example. I've created a simple Node app that does nothing, just so that we have some JavaScript to work with. If you've never worked with Node, don't worry. We're just looking at plain JavaScript. We'll start code in the current directory. Under App Controllers, we'll look at the home dot js file. Here in this router dot get method, we want to log to the console that a request has come in. Since we're probably going to make lots of console dot log calls, it would be handy if we didn't have to type the boiler plate each time. Let's go to File, Preferences, User Snippets. Now we'll just select JavaScript. The documentation is good here and the example is exactly what we're looking for. I'll copy the example bit and paste it down here, then save. We'll take a closer look in a moment but for now just notice that you have a prefix of log, and in the body we have the console dot log call with a couple of numbers, prefixed by dollar signs. Back over in the home dot js file, I'll jump in here and type log. You can see how it just popped up with the suggestion I want. I can either hit Tab or Enter and it drops in the console dot log call with my cursor positioned inside the quotes so I can just start typing. Once I type my output text I can hit Tab and it will take me to the line following the call. I can keep tabbing and switching between the two locations which correspond to those two numbers with dollar signs that we saw. Okay now we know what a snippet is and how to trigger one, so let's take a look at the anatomy of a snippet. In the code editor, a snippet exists as a json object with three properties: a prefix, a body, and a description. The prefix is the trigger. In the example we saw log was the prefix so we just typed log to trigger the suggestion to pop up. The description shows up in that popup to help us decide if this is the right snippet. The working part is the body, which is defined as an array of strings with each string being a line in the snippet output. Let's talk about what can go inside that body. Here's the log snippet again. You can see that it is two lines. The first line is the actual method call plus the dollar sign one. The second line has nothing but a dollar sign too. Like many text editors, code uses the textmate snippet format, which defines the dollar sign followed by a number as a tab stop. That's why we were able to tab between the two locations, tab stop one and tab stop two. In this case, tab stop one lets us type in the actual message we want logged, and tab stop two just moves our cursor to the line after our method call. Okay, let's say we create a more complex snippet to see what else we can do. Let's say we want to log something only if a variable exists. We'll say if foo, then console log logging response for foo. Then else, and then console log unable to log because foo is null. I'll copy this and we'll flip back to our JavaScript dot json file. Now put a comma here and start our new snippet. Let's call it print to console if variable exists. Let's stub it in with a prefix, body, and description properties. We fill them in with a prefix of log if and a description of log output to console if variable exists. For the body we'll add a couple of lines and paste in our code. The first thing we have to do is a bit tedious. We have to convert this to an array of strings by surrounding each strings with quotes and then following it with a comma. Now we need to escape out these internal quotes. I'll replace this foo with a dollar sign one so we can just type in whatever variable we want to check. Just like our original example, I'll put a dollar sign two at the bottom so we can tab to the end and keep typing when we're done. Since we had foo in three places, let's replace the other two with the dollar sign one as well. Let's have a look at what that gives us. If I starting typing log, we can see now that both log and log if show up. I'll keep typing until log if is the only choice and then hit either Tab or Enter. We'll use the articles variable up here so I'll just type articles. See how it replaced foo with articles in all three places? That's pretty handy. If I hit tab a few times, you can see that's it's jumping between the articles and the end. Here at the end I can hit Enter and I'll be able to continue on with my typing. That's pretty good but it's still not perfect. Let's go back over to our snippet definition and here at the first dollar sign, let's wrap that one with curly braces, then put a colon and variable name here. Save it and then jump back to the home dot js file. Delete this and we'll try again. This time it says variable name in each of the three places where we had our dollar signs. If we switch over to the style dot css file and type log, you can see that our snippet does not show up. This is cool because we can define snippets that are specific to a type of file we are editing. Writing our own snippets is cool but we can also install snippet files from the marketplace. If we code to code.visualstudio.com and click the extensions link, we get to the marketplace, where we can find new themes, extensions, snippets, and more. If we click on one of them, we'll get a page with instructions and links. To install one, we can use the Ctrl + Shift + P or the Cmd + Shift + P, or F1 and then type install extension and hit Enter. That's going to download a list of extensions, snippets, et cetera and we can find the one we want. Let's pick this JavaScript one. We have to restart for it to be loaded. On the instruction page, we can see that one of the triggers is Ime so let's try that. Cool, that worked. The last thing we need to look at is where are our snippet files stored. Since Visual Studio code is a cross-platform tool, we need to see where they are stored on each supported operating system. We'll start with Windows. On Windows, we start with our user directory and look for App Data, the Roaming, then Code, then User, and snippets. On the Mac, we have to look in the hidden Library folder in our home directory. From there we go to Application Support, then Code, User, and snippets. On Linux, we start again in our user directory and go to dot config then Code, User, and snippets to see our snippets files. Extensions and snippets we install from the marketplace haver are stored in a dot vs code slash extensions directory in the home directory of each of these operating systems. So now we've seen how to change our basic configuration settings and write some snippets. We've seen where the snippets live on all three of the main desktop operating systems. Wouldn't it be cool if we could just sync up our settings and snippets across all the places we run code? Stick around for the next clip and we'll do exactly that.
Syncing Settings
Now that we know how to customize our settings, and both create and download snippets, let's learn how to sync them up everywhere that we use Visual Studio code. In the marketplace, we can scroll down to the other section and click See More. If we scroll down a bit we can find Visual Studio Code Settings Sync by Shan Khan. If we click on that we see the info page. The gist here is that we'll use a GitHub gist to store our settings, snippets, and extensions. The first thing we'll need to do is get a GitHub account token. Here on the GitHub site I'll click my avatar dropdown and pick Settings. Over here you see personal access tokens. Click that and on the next screen click the Generate New Token button. We can give this token a name to help us remember what it is. Let's follow Shan's example and call it vs underscore code. We'll click Repo and Gist, and then the Generate Token button. There we have our token. This is our one and only chance to see it so let's copy it. Now we'll head back over to code. Ctrl + Shift + P or Cmd + Shift + P to open the command palette and type install extension. Now just type sync and select Visual Studio Code Settings Sync. A quick restart, and we're in business. Now we need to press Shift + Alt + U and paste in our GitHub access token. Hmmm, it assumes we haven't created it yet and opens GitHub for us. Once we enter it, it will create a new gist for us. As you can see, the gist is a secret gist so it won't be searchable. An important note here is that a secret gist can't be searched but it is not private. If someone figures out your gist ID, they can see your gist. What that means is don't put anything private in your settings that are synced. For example, you might want to create a snippet that puts a secret key under your code. I wouldn't do that. Shan is adding to his roadmap the ability to sync to a private repo, but remember it is an open source project and he would welcome a pull request if someone can get to it before he does. Now let's sync those settings to our Mac and Linux machines. Installing the extension is the same in each environment so I won't show that step. I like to use a different access token for each system so let's create one for our Mac. Now in code I'll open the command palette, type in sync, and select Download Settings. It forwards us over to Safari but since we just copied our token, I'll switch back and paste it in. Now we need our gist ID since we are downloading from an existing gist and not creating a new one. We can grab that ID from the embed tag here then paste it in and hit Enter or Return. All of our settings have been downloaded. We'll verify that by looking at the snippets for JavaScript. And there is the snippet we created. The process on Linux is identical so I'll just speed through it here. And that's it. Now we can sync up our settings across Windows, Mac, and Linux.
Summary
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
Introduction
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
Let's go get the tools we'll need. The first two tools we need are Node.js and npm. These are common enough that I'm not going to walkthrough how to install them. If you don't have them however, just head over to nodesjs.org. They come together, so you'll only have install. The next tool we need is Yeoman which you can find at yeoman.io. Yeoman calls itself the web's scaffolding tool for modern webapp. So you can think of it as a factory for producing the basic skeleton of a project. If you're a Visual Studio user, just think of File New Project. The last tool we need is Yo Code which is generator plugin for Yeoman that can give us the starter project for an extension. Let's start by installing Yeoman. It's as easy as npm install dash g yo and waiting a minute or two. Now, we'll just clear the screen and then install Yo Code by tapping npm install dash g yo space generator dash code. Now that that's done, we just type yo code to create a new project. You get a little menu down here that you can navigate using the up and down arrow keys. We can pick TypeScript or JavaScript here. Since I'm guessing more people are familiar with JavaScript, I'll pick that. You can pick whichever you like. Now we just give it a name, I'll say Hello Word. We need an identifier, it's suggesting hello dash world. That's fun so I'll just hit enter. Next, we need a description. I'll type simple extension that says hello. Now it wants a publisher name. I did a sample off camera so it remembers that I used jeffa. That's fun, so I'll just enter. The last thing it will ask if I want a git repo, that's totally up to you but I'll say yes. Here's a list of the files it created. Now it's running npm install force to pull down any node module specified in the package dot json file and it's done. You can see it's telling to cd into the hello dash world directory it created then type code dot to load the whole directory in the code as a project. That's exactly what we're going to do in the next clip.
Tour of the Hello World Extension
Let's follow the instruction geo code gave us. We'll see the end of hello dash world and type code dot. We're not going to go deeply into anything in this clip, this is just a tour of what directories and files we get by default. We'll start at the top, up here we see there's a dot vs code directory. If you're familiar with the unix approach you'll know that starting with the dot implies that something is hidden. In this directory, we have one file, launch dot json. This file is used to launch a separate instance of code for debugging our extension. Next we have a node underscore modules directory, if you use nodes, you know that this where npm restores any dependencies. One important subdirectory to know about here is the vs code directory that contains vs code dot d dot ts. This is the library we need to make an extension. What you want to remember for now is that this is where you'll go find out what methods and objects are available to you. There's some documentation here to help you out as well. Next is our test directory, we'll take a closer look here in the next module but for now, just know that this is where you can put your tests. The last directory is typings, this is a type script folder that contains type declaration files for JavaScript libraries. That leaves us these files that are directly in the hello dash world project directory. Dot git ignore just tells git not to put the node modules directory into source controls since npm can restore all those dependencies for us from the package dot json file down here. You can see that the package dot json file contains metadata about our project including this one dependency on the vs code module. Npm restores that and all of the packages that it depends on. The dot vs code ignore is like the dot git ignore file but this time, we're telling vs code itself to ignore patterns of files and directories when publishing the extension. Next we have extension dot js, this is the heart of the extension. It's where we're going to start writing our code. Let's take a quick tour of this file. Right off the bat, we load the vs code module into the vs code variable. Next we have a function named activate. This function is called whenever something triggers our extension for the first time. Code doesn't load extensions on start up, only when they're needed. The default gives us an example here of writing to the console log to let us know that our extension was loaded. Next, we have to register to the function that will do the actual work of our extension. You can see here that we're registering the function as a command called extension dot say hello. The only action this function performs is showing an information window where the text, hello world. Here, we push the registration into an array of subscription on the context object that was passed into the activate method by vs code. After that, we export the activate function. The last thing in the file is an empty deactivate function that is created and then exported. If we need to do any cleanup when our extension is deactivated, we can do it in here. That's it for the extension dot js file, we'll spend much more time here in the next clip. Js config dot json is where we set compiler option like which version of JavaScript we want to use. Last, we have read me dot md and vsc dash extension dash quickstart dot md. You can see the suggestion here to use the mark down viewer. We'll do that with the quickstart file. There's some good info in here so you should give it a read through and that's it for our high level tour of the project structure. Next, we'll start playing with some actual extension code.
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.
Summary
This module gave us a taste of extension building. We started by installing Yeoman and using it to create the bare bones of a hello world extension. Then, we walked through that extension and learned the important files and directories. We learned that metadata about our extension goes in the package dot json file along with any dependencies npm should restore. We also learned that this is where our command title mapping goes. We saw there are actual extension code goes into the extension dot js file and that we can specify which JavaScript version we want to use in the js config dot json file. In the code we saw that the vs code node module gives us access to vs code. We saw how to use it to show messages via the code called vs code dot windows dot show information message and how to work with the content of a file via the vs code dot window dot active text editor. In the next module, we'll use this knowledge to begin building real world extensions.
Building a Real World Extension
Introduction
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
We create the project exactly the same way we did our earlier one. We start with yo code and then pick JavaScript. I'll name this extension Static Site Hero and accept the identifier that it makes. Next I'll just put in a description. We can fix these up later. For the publisher name, I'll use my website, which is jeffa.tech, or not. Let's try it without the dot. Okay, it likes that. This time we definitely want to get repo. Now it's doing the NPM install for us. No point in watching that so I'm going to skip past it. Let's start with a git status. Now, git add dot to stage all the changes. Next we'll do git commit dash m Initial Commit. Now I'm going to go over to my GitHub account and create a new repository. I'll name it Static Site Hero and give it a description. Down here I'll pick the MIT license. You can pick whichever OpenSource license you like. And here's our repo. I'll just copy the address here and go back to the command line. Let's say git remote add origin, then paste in the URL. We'll check it with a git remote dash dash verbose, and there's our origin. Now I'll say git push origin master and then tell it yes, we want to connect to the repo. And it fails. This is a very easy mistake to make with git and GitHub. Since I had GitHub add the license file for me, I can't just push my version up. First I'll need to do a pull to get that license file. So we'll say git pull origin master. It does its thing and then throws us to the text editor to add a commit message. Since it merged the license file in, we need to commit the merge version before we can push it back. I'll just tell it what we did then save and exit. This may be a bit different for you depending on your operating system and configuration. Now I'm going to do a git push origin master and then we'll take a look on GitHub. There's our starter project. So now that we have a project and have pushed it to GitHub, let's define our functions.
Define Our Functions
Now let's define the functions we're going to need. We want one that inserts markdown code for either an image or a file link, and one that walks us through the process of setting up a figure tag. Before we do anything else, let's add used strict up here so that JavaScript will help us avoid doing something dumb. If you want more info on strict mode, check out this link. Now we'll go over to the jsconfig.json file and tell it we want ES6. Back in extension.js we'll scroll down to our hello world function. We'll change Disposable to helloDisposable, then copy it and paste it down here, where we push it into the context's list of subscriptions. Since we said we want to us ES6, let's change the vars to lets. Now let's stub in our functions. We'll start with let fileLinkDisposable equal vscode commands register Command extension.insertLink. I'll put a fat arrow here and leave the function body empty for now. We need to push that disposable into the subscription list now. Next we're going to do the same process for the insert figure function. We want to make so-called tracer bullets here, so we're going to add a show information message to each of these functions. The idea of a tracer bullet is working code that cuts through all the layers of an application early so you know if you're on the right track now, and later you'll know if you've gone off the rails. In this case, if we call our functions and get our information messages, we know we've wired things up correctly. We do that wire up over here in the package.json. Let's scroll down to the contribute section and find the commands array. I'll put in a new empty object and then another one. They both need a command and a title. The first command will be extension.insertLink and the title will be Insert Markdown Link to File or Image. The next command will be extension.insertFigure and the title will be Insert HTML Figure. Now we need to add the activation events. On command extension insert link and then on command extension insert figure. Now we'll hit F5 to start the debugger and test our tracer bullets. I'll just open the command bar and type insert. I'll pick Insert HTML Figure and there's our message. Now I'll do the same thing for the link and there it is, awesome. Since we know our functions are wired up correctly, let's stop the debugger and head back over to the package.json file. Let's go down here to the ends of the commands array, and insert the new section. This will be keybindings, and it's also an array. I'll put in an object and give it a command property that's the same as our link command. Now I'll tell it we want to call the command when the user hits the key combination shift control L. And I'll add a mac property now and set it to shift command L. Let's tell code to listen for this combo when the editor has text focus. I'll do the same thing for the Insert Figure command and make it shift control F and shift command F. I'll start the debugger now so we can test them. Let's try shift control F and that worked. Now for shift control L, looks like that worked too. Next we'll start fleshing out those functions.
Insert File or Image Links
It's time to tackle our first function, inserting a file or image link. I'll start by getting rid of the boilerplate hello world function that yo code gave us. Now I'll go over to package.json and take it out of the activation events and commands. We'll scroll on down to the bottom of the extension.js file. Since both our functions need to insert text into the editor, I'll start with a function to handle that. Let insertText, and we'll pass in a parameter called value for the text that we want to insert. Now a fat arrow and curly brace. Stick a semicolon down here. To keep things simple I'll pull the active text editor into a variable called editor, then we can just check to see if something went wrong. If so, we're going to show an error message and then just bail on the method. Now let's grab the current selections out of the editor. We can create a range object based on the current selection's start and end. On the editor, let's call the edit function, and pass in an anonymous function that takes a parameter called editBbuilder. In the body of the function, let's take our editBuilder and call it's replace function and pass in the range we defined and the text that was passed into our insertText function. That's pretty simple and now we should be able to call it from both our commands. We'll test that by coming back up here to the insertLink command. After showing the information message I'll call our insertText method and pass in Insert File Link. Let's fire up the debugger and call the command. There's the message and the inserted text. So even though it worked, VS code is grumpy about the fact that we're calling the insertText function before it is defined. Since red squigglies make little kittens sad, let's move our function up to the top of the file. Now we can get rid of the information message. We have two kinds of links, so let's define them here in this array called LinkTypeList. Now we can add a QuickPick so the user can select whatever they want. We'll call VS code window showQuickPick, and then pass in the array. Let's also pass in an object with a property called placeHolder that we set to link type. Since QuickPick returns a promise we can chain a .then to process the result. Now inside this function we can call our insertText function and pass in the result of the QuickPick. Let's get rid of this test insert. Now to the debugger to test. When I hit shift control L now I get the popup QuickPick list. I'll pick image and there it is. Okay, here's a good thing to look at. See how the git icon says we have two changes pending? I'll click it and now we can enter a commit message and click the check mark. Let's comment out the insert here. I'll put some logic in here. I'll make our options, if result equals file then else if result equals image. Now let's insertText File Selected here, and insertText Image Selected down here. Let's give that a try on the debugger. That works just like we expected. Back up here just below our insertText method, I'm going to put a couple of new functions for returning the image or the file URL templates. Now we'll replace the text we're returning with our getFileTemplate and getImageTemplate calls. Let's test it. Look's good. This is nice, but what if you don't want to put your files in the same directory as I like to put my files? How about if we pull our templates out of configuration settings? We do that back over here in the package.json file. Up at the top of the contribute section, I'll add a new property called configuration. We get an IntelliSense popup that fills in a sample configuration object for us. I'm going to add a type here and set it to object. Next we'll give it the title Static Site Hero Configuration. The properties work differently than the commands and keybondings. Instead of an array we're going to have a set of named properties. We'll start with static Site Hero Image Path Template then static Site Hero File Path Template. Remember that we prefix these with static Site Hero. We're going to need that in just a minute. I'll give this a type of string and then set the default values to image, then year and month as placeholders we plan to replace. The description will be specifies the folder containing images. I'll just copy this and paste it down here. Now I'll replace the word images with files in the default and description. We can replace the text we're returning here with a call to VS code workspace get configuration. We'll pass in the prefix I said to remember, static Site Hero, and then give it the key of the item we want, in this case, ImagePathTemplate. I'll copy and paste that, then change image to file. Another quick test and we see that we're getting what we want. I'll just commit these changes as well. Let's go back to the debugger and I'll show you something. If we go to file, preferences, and then user settings, we can scroll down to the default settings until we find the ones we just added. We can override the defaults over here. If you remember in my blog, I stick my images in files under a directory called content. I can add that here without changing the defaults. Now let's see what we can do about replacing the year and month in the templates. I'll create a new function called update template with the date and we'll pass in the template here. We need a new date object called today. From that we can get the year by calling get full year. We want the month to have a leading zero if necessary, so I'll start with a text zero plus today.getMonth, then add one since JavaScript months start with zero, and then slice the resulting string and take the last two digits. In other words, if we have the month of one, it coerces the one into a string and concatenates it with the zero, then takes the only two characters. On the other hand, if we had the month of 10, it adds the zero and we get zero one zero. Now when we slice off the last two characters, we get 10. Since we have the year and month strings now, we can just call a replace on our template that was passed in, then return the new template string. Back in our QuickPick handler, we can call our update template with date, and pass in the get template functions. A quick test shows that we're getting what we expect. All that's left now is to concatenate the markdown syntax for a link here, and an image down here. Let's go test that. Bingo. Now we can just type in our file names. In the next clip, we'll write our first unit test.
Write Unit Tests
Good code is only good if the tests pass, right? When we start a project with yo code, we get a sample test by default. Over here in the test directory, we have extension.test.js. I'll close this up to give us some more space. You can see that it loads the assert library, the VS code library, and down here it loads in our actual extension. The actual tests go here in this suite method. We can add our own suites and we can add tests to suites. When we add a test, we give it a name and a function. You can see here that this test calls something one asserts that these values are equal. Since the array doesn't have a five in it, the function returns a negative one, which is equal to this negative one. So the assert is true. Let's run the tests. Now it's not obvious what you do here. You click on the debug icon and open up this panel. I'll pull this over so we can see better. Up here where it says Launch Extension, I'll click the dropdown and you can see that the other option is Launch Tests. I'll select that. Now we can click the debug button or hit F5. It opens up the debugger just like before. If we switch back over to the main instance, we can see the test results over here in the debug console. Let's make a new test called Update Template with Date. I'll start by making a template to pass into our function. Next I'll say Let updatedTemplate equal myExtension.updateTemplateWithDate, and then pass in the template. Now I'll assert that the updateTemplate isn't null. When I run that, I'm going to get an error. See down here it says block-scoped declarations aren't supported outside of strict mode. Let's add that up here. Just automatically adding it is a good habit to get into in JavaScript. So now it should work, right? Well, not yet. It says updateTemplate with date isn't a function. You know why? Cause we didn't expose it by exporting it. Let's add that in here. Exports updateTemplate with date equals updateTemplateWithDate. Now if we run it we do have success. What if we needed to debug the test? Let's set a break point here and then run the tests. Unfortunately we don't really see much or indication here of what's going on, but if we alt tab over to the main instance, we can see that we are indeed stopped at the break point. I'll hover over this variable, and we get a popup with the contents. So let's go back to our test. One thing we can add here is a message to write in the log if the assert fails. We'll say updatedTemplate was null. Now we know that it isn't null, but we can be a bit more specific than that. I'll create a control template to test against, and then go get this bit of code from the function and paste it in. Now we'll just replace Template with controlTemplate throughout here. Let's comment this out and then add a new assert. Assert equal and we'll pass in our updated template and control template. We'll make a helpful farrier message too. UpdatedTemplate not correct, expected, and then I'll add in the control template, and then I'll add but got and the updated template. Now we won't just get an error message, we'll know what was returned. We'll run the test and we get two passing tests. Let's make it fail. I'll add a step to make the control template be bogus template. Now when we run the test, we see that it expected bogus template but got images/2016/04. I'll just clean up the commented out bit and get rid of the default test. This is just a simple example test but now you know the basics and can begin adding tests to your extensions. Next we're going to dig in to a much more complex function.
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.
Summary
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
Introduction
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.
Summary
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.
Course author
Jeff Ammons
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,...
Course info
LevelIntermediate
Rating
(33)
My rating
Duration1h 11m
Released10 Jun 2016
Share course