VSCode is a lightweight code editor from Microsoft, free and powerful, with powerful functions, prompt friendly, good performance and appearance level captured a large number of developers favor, JavaScript and NodeJS support is very good, with many features, such as code formatting, code intelligent prompt completion, Emmet plug-in.

It’s cross-platform via Electron, which is based on Chromium and Node.js, like the VS Code interface, which is rendered via Chromium. Also, VS Code is multi-process architecture. When VS Code is first started, a main process is created, and then a Renderer process is created for each window. At the same time, VS Code creates a process for each window specifically to execute the plug-in, known as the Extension Host. In addition to these three main processes, there are two special processes. The first is the Debug Adapter. VS Code creates a Debug Adapter for the debugger. The renderer communicates with the Debug Adapter through VS Code Debug Protocol.

The architecture diagram is as follows:

However, we won’t go into the architecture much in this share, focusing on how plug-ins (also called extensions) are written.

What are the types of VSCode plug-ins

From the scaffolding of vscode plug-in development (executing yo code) we can see the following options:

  • New Extension (TypeScript)
  • New Extension (JavaScript)
  • New Color Theme
  • New Language Support
  • New Code Snippets
  • New Keymap
  • New Extension Pack

Through the CLI, we can directly create extensions, themes, language support, code snippets, shortcuts and other plug-in projects. After these plug-in projects are created, open the box for direct use, press F5 to run.

What can VSCode plug-ins do?

  • Unrestricted local disk access
  • Customize commands, shortcut keys, and menus
    • Explorer right-click menu
    • Editor right click menu
    • The title menu
    • The drop-down menu
    • Icon in upper right corner
  • Custom jump
  • Automatic completion
  • Suspended prompt
  • Custom Settings
  • Customize the welcome page
  • The customwebview(e.g.,markdown preview)
  • Customize the left panel (e.ggit)
  • Custom color theme, icon theme
  • New language support (Java..Net.Python.Dart.Go……)

… Etc., etc.

VSCode’s excellent extension architecture gives us a lot of room to play.

For example, if you’re frustrated with doing something tedious over and over again in your project, it’s time to make a plugin to free your hands!!

Check out the blog below for an excellent overview of the main plugin features, including custom jumps, autocomplete, and hover tips

VSCode plug-in development complete guide

How to implement a WebView plug-in

What I want to talk about today is how I experimented with the WebView plug-in. For the front end, it’s always more attractive to do something pretty that you can see, so I focused on the WebView. First paste a finished drawing:

First, install VScode CLI,

npm install -g yo generator-code
Copy the code

Create a New Extension (TypeScript) project using cli

yo code
Copy the code

It will initialize the following blocks:

  • tsconfig.json
  • package.json
  • extension.ts
  • .vscode directory including one – click debugging configuration items

The tsconfig.json file is out of the box for now, unless we need to use some of typescript’s unique features.

Let’s start with what’s in package.json:

{/ / plug-in activation event "activationEvents" : [" onCommand: extension. SayHello "], / / the entry file "main" : ". / SRC/extension ", "engines" : {"vscode": "^1.27.0"}, // Contribute point, most of the vscode plug-in functionality is contributed here "Contributes ": {"commands": [{"command": "extension.sayHello", "title": "Hello World" } ] } }Copy the code
  • activationEventsExtended activation event, the property value is an array containing a series of events (exceptonCommandThere wasonView,onUri,onLanguageEtc.). becauseVSCodeFor performance reasons, not all plug-ins are loaded as soon as they are opened. Only if the user action triggers the event contained in the array (such as executing a command or opening a file in a language)json), the plug-in will be activated (it can also be specified as “*” and will be loaded immediately, but this is not recommended);
  • mainDefines entry points for the entire plug-in.
  • enginesMinimum plugin supportVSCodeversion
  • contributesDefines all contribution points of the plug-in, such ascommands(Command),menus(Menu)configuration(Configuration item),keybindings(Shortcut key binding),snippets(Snippet),views(Implementation of view in the sidebar),iconThemes(Icon theme), etc.

We will attach a menu in the upper right corner and paste the configuration directly:

"contributes": {
  "commands": [{
    "command": "extension.colaMovie",
    "title": "Cola Movie",
    "icon": {
      "light": "./images/film-light.svg",
      "dark": "./images/film-dark.svg"
    }
  }],
  "menus": {
    "editor/title": [{
      "when": "isWindows || isMac",
      "command": "extension.colaMovie",
      "group": "navigation"
    }]
  }
}
Copy the code

Explanation: Define an extensive. colaMovie command and configure the title and icon.

