Jon Rumsey

An online markdown blog and knowledge repository.


Project maintained by nojronatron Hosted on GitHub Pages — Theme by mattgraham

Build VSCode Extension

This document will store my experiences learning about, and interacting with the process of building a VSCode Extension.

Table of Contents

Goals

At the end of this experience, I hope to have learned the following:

  1. Minimum requirements to build an extension.
  2. About the development process.
  3. How to test the extension.
  4. Extension publication.
  5. Supporting the extension going forward.

In addition, I am challenging myself to complete the following:

  1. Build an extension that I will use.
  2. Develop short videos and/or sequenced screenshots demonstrating its use.
  3. Publish the extension to VSCode Marketplace.
  4. Add this to my portfolio.

Planning

Learning How To Build Extensions

New Project

Yeoman: Aka "yo". VSCode Extension Generator. Enables creating new:

Yeoman supports NPM, Yarn, and PNPM.

  1. Set up dependencies: npm install -g yo generator-code.
  2. Set up the project: yo code
  3. Respond to questions.
  4. CD to the new project.
  5. Update package.json (see below).
  6. Start coding!

Update Package.json:

Debug in Special VSCode Instance

  1. Save changes.
  2. Press [F5]. This launches the Extension Development Host.
  3. Open a file or target folder location if necessary.
  4. Open the Command Palette (Press CTRL + SHIFT + P).
  5. Type the name of the command as indicated in Package.json as the Command Title.

Reloading After Making Code Changes

  1. Launch the Extension Development Host.
  2. Make changes to your code in the VS Code instance that is in "Run and Debug" mode.
  3. Save code changes.
  4. Open the Command Palette in Ext Dev Host.
  5. Type 'Developer: Reload Window'.

Newly saved code is now running.

Notes While Learning

Build Development Host is a live build+run version of VSCode using the Extension code in the parent VSCode instance from 'Run Extension' (F5) command.

Naming a Command requires editing package.json: 'contributes:commands:command:' and 'contributes:commands:title'. This is different than naming the Extension Project (top of Package.json 'name' and 'displayName' KVPs).

VSCode Commands follow a disposable pattern:

  1. Code within the activate(context: vscode.ExtensionContext) method.
  2. Create a variable as a result of vscode.commands.registerCommand() with the package.json command name followed by a lambda that executes a function or code block.
  3. At the end of the activate() function, call context.subscriptions.push(disposable_variable_from_step2).

Live Debugging is supported! Use breakpoints and view in-execution variable values just like any other code.

Async-Await and Promises are supported example code source.

// the following code was copied on 7-June-23 from github.com/microsoft/vscode-extension-samples
import * as vscode from 'vscode';
import { SampleKernel } from './controller';
import { SampleContentSerializer } from './serializer';

const NOTEBOOK_TYPE = 'test-notebook-serializer';

export function activate(context: vscode.ExtensionContext) {
  context.subscriptions.push(
    vscode.commands.registerCommand(
      'notebook-serializer-sample.createJsonNotebook',
      // ASYNC
      async () => {
        const language = 'json';
        const defaultValue = `{ "hello_world": 123 }`;
        const cell = new vscode.NotebookCellData(
          vscode.NotebookCellKind.Code,
          defaultValue,
          language
        );
        const data = new vscode.NotebookData([cell]);
        data.metadata = {
          custom: {
            cells: [],
            metadata: {
              orig_nbformat: 4,
            },
            nbformat: 4,
            nbformat_minor: 2,
          },
        };
        const doc = await vscode.workspace.openNotebookDocument(
          NOTEBOOK_TYPE,
          data
        );
        // AWAIT
        await vscode.window.showNotebookDocument(doc);
      }
    )
  );

  context.subscriptions.push(
    vscode.workspace.registerNotebookSerializer(
      NOTEBOOK_TYPE,
      new SampleContentSerializer(),
      { transientOutputs: true }
    ),
    new SampleKernel()
  );
}

