DEV Community

Cover image for DIY VS Code Extension 1: Development
Abe Dolinger
Abe Dolinger

Posted on

DIY VS Code Extension 1: Development

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.


Tools by Barn Images on Unsplash

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.


preserves formatting
supports nested ternaries (not that you should)

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.


Boots by Oziel Gomez on Unsplash

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.


Editing by Kelly Sicema on Unsplash

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.

  1. Your extension must be wrapped in a function called activate, which takes the editor context as an argument.
  2. Your code must be registered by the registerCommand command and stored as a variable.
  3. That variable must be pushed to the context.subscriptions array, which where VS Code manages active extensions.
  4. You already have all this boilerplate in your new extension.
  5. registerCommand is where we come in. It takes two arguments: a string and a function. Let's talk about the string first.

Phone by Quino Al on Unsplash

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.)


Blueprints by Sven Mieke on Unsplash

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

Discussion (0)