To configure multiple uses for one command, the title and icon items are placed in Commands. In addition, Icon supports both light and dark themes. If icon is not configured, text headings are displayed.

Define a Menu with the type Editor /title, representing the icon in the upper right corner.

  • whenConfigure the scenario (condition) in which this menu appears, in additionisWindowswithisMacThere areA lot of conditions can be used
  • commandSpecifies what commands are triggered by clicking on the menu (commandsCommand)
  • groupSpecifies menu groups, mainly used for the editor right-click menu

Then let’s go back to the main entry extension.ts file:

const vscode = require('vscode');

/** * triggered when the plugin is activated, all code entry * @param {*} context plugin context */
exports.activate = function(context: vscode.ExtensionContext) {
    console.log('Congratulations, your extension' vscode-plugin-demo 'has been activated! ');
    // Register the command
    context.subscriptions.push(vscode.commands.registerCommand('extension.colaMovie'.function () {
        vscode.window.showInformationMessage('Hello World! ');
    }));
};

/** * triggers */ when plug-ins are released
exports.deactivate = function() {
    console.log('your extension' vscode-plugin-demo 'has been released! ')};Copy the code

This entry file exports two life cycle methods, activate and deactivate.

Recall from the activationEvents property that the activate method is invoked when the plug-in is triggered by the corresponding event in the activationEvents property, and the deactivate method is invoked when the plug-in is destroyed.

Then we have to register a command extension. ColaMovie at activate:

context.subscriptions.push(vscode.commands.registerCommand('extension.colaMovie'.async () => { 
  vscode.window.showInformationMessage('Hello World! ');  
}));
Copy the code

Notice that all registered object (whether it is a command or language vscode. Languages. RegisterDefinitionProvider or otherwise) will have to put the results in the context. Subscriptions. This is so that VSCode can deactivate them automatically for you.

At this point, after we press F5 to debug, we can already see the Cola Movie icon appears in the upper right corner. When we click on it, Hello World pops up in the lower right corner. “Is displayed.

Let’s refine the click event and try creating a WebView:

panel = vscode.window.createWebviewPanel(
  "movie"."Cola Movie",
  vscode.ViewColumn.One,
  {
    enableScripts: true,
    retainContextWhenHidden: true});Copy the code
  • enableScriptsIndicates that js scripts are allowed to execute
  • retainContextWhenHiddenRepresents leaving the plug-in context intact when the TAB is switched away

VSCode, for performance reasons, destroys the context for non-current tabs until the context is rebuilt after switching back. So two methods, setState and getState, are provided for webView to save and restore context instantly.

At this point, the WebView has been created and opened, but is blank.

Now we need to set the HTML content for panel.webview.html, but:

For security reasons, Webview cannot directly access local resources by default. It runs in an isolated context. To load local images, JS, CSS, etc., you must use the special VS code-resource: protocol. The vscode-resource: protocol is similar to the File: protocol, but it only allows access to specific local files. Like file:, vscode-resource: loads resources from disk with absolute paths.

Find a function to replace the HTML reference resource protocol as follows:

