Originally posted on my blog:
www.bmpi.dev/dev/vscode-…

VSCode introduction

VSCode is an open source code editor developed by Microsoft based on Electron. Electron is a Chromium-based project that can be used to develop native Node.js based applications. VSCode uses the Blink typography engine to render the user interface. Although based on the Electron framework, it is not a copy of Atom. Code is made by the editor core of “Monaco”, the same as Visual Studio Team Services.

In the 2019 Stack Overflow developer Survey, VS Code was considered the most popular development environment for developers, with 50.7% of 87,317 respondents claiming to be using VS Code1.

Feature Overview

Visual Studio Code feature overview

Context View

Context view diagram

Module Organization

Module organization

VSCode Architecture

VSCode architecture

Process structure of VSCode

Came out from the above we can see and debugging/plug-in rendering process are isolation, through RPC calls, this ensures that VSCode when installing many plug-ins can be launched at breakneck speed, which is very important for the user experience, because the start slowly makes people anxiety (think of the IDEA and Emacs in plug-in more time to start moving speed).

Emacs Architecture

EMACS Conceptual Architecture

After analyzing VSCode’s architecture, we can also take a look at the architecture of Emacs, an ancient artifact that has extremely powerful extensibility capabilities and is known as an editor masquerading as an operating system (just one kernel away).

In the world of Emacs, user input is sent via terminals to the Command Dispatcher component, which works with the Lisp interpreter to process user input and then invokes the underlying Display Processer and Primitives, which provide basic functions such as refreshing the screen, Insert a character and load the file), which in turn call the lowest level Buffer and OS (native operating system commands).

This architecture is similar to the MVC architecture in our Web development, with Lisp interpreters providing powerful customization capabilities that allow users to write ELISP code to add functionality to Emacs.

Similarly, VSCode provides a variety of plug-in apis, which are not as powerful as Emacs, but are sufficient for many of our needs. Plus, VSCode has a much better ecosystem than Emacs, with very low barriers to entry. I didn’t know anything about VSCode plug-in development until I released my first plug-in in less than two days (it was a bit of intermittent development).

VSCode plug-in development

background

If you just make a very simple demo plug-in, it can be done in less than half an hour, but the real scenario is usually more complicated. In my time management tool article, I mentioned that I developed VSCode plugin TODO++, which is a modified version based on someone else’s plug-in. A lot of the functionality I need is already provided by vscode-todo-plus, but I need to see what I’m currently DOING and what I’m currently marked as important but haven’t started, and I need to provide two more Windows (DOING/CRITICAL) in the window to the left of vscode.

Step1 / development

To add two TreeView Windows, first learn the basics of a TreeView. Check out VSCode’s official TreeView Guide and your first-extension.

Add Doing Task Tree View here.

Note that we need to add our own command in package.json first, because we need to add a refresh button in the TreeView. When the refresh button is clicked, the refresh command will be invoked.

{
    "command": "todo.refreshDoingEntry"."title": "Refresh"."icon": {
        "light": "resources/icons/refresh_light.svg"."dark": "resources/dark/refresh_dark.svg"}}Copy the code

The json will register todo.refreshDoingEntry into VSCode’s command table, so the command is actually in commands.ts:

function refreshDoingEntry () {
  DoingFiles.refresh ( true );
}
Copy the code

class Doing extends View {

  id = 'todo.views.0doing';
  clear = false; filePathRe = /^(? ! ~). * (? : \ \ | \ /); getTreeItem ( item: Item ): vscode.TreeItem {returnitem } async getChildren ( item? : Item ): Promise<Item[]> {if ( this.clear ) {

      setTimeout ( this.refresh.bind ( this ), 0 );

      return [];

    }

    let obj = item ? item.obj : await Utils.files.get ();

    while ( obj && ' ' in obj ) obj = obj[' ']; // Collapsing unnecessary groups

    if ( _.isEmpty ( obj ) ) return [new Placeholder ( 'No todo files found' )];

    if ( obj.textEditor ) {

      const items = [],
            lineNr = obj.hasOwnProperty ( 'lineNr')? obj.lineNr : -1; Utils.ast.walkChildren ( obj.textEditor, lineNr, data => { data.textEditor = obj.textEditor; data.filePath = obj.filePath; data.lineNr = data.line.lineNumber;let isDoing = data.line.text.includes("@doing") || (data.line.text.includes("@started") && !data.line.text.includes("@done"));

        let isGroup = false;

        Utils.ast.walkChildren2 ( obj.textEditor, data.line.lineNumber, data => {
          if ((data.line.text.includes("@doing") || data.line.text.includes("@started")) && !data.line.text.includes("@done")) {
            isGroup = true;
            return false;
          }
          return true;
      });

        if(isDoing || isGroup) { const label = _.trimStart ( data.line.text ), item = isGroup ? new Group ( data, label ) : new Todo ( data, label ); items.push ( item ); }});if ( !items.length ) { return []; }

      return items;

    } else {

      const keys = Object.keys ( obj ).sort ();

      return keys.map ( key => {

        const val = obj[key];

        if ( this.filePathRe.test ( key ) ) {

          const uri = Utils.view.getURI ( val );

          return new File ( val, uri );

        } else {

          returnnew Group ( val, key, this.config.embedded.view.icons ); }}); } } refresh ( clear? ) { this.clear = !! clear; super.refresh (); }}Copy the code

Doing extends View implements vscode.TreeDataProvider

, which is the vscode TreeView, with id = ‘todo.views. In the package. The json:

"views": {
      "todo": [{"id": "todo.views.0doing"."name": "Doing"
        },
        {
          "id": "todo.views.3critical"."name": "Critical"
        },
        {
          "id": "todo.views.1files"."name": "Files"
        },
        {
          "id": "todo.views.2embedded"."name": "Embedded"}}]Copy the code

Register the refresh button in the TreeView:

"view/title": [{"command": "todo.refreshDoingEntry"."when": "view == todo.views.0doing"."group": "navigation@0"}]Copy the code

Finally, don’t forget to export in index.ts:

export default [Doing, Critical, Files, Embedded];
Copy the code

Step2 / release

After development, we can use the automatic release plugin Vscode plugin provided by Github Actions to write our own plug-in release pipeline:

on: push
name: "Release Vscode Plugin "
jobs:
  npmInstall:
    name: npm install
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: npm install
        run: npm install
      - name: Vscode release plugin
        uses: JCofman/vscodeaction@master
        env:
          PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
        with:
          args: publish -p $PUBLISHER_TOKEN
Copy the code

Each push triggers automatic publishing to VSCode Marketplace. For detailed information on Publishing, check out the official Publishing Extensions.

Further reading

Take a look at vscode-extension-samples, which is the official Demo for VSCode.

Debugging is inevitable in plug-in development. VSCode also provides powerful Debugging capabilities. Refer to Debugging extensions.

References

  1. Developer Survey Results 2019-Most Popular Development Environments ↩︎

See also in GTD

  • The Parser black magic
  • My time management tool