Cancellation Tokens: The last parameter of a function call (optional). Use isCancellationRequested to check if canceled, or register for notifications via onCancellationRequested. A Cancellation Token will be valid on events that cause an API call to become obsolete. For example: During an API Call the user types more characters, so a new API call could be made with more up-to-date context and data.

Events are exposed as Functions. Use the Listener Pattern (example below) to subscribe to Events. Return a Disposable so the Event Listener can be removed. From vscode api patterns documentation: '...an event fired when the active text editor (noun) has been (onDid) changed (verb)'

// Event Listener Pattern from code.visualstudio.com
var listener = function (event) {
  console.log('It happened', event);
};

// start listening using 'onWillVerbNoun' and 'onDidVerbNoun' terminology
var subscription = fsWatcher.onDidDelete(listener);

// do stuff
// ...

// stop listening
subscription.dispose();

Command WHEN and ENABLEMENT

Commands can be hidden until a when clause returns true, for example: Only show the command when the editor language is markdown:

{
  "contributes": {
    "menus": {
      "commandPallete": [
        {
          "command": "markdowntocer:markdownTOCer",
          "title": "Markdown TOCer",
          "when": "editorLangId == markdown"
        }
      ]
    }
  }
}

When a command is showing (when clause returns true), the enablement allows use of the command, perhaps when a cursor is over a line of text.

Example (from API Guides) of storing a value to use with a when clause to check if number of cool open things is greater than 2:

vscode.commands.executeCommand('setContext', 'myExtension.showMyCommand', true);
vscode.commands.executeCommand(
  'setContext',
  'myExtension.numberOfCoolOpenThings',
  4
);

Localization

Namespace for localization-related functionality is l10n and must be definied in Extension Manifest and have bundle.l10n.json files included. Does not apply to built-in Extensions like GitHub Authentication, Git, some Language Features, etc.

Extension Anatomy

Check out Extension Anatomy Documentation for up-to-date info.

Language Server

Program analyzes project to provide dynamic features.

Example: typescript-language-features utilizes TS Language Service to offer features like Hover Info, Auto Completion, Formatting, etc.

LSP: Language Server Protocol

Language Server Extension Guide

Providers

Testing Extensions

The Workbench can be put into 'readonly' mode to prevent changes to specific files, however files that match cannot be edited in the Editor:

Launch.json

VSCode Documentation on Testing Extensions.

Requirements:

Capabilities:

Electron

Electron is going to be the way to test VSCode Extensions going forward.

Testing Extension in GitHub Actions

When targeting Linux in YAML, ensure Xvfb enabled environment is targeted, otherwise test will fail.

See GitHub Actions subsection in Working With Extensions.

Publication via GitHub Actions

See GitHub Actions Automated Publishing subsection in Working With Extensions

Publishing

Define the publisher property in order to enable managing an Extension.

Publishing can be done 2 ways:

Packaging tool: vsce the Visual Studio Code Extensions packaing and publishing tool.

  1. Update the README.MD file (make it yours, make it stellar).
  2. Run npm install -g vsce at the root of the project.
  3. Open a MSFT account using Azure DevOps.
  4. Create an Org in Azure DevOps.
  5. Get Personal Access Token(s).
  6. Set "All accessible organizations".
  7. Set show all scopes and select "Marketplace: Manage".
  8. Copy the Personal Access Token to a safe place.
  9. Click Create.
  10. Create a publisher using Manage Publishers & Extensions page in visualstudio.com.
  11. Add name and ID and Create to make a new publisher. This must be unique in the Marketplace.
  12. Login as the Publisher using vsce login {publister_name_from_step_11} and the Publisher Token captured previously.
  13. Specify the Git Repository field in teh package.json.
  14. Specify the publisher field in the root of package.json: { ..., "publisher": "{publisher_name_created_previously}", ...}.
  15. Publish the extension using vsce publish.

References

VSCode Extension Samples

VSCode Extensions API

Return to Conted Index.

Return to Root README.