DIY VS Code Extension 1: Development
Abe Dolinger
Posted on October 12, 2020
Have you ever wished for a little extra feature in VS Code, and nothing turns up in a search? Fear not. It's time to DIY.
A few days ago I realized I had once again swapped my true/false cases in a ternary expression. It's always a little annoying to reformat it, switch the punctuation, redo whitespace, etc. So I made SwapTernary
!
If you want to try it out, it's here - or search the VS Code Extension Marketplace for SwapTernary
. You can also check out the repo if you just want to see the code.
By my calculations, if I save only 11,000 developers just five seconds each, it will be worth the time investment! Demos below.
Development
Getting Started
Fortunately for us, the VS Code team has made bootstrapping a project a pretty quick process. One thing I will say for Microsoft is that their push towards better documentation really shines in some areas. This is one of them.
Please Stop Working With ICE
A much larger thing I will say against Microsoft - stop working with ICE.
Getting Started, cont'd
Their doc Your First Extension was hugely helpful, and I recommend following it closely. I'll cover the essentials here.
Bootstrap Your Extension
The team have made an NPM package using yeoman that generates a Hello World extension for you, complete with tests. You can install it with the following command in Terminal (assuming you use NPM):
npm i -g yo generator-code
When that's all set, run yo code
from the folder you'd like to have your code in. It offers a nice selection of starter templates:
❯ New Extension (TypeScript)
New Extension (JavaScript)
New Color Theme
New Language Support
New Code Snippets
New Keymap
New Extension Pack
New Language Pack (Localization)
I chose the first option. You'll also be able to enter a name, description, initialize a repo, etc. (Hit Enter to go with defaults - you can always change stuff later.)
Once that finishes installing, open up src/extension.ts
in Code. Hit F5 to start the debugger. A new Code window with your extension installed should appear.
Then you can enter the Command Palette (Mac: ⇧⌘P, Windows: ^⇧P) and type Hello World, or whatever you named it in the last step. You should be able to hit Enter and see a friendly popup.
Let's Write Some Code
To swap a ternary, I knew I would need to edit selected text. The team have graciously compiled a repo with ~50 sample extensions for us to work from. How about document-editing-sample
? That seems promising. For me, it's perfect - an extension that reverses the selected text. The whole thing is below for reference.
// extension.ts
export function activate(context: vscode.ExtensionContext) {
const disposable = vscode.commands.registerCommand('extension.reverseWord', function () {
// Get the active text editor
const editor = vscode.window.activeTextEditor;
if (editor) {
const { document, selection } = editor;
// Get the word within the selection
const word = document.getText(selection);
const reversed = word.split('').reverse().join('');
editor.edit(editBuilder => {
editBuilder.replace(selection, reversed);
});
}
});
context.subscriptions.push(disposable);
}
There's a lot of useful info here.
- Your extension must be wrapped in a function called
activate
, which takes the editor context as an argument. - Your code must be registered by the
registerCommand
command and stored as a variable. - That variable must be pushed to the
context.subscriptions
array, which where VS Code manages active extensions. - You already have all this boilerplate in your new extension.
-
registerCommand
is where we come in. It takes two arguments: a string and a function. Let's talk about the string first.
The Command String
The command string takes the format of <publisher>.<commandName>
. Mine is 256hz.swapTernary
.
The publisher is you. If you don't have an Azure DevOps account yet, don't change it now; we'll cover creating a publisher in the next post.
Enter a commandName
. Use simple, letter-only strings. Now, add the whole command string into your package.json
twice: under activationEvents
and contributes.commands
.
// package.json
...
"activationEvents": [
"onCommand:256hz.swapTernary"
],
...
"contributes": {
"commands": [
{
"command": "256hz.swapTernary",
"title": "Swap Ternary"
}
]
},
Make sure the command string is the same in all three places or nothing will work. This is especially true if you are like me and change the name of your command 50 times.
The title
field above is the friendly name that will show up in the Command Palette.
(You can also add a keybinding here. See my package.json for an example.)
Your Custom Function
Now, the fun
part. Er, the function. This is the entry point for your command.
const editor = vscode.window.activeTextEditor;
This gives us access to the active editor environment.
if (editor) {
const { document, selection } = editor;
Our editor context has a document
and a selection
. Cool. These are not raw text, by the way - they are instances of the Document
and Selection
classes. Each has special methods and properties.
const word = document.getText(selection);
This is how we get our raw text. The Selection
contains pointers to its beginning & end positions in the Document
, and the document takes these and gives you what's between them.
You can perform whatever logic you want on the text now, and all the work in my extension takes place at this step. In the example, they reverse the text.
const reversed = word.split('').reverse().join('');
Then, we use the active editor's edit
method to replace what we have. edit
takes a callback and passes in something called the editBuilder
.
editor.edit(editBuilder => {
editBuilder.replace(selection, reversed);
});
editBuilder.replace
takes two arguments: a Selection
, and our new, raw text. selection
is already good to go - we destructured it out of the editor
above. So all you have to do now is process your text and pass it in as the second argument.
I needed to do a bunch of string manipulation for my extension:
- split the statement up into the condition, true/false clauses, and formatting.
- keep track of whether you're in the middle of a string, so the
?:
characters won't be wrongly parsed as syntax. - keep track of nested ternaries, so we only swap the outermost expression.
- show error modals if the expression can't parse properly.
Happy to answer questions about those if you're interested, or you can check out the repo.
That's all for development! Now, let's publish the extension. On to part 2.
Cover photo: Code by Clement H on Unsplash
Posted on October 12, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 28, 2020