function getWebViewContent(context: vscode.ExtensionContext, templatePath: string) {
  const resourcePath = path.join(context.extensionPath, templatePath);
  const dirPath = path.dirname(resourcePath);
  let html = fs.readFileSync(resourcePath, 'utf-8');
  // vscode does not support direct loading of local resources, so it needs to be replaced with its own path format. Here we simply replace the style and JS path
  html = html.replace(/(.(m, $1, $2) = > {
    return $1 + vscode.Uri.file(path.resolve(dirPath, $2)).with({ scheme: 'vscode-resource' }).toString() + '"';
  });
  return html;
}
Copy the code

I had written the electron version of Cola Movie earlier, and at this point I wanted to port it over to see how far the WebView plug-in could go.

I’ll just copy the dist directory over there and load index.html:

const html = getWebViewContent(context, 'dist/index.html');
panel.webview.html = html;
Copy the code

Once tested, it was found that there was a huge crater:

webviewinternalAjax requests are not allowedAll,ajaxRequests are cross-domain becausewebviewNone per sehostthe

I met the cross-domain problem when I was doing the electron development there. The cross-domain permission can be opened by simply configuring the electron webSecurity: false:

let winProps = {
  title: '* * * * * *',
  width: 1200,
  height: 800,
  backgroundColor: '#0D4966',
  autoHideMenuBar: true,
  webPreferences: {
    webSecurity: false,
    nodeIntegration: true}};Copy the code

But VSCode doesn’t give us access to the electron configuration, so I think the route is blocked.

So how do you send an Ajax request to get the data?

I checked with Extension. ts that Axios can send requests and fetch data, which brings us to the next big thing:

Message communication

Webviews, like regular web pages, do not call any VSCode API directly. However, its only special feature is the addition of a method called acquireVsCodeApi, which returns a simplified version of vscode object with the following three methods:

  • getState()
  • postMessage(msg)
  • setState(newState)

In this case, we can send a message to Extension to send the HTTP request for us!

The message communication mode is as follows:

// The plugin sends a message to the WebView
panel.webview.postMessage(message);

// WebView receives the message
window.addEventListener('message'.event= > {
  const message = event.data;
  console.log('Message received by Webview:', message);
};

// The webView sends a message to the plugin
const vscode = acquireVsCodeApi();
vscode.postMessage(message);

// The plug-in side receives the message
panel.webview.onDidReceiveMessage(message= > {
    console.log('Messages received by plug-ins:', message);
}, undefined, context.subscriptions);
Copy the code

Those of you who have written the electron program must know that this is the same as the ipcMain/ipcRenderer and websocket send/ onMessage of electron. The intercalling interfaces at both ends are independent, which is not very nice to write…

cs-channelCross-end communication library

So I packaged a cross-end communication library cS-channel, and open source out, you can have a look at the way to use.

extensionServer-side code

const channel = new Channel({
  receiver: callback= > {
    panel.webview.onDidReceiveMessage((message: IMessage) = > {
      message.api && callback(message);
    }, undefined, context.subscriptions);
  },
  sender: message= > void panel.webview.postMessage(message)
});
channel.on('http-get'.async param => {
  return await Q(http.get(param.url, { params: param.params }));
});
Copy the code

Above, an HTTP-GET interface definition is completed on the plug-in side

webviewServer-side code

const vscode = acquireVsCodeApi();
const channel = new Channel({
  sender: message= > void vscode.postMessage(message),
  receiver: callback= > {
    window.addEventListener('message'.(event: { data: any }) = >{ event && event.data && callback(event.data); }); }});const result = await channel.call('http-get', { url, ... data });Copy the code

Above, the WebView side completed an HTTP-get interface call, and directly get the plug-in side HTTP call results!

Channel objects, two instantiations per project (WebView + Extension) are sufficient, not frequently instantiated. If a project has multiple communication modes, such as websocket + Web worker + iframe parent-child communication, instantiate each Channel object.

DLNAMigration of the screen casting function

The electron version of Cola Movie has the DLNA screen casting feature, but I think that if you can use the nodejs API fully in VSCode, you should be able to cast the screen as well.

I wrote some test code

import * as Browser from 'nodecast-js';
// Yes, you read that right, using DLNA with nodecast-js library nodejs is that easy
const browser = new Browser();
browser.onDevice(function () {
  console.log(browser.getList());
});
browser.start();
Copy the code

Does print all the screen projector devices in the LAN ~

Use the same principles as ajax to ask Extension to hold the device list and push the screen request. Post code directly:

extensionServer-side code

const DLNA = {
  browser: null,
  start: (): Promise<any[] > = > {if(DLNA.browser ! = =null) {
      DLNA.stop();
    }
    return new Promise(resolve= > {
      DLNA.browser = new Browser();
      DLNA.browser.onDevice(function () {
        resolve(DLNA.browser.getList());
      });
      setTimeout((a)= > {
        resolve([]);
      }, 8000);
      DLNA.browser.start();
    });
  },
  stop: (a)= > {
    DLNA.browser && DLNA.browser.destroy();
    DLNA.browser = null; }}; channel.on('dlna-request'.async param => {
  const devices = await DLNA.start();
  localDevices = devices;
  return devices;
});

channel.on('dlna-destroy'.async param => {
  DLNA.stop();
});

channel.on('dlna-play'.async param => {
  localDevices.find(device= > device.host === param.host).play(param.url, 60);
});
Copy the code

Define three interfaces:

  • dlna-requestObtaining the Device List
  • dlna-playAddress for playing screen videourlTo a certain device
  • dlna-destroyThe destructionbrowserobject

webviewServer-side code

const DLNA = {
  start: async() = >await chanel.call<IDevice[]>('dlna-request'),
  play: (device: IDevice, url: string) = > channel.call('dlna-play', { host: device.host, url }),
  stop: (a)= > channel.call('dlna-destroy');
}
Copy the code

F5, debug, screen casting success!

PS: in fact, there is a pity that VSCode itself removed ffmpeg when packing electron, so audio and video labels cannot be used in webview at all, so the playback function cannot be done. Cookie, localStorage and other interfaces cannot be accessed. So I’m just going to open the browser and play. Play HLS m3U8; Play HLS m3u8;