I always wanted to try creating a VS Code extension since it’s fantastic to have everything in one place: VS Code.

Starting the Extension Link to heading

From Getting Started, we can use npx to generate a template directory for the repo.

npx --package yo --package generator-code -- yo code
# ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? myapp
### Press <Enter> to choose default for all options below ###

# ? What's the identifier of your extension? myapp
# ? What's the description of your extension? LEAVE BLANK
# ? Initialize a git repository? Y
# ? Which bundler to use? unbundled
# ? Which package manager to use? npm

# ? Do you want to open the new folder with Visual Studio Code? Open with `code`

To run the extension, we need to do the following:

  • Open extension.ts
  • In the command window (CTRL-P), use Debug: Start Debugging.
  • In the new code window, try “Hello World” in the command prompt.

Create Commands Link to heading

The command runTest can be registered with registerCommand and passed to context.subscriptions(). The command can be run with CMD-SHIFT-P and choose runTest. The command uses showInputBox to get the test name, then does something with it and finally shows a warning with showWarningMessage.

	const runTest = vscode.commands.registerCommand('myapp.runTest', async () => {
		const testName = await vscode.window.showInputBox({
			prompt: 'Enter the test name to run (e.g., test_my_feature)',
			placeHolder: 'test_my_feature'
		});
		if (!testName) {
			vscode.window.showErrorMessage('No test name provided.');
			return;
		}

    // Do something here

  	vscode.window.showWarningMessage('Done running test');
	});

	context.subscriptions.push(
		runTest,
		outputChannel
	);

Add Commands to package.json Link to heading

Now we have a functional extension. The first step is adding the sidebar to package.json. The first section is used to add new commands:

  "contributes": {
    "commands": [
      {
        "command": "myapp-vscode.helloWorld",
        "title": "Hello World"
      },
      {
        "command": "myapp-vscode.runTest",
        "title": "Run Test"
      },
    ],

UI and Sidebar Link to heading

The next step defines the container and view that would be picked up by TypeScript:

    "viewsContainers": {
      "activitybar": [
        {
          "id": "myapp",
          "title": "myapp",
          "icon": "resources/icon.png"
        }
      ]
    },
    "views": {
      "myapp": [
        {
          "id": "myappSidebar",
          "name": "myapp",
          "icon": "resources/sidebar-icon.png"
        }
      ]
    }

and back in extension.ts, we define myappTreeDataProvider which works with VS Code by registering emitter events and defining getTreeItem and getChildren. In the snippet, there are _onDidChangeTreeData and onDidChangeTreeData:

  • _onDidChangeTreeData is a private event emitter that only the extension here can call .fire()
  • onDidChangeTreeData is public for anyone to listen to.

getChildren defines one child here which is the command we defined above myapp-vscode.runTest. At the end, we create an instance of the provider and hook it into the myappSidebar defined above.


	function createCommandTreeItem(label: string, commandId: string, title: string): vscode.TreeItem {
		return Object.assign(new vscode.TreeItem(label, vscode.TreeItemCollapsibleState.None), {
			command: { command: commandId, title }
		});
	}

	class myappTreeDataProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
		private _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | undefined | void> = new vscode.EventEmitter<vscode.TreeItem | undefined | void>();
		readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | undefined | void> = this._onDidChangeTreeData.event;

		refresh(): void {
			this._onDidChangeTreeData.fire();
		}

		getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
			return element;
		}

		getChildren(element?: vscode.TreeItem): vscode.ProviderResult<vscode.TreeItem[]> {
			if (!element) {
				return [
					createCommandTreeItem('Run a test', 'myapp-vscode.runTest', 'Run one test'),
				];
			}
			return [];
		}
	}

	const treeDataProvider = new myappTreeDataProvider();
	treeDataProvider.refresh();
	const treeView = vscode.window.createTreeView('myappSidebar', { treeDataProvider });
	context.subscriptions.push(treeView);

	// Refresh test list when the active editor changes or is saved
	vscode.window.onDidChangeActiveTextEditor(() => treeDataProvider.refresh(), null, context.subscriptions);
	vscode.workspace.onDidSaveTextDocument(() => treeDataProvider.refresh(), null, context.subscriptions);

[1] https://code.visualstudio.com/api/get-started/your-first-extension