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 custom
webview
(e.g.,markdown preview
) - Customize the left panel (e.g
git
) - 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
activationEvents
Extended activation event, the property value is an array containing a series of events (exceptonCommand
There wasonView
,onUri
,onLanguage
Etc.). becauseVSCode
For 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);main
Defines entry points for the entire plug-in.engines
Minimum plugin supportVSCode
versioncontributes
Defines 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.
when
Configure the scenario (condition) in which this menu appears, in additionisWindows
withisMac
There areA lot of conditions can be usedcommand
Specifies what commands are triggered by clicking on the menu (commands
Command)group
Specifies 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
enableScripts
Indicates that js scripts are allowed to executeretainContextWhenHidden
Represents 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:
webview
internalAjax requests are not allowedAll,ajax
Requests are cross-domain becausewebview
None per sehost
the
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.
extension
Server-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
webview
Server-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.
DLNA
Migration 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:
extension
Server-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-request
Obtaining the Device Listdlna-play
Address for playing screen videourl
To a certain devicedlna-destroy
The destructionbrowser
object
webview
Server-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;