VsCode Plugin Language Plugin Development

Recently, I was interested in how to develop a VsCode plugin, so I deliberately read some plugin Development Documents, mainly looking at a中文翻译的文档However, this doc is not complete. It is basically a list of key functions, but it is very good to get started. However, if you want to go deeper, you still have to look英文的文档

Most of the content is relatively simple, mainly configuration, it can take effect. What I want to summarize this time is how to develop a brand new language plugin. That is to say, if you want to define a new language similar to JSX, if you provide VsCode Support, such as syntax highlighting, code snippets, tag autism, error prompts, auto-completion, etc.

Plugin directory structure

1
2
3
4
5
6
7
8
9
├── .vscode
│ │ ─ ─ launch.json//Plugin loading and debugging configuration
│ ・ ─ ─ tasks.json//Configure TypeScript compile tasks
- -.gitignore//Ignore build output and node_modules files
- README.md//A friendly plugin doc
├── src
│ ・ ─ ─ extension.ts//plugin source code
Van ─ ─ package.json//Plugin configuration list
├── tsconfig.json // TypeScript配置

Plugin List

Every VS Code plugin must contain a’package.json ', which is the plugin’s配置清单Package.json mixes Node.js fields such as scripts and dependencies, and also adds some fields unique to VS Code, such as publisher, activationEvents, contributes, etc插件清单参考Can be found in. We introduce some very important fields in this section:

  • ‘name’ and’publisher ‘: VS Code uses’ < publisher >. < name > ‘as the ID of a plugin. You can understand it this way, the ID of the Hello World example is’vscode-samples.helloworld-sample’. VS Code uses IDs to distinguish between different plugins.

  • ‘main’: the main entry point of the plugin.

  • activationEventscontributes: 激活事件 and 发布内容配置

  • ‘Engines.vscode’: Describes the minimum VS Code API version that this plugin depends on.

  • ‘postinstall’ script: If your’engines.vscode ‘declares a version 1.25 VS Code API, it will install the target version according to this declaration. Once the’vscode.d.ts’ file exists in node_modules/vscode/vscode.d.ts’, IntelliSense will start working and you can perform definition jumps or syntax checks on all VS Code APIs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"name": "helloworld-sample",
"displayName": "helloworld-sample",
"description": "HelloWorld example for VS Code",
"version": "0.0.1",
"publisher": "vscode-samples",
"repository": "https://github.com/Microsoft/vscode-extension-samples/helloworld-sample",
"engines": {
"vscode": "^1.25.0"
},
"categories": ["Other"],
"activationEvents": ["onCommand: extension.helloWorld"],//Activate the event, that is, what event will activate the plugin. In this example, when the command extension.helloWorld occurs, it will activate the plugin
"Main": "./out/extension.js",//the entry file of the plugin
"Contributs": {//Plugin release configuration, very important, registration events, file icons, label self-closing, etc. Most of the plugin configuration is here
"Commands": [//register commands
{
"Command": "extension.helloWorld",//This is the command in the activationEvent above, that is, the command we choose to run in comman + shift + P
"Title": "Hello World"//what is displayed in the command list
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install"
},
"devDependencies": {
"@types/node": "^8.10.25",
"tslint": "^5.11.0",
"typescript": "^2.6.1",
"vscode": "^1.1.22"
}
}

Entry file (main in package.json)

The plugin entry file will export two functions, ‘activate’ and’deactivate ‘, which are executed when the ** activation event ** you registered is triggered, and’deactivate’ provides the opportunity to perform cleanup work before the plugin is shut down.

The vscode module contains a script located in node./node_modules/vscode/bin/install that pulls the VS Code API defined by the engines .vscode field in package.json. After executing this script, you will get smart code hints and define TS features such as jumps.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//The'vscode 'module contains the VS Code extensibility API
//Import this module as follows
import * as vscode from 'vscode';
//Once your plugin is activated, vscode will immediately call the following method
export function activate(context: vscode.ExtensionContext) {

//Use console to output diagnostic information (console.log) and errors (console.error).
//The following code will only be executed once when your plugin is activated
console.log('Congratulations, your extension "my-first-extension" is now active!');

//The entry command has been defined in the package.json file, now call the registerCommand method
//The parameters in registerCommand must be consistent with the command in package.json
let disposable = vscode.commands.registerCommand('extension.sayHello', () => {
Write your code here, and it will be called every time the command is executed
// ...
Display a message prompt to the user
vscode.window.showInformationMessage('Hello World!');
});

context.subscriptions.push(disposable);
}

Publish content configuration

What can plugins do

Common features

  • Register commands, configurations, shortcut bindings, menus, etc.

Save workspace or global data.

  • Display notification information.

  • Use Quick Select to get user input.

  • Open the system’s file selection tool so that users can select files or folders.

  • Use the progress API to prompt for longer operations.

Command

  • Register commands using contributions.commands in pacakge.json. Only commands registered here can be searched in the command panel.
  • By default, all commands that appear in the Command Panel can be configured in the commands section of package.json. However, some commands are context-specific, such as in language-specific editors, or only displayed when the user has set certain options.Visual Studio Code Key BindingsYou can see how to write this when command
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"contributes": {
"menus": {
"commandPalette": [
{
"command": "myExtension.sayHello",
"when": "editorLangId markdown"
}
]
}
}
}
//Now the myExtension.sayHello command will only appear in the user's Markdown file.
  • Use vscode.command.registerCommand to register the callback function for the command
  • Execute commands using vscode.command.excuteCommand

Vscode also has many built-in commands.VS Code内置命令清单

Let’s look at an example 🌰: The editor.action.addCommentLine command can turn the currently selected line into a comment (you can secretly integrate this function into your own plugin):

1
2
3
4
import * as vscode from 'vscode';
function commentLine() {
vscode.commands.executeCommand('editor.action.addCommentLine');
}

Configure

The plugin needs to publish the content configuration point in’contributs.configuration '发布内容配置点Part of package.json, used to configure plugin startup commands and user-changeable plugin configurations, which can be understood as the main configuration file of the plugin. Fill in the relevant configuration, you can read about it in the’workspace.getConfiguration 'API.

Key binding

Plugins can add custom keymappings in contributs.keybindings and键位绑定Learn more about the content.

Plugins can customize context menu items, which vary depending on where the user right-clicks in the VS Code UI. See more’contributs.menus’ publish content configurations.

Data storage

There are three ways to store data in VS Code:

  • ‘ExtensionContext.workspaceState’: Attribute - Workspace data consisting of Value Pair. When the same workspace is opened again, the data will be retrieved.

  • ‘ExtensionContext.globalState’: Attribute - Global data consisting of Value Pair. This data will be retrieved again when the plugin is activated.

  • ‘ExtensionContext.storagePath’: Path to a local folder that your plugin can read and write to. This is a great choice if you want to store larger data.

  • ‘ExtensionContext.globalStoragePath’: Path to the local storage that your plugin can read and write to. This is a very good choice if you want to store large files in all workspaces.

Notification

Almost all plugins need to prompt users with information at some point. VS Code provides 3 APIs to display information of varying importance:

  • window.showInformationMessage

  • window.showWarningMessage

  • window.showErrorMessage

Theme

Product icon theme and color theme can refer to doc:VS Code插件创作中文开发文档 - 色彩主题

File Icon Theme

The UI of VS Code displays icons to the left of the file name, and the icon series configured by the plugin allows users to freely choose their favorite icons.

However, Vscode currently does not support a language plugin that only defines its own language file icons. If users switch to the theme provided by your plugin, but cannot support all file types, there will be problems

First, create a VS Code plugin and add the iconTheme contribution point

1
2
3
4
5
6
7
8
9
"contributes": {
"iconThemes": [
{
"id": "turtles",
"label": "Turtles",
"path": "./fileicons/turtles-icon-theme.json"
}
]
}

Copy to clipboard copy error copied

  • ‘id’, as the identifier of this icon theme, is currently only for internal use and may be used in settings in the future, so it is best to set a unique value that is readable.

  • ‘label’ will be displayed in the Theme Selection drop-down box.

  • ‘path’ indicates where the icon set is located. If your icon family names follow the ‘* icon-theme.json’ naming convention, then VS Code provides full support.

1
2
3
4
5
6
7
//turtles-icon-theme.json{
"iconDefinitions": {
"_folder_dark": {
"iconPath": "./images/Folder_16x_inverse.svg"
}
}
}

Here, the icon definition contains an identifier _folder_dark. In addition, the following properties are supported:

  • ‘iconPath’: When using svg/png file: the path to the image.

  • ‘fontCharacter’: when using glyph fonts: the characters used in the font.

  • ‘fontColor’: When using glyph fonts: Set the color of the glyph.

  • ‘fontSize’: When using fonts: Set the font size. By default, the font size defined by the font itself will be used. This value should be the relative value of the parent font size (such as 150%).

  • ‘fontId’: When using a font: The ID of the font. If not specified, the first font in the’font specification 'section will be used.

After defining it in iconDefinitions, it can be referenced

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"iconDefinitions": {
"_folder_dark": {
"iconPath": "./images/Folder_16x_inverse.svg"
}
},
"file": "_file_dark",
"folder": "_folder_dark",
"folderExpanded": "_folder_open_dark",
"folderNames": {
".vscode": "_vscode_folder",
},
"fileExtensions": {
"this": "_ this _ file,"
},
"fileNames": {
"win.ini": "_ win _ this _ file,"
},
"languageIds": {
"this": "_ this _ file"
},
"light": {
"folderExpanded": "_folder_open_light",
"folder": "_folder_light",
"file": "_file_light",
"fileExtensions": {
"this": "_ this _ file _ light,"
}
},
"highContrast": {
}
}
  • ‘File’ is a default file icon for files that do not match any plugin, filename, or language type. Currently all file icon properties will be inherited (only applicable to: glyphs font, font size (fontSize)).

  • Folder icon for folded folders. If folderExpanded is not set, this icon will also be used for expanded folders. Use’folderNames’ to associate folders with special names. The folder icon is optional, if not set, the folder will not display any icons.

  • ‘folderExpanded’ expanded folder icon. This icon is optional, if not set it will use the’folder 'defined icon.

  • ‘folderNames’ special name folder icon. This key is for folder names, names containing paths are not supported, matching patterns and Wildcards are not supported. Case insensitive.

  • ‘folderNamesExpanded’ expanded special name folder icon.

  • The root folder icon of the collapsed workspace of’rootFolder ‘. If’rootFolderExpanded’ is not set, then the root folder of the expanded workspace will also use this icon. If not set, the folder icon defined by’folder 'will be used.

  • ‘rootFolderExpanded’ expanded workspace root folder icon. If not set, the folder icon defined by’rootFolder 'will be used.

  • ‘languageIds’ language type icon. This key will match the * language configuration point (contribution point) * configured语言idNote that the’first line 'of the language configuration is not taken into account.

  • ‘fileExtensions’ file plugin icon. Matches according to the name of the file plugin. The plugin name is after the dotted number of the file name (excluding the dotted number). File names with multiple dots, such as’lib.d.ts’, will match multiple patterns - ‘d.ts’ and’ts’. Case sensitive.

  • ‘fileNames’ file icon. This key requires the full name of the file for matching, does not support names containing paths, does not support patterns and Wildcards. Case sensitive. ‘fileNames’ is the highest priority match.

Match priority: ‘fileNames’ > ‘fileExtensions’ > ‘languageIds’

The property sheets of the’light ‘and’highContrast’ sections are the same as above, but will override the original icon configuration under the corresponding theme.

Language features

Declarative language feature

声明式语言特性添加了基础的编程语言编辑支持,如括号匹配、自动缩进和语法高亮。这些功能都可以通过声明配置而不用写任何代码就可以获得,更高级的语言特性如IntelliSense或调试,请看编程式添加语言特性

  • Package commonly used JS code snippets into plugins

  • Added new language support for VS Code

  • Add or replace syntax for a language

  • Extend a syntax by injection

  • Migrate existing TextMate syntax to VS Code

Code snippets

VS Code插件创作中文开发文档 - 代码片段

Language configuration

By publishing content configuration via contributions.languages, you can configure the following * declarative language features *:

  • enable/disable annotations

  • Definition of brackets

  • Auto-closing symbols

  • Auto wrap around symbol

  • Code folding

  • Word match

  • Indentation rules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
{
"name": "language-configuration-sample",
"displayName": "Language Configuration Sample",
"description": "Language Configuration Sample",
"version": "0.0.1",
"publisher": "vscode-samples",
"engines": {
"vscode": "^1.28.0"
},
"categories": [
"Programming Languages"
],
"contributes": {
"Languages ": [ // define new languages
{
"Id": "vnm",//id of language
"extensions": [
".js"
],
"aliases": [
"js",
"JavaScript"
],
"Configuration": "./language-configuration.json"//configuration of the language
}
]
}
}
{
"Comments ": { // Define comments
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"Brackets ": [["{", "}"], ["[", "]"], ["(", ")"]], // define parentheses, when the mouse moves over a parenthesis, the corresponding parenthesis will be automatically highlighted
"autoClosingPairs": [//autoClosingPairs
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "'", "close": "'", "notIn": ["string", "comment"] },
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "`", "close": "`", "notIn": ["string", "comment"] },
{ "open": "/**", "close": " */", "notIn": ["string"] }
],
"Au t oC lose Before ": ";:.,=}])>` \ n\ t",//By default, the label will only be closed when there is space on the right side, you can override the default behavior with this configuration
"surroundingPairs ": [ // auto-wrap brackets
["{", "}"],
["[", "]"],
["(", ")"],
["'", "'"],
["\"", "\""],
["`", "`"]
],
"folding": {
"markers": {
"start": "^\\s*//\\s*#?region\\b",
"end": "^\\s*//\\s*#?endregion\\b"
}
},
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)",
"indentationRules": {
"increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$",
"decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$"
}
}

Programming language feature (monitoring user actions)

VS Code插件创作中文开发文档 - 程序性语言特性

The following table corresponds to the programming language features that we can implement. Each feature can be implemented separately through the VScode API in the left column, and can also be implemented separately throughLanguage Server ProtocolImplemented in the language server.

In theory, all the features implemented through the language server can be implemented with the client alone

Unable to copy while content loads

编程式添加语言特性可以为编程语言添加更为丰富的特性,如:悬停提示、转跳定义、错误诊断、IntelliSense和CodeLens。这些语言特性暴露于vscode.languages.*API。语言插件可以直接使用这些API,或是自己写一个语言服务器,通过语言服务器库将它适配到VS Code。

Although we offer a语言特性List, but it doesn’t prevent you from using these APIs freely. For example, displaying additional information in line, using CodeLens and code hovering are very good ways, and error diagnosis can highlight spelling or code style errors.

  • Usage example appears when mouse hovers over API

  • Use diagnostics to report code style errors

  • Register new HTML code formatting

  • Provides rich IntelliSense Middleware

  • Add code folding, breadcrumb, contour support for one language

Take auto-completion as an example to illustrate usage

Client implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class GoCompletionItemProvider implements vscode.CompletionItemProvider {
public provideCompletionItems(
document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken):
Thenable<vscode.CompletionItem[]> {
...
}
}
export function activate(ctx: vscode.ExtensionContext): void {
...
//registerCompletionItemProvider returns the function of unregistering itself. It is speculated that one purpose of adding a subscription is to unregister in the future.
ctx.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
GO_MODE, new GoCompletionItemProvider(), '.', '\"'));
...
}

LSP implementation

In the initialize method that receives the response, your language server needs to declare whether it can provide completion and whether it supports the completionItem\ resolve method that dynamically evaluates completion items.

1
2
3
4
5
6
7
8
9
10
{
...
"capabilities" : {
"completionProvider" : {
"resolveProvider": "true",
"triggerCharacters": [ '.' ]
}
...
}
}

After returning this configuration, the Client will send the textDocument/completion event to the language server after the input is completed. The language server listens for this event through the onCompletion event and returns a matching list after listening.

When we specifically select a completion item, we will send a completionItem/resolve event, which is monitored by the onCompletionResolve event to complete specific information or make modifications

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//server.ts
connection.onCompletion(
({ textDocument, position, context }: CompletionParams): CompletionItem[] => {
const doc = documentService.getDocument(textDocument.uri)!;

//return type is CompletionItem []
const completionItems = doComplete(doc, position);

return completionItems || [];
}
);

//This function provides more information about the selected items in the completion list
connection.onCompletionResolve((item: CompletionItem): CompletionItem => {
if (item.label = '$store') {
item.insertText = 'store'
}
return item;
});

Changes in monitoring doc

The above programming language features mainly do when we perform certain actions, the Client itself listens or the Client will listen to the actions and use events to communicate with the server.

Another way is to notify the client or tell the language server the content of the text when the text itself changes

Client itself listens to content changes, you can find the corresponding api in vscode.workspace. *, such as vscode.workspace.onDidChangeTextDocument

LSP is a little more complicated

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//DocumentService.ts
import { Connection, TextDocuments } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';

/**
* Service responsible for managing documents being syned through LSP
*/
export class DocumentService {
private documents: TextDocuments<TextDocument>;

constructor(conn: Connection) {
this.documents = new TextDocuments(TextDocument);
this.documents.listen(conn);
}

getDocument(uri: string) {
return this.documents.get(uri);
}

getAllDocuments() {
return this.documents.all();
}

get onDidChangeContent() {
return this.documents.onDidChangeContent;
}
get onDidClose() {
return this.documents.onDidClose;
}
}
//server.ts
documentService.onDidChangeContent((change) => {
const textDocument = change.document;
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: validateTextDocument(textDocument) });
});

This method sends the full text every time, which will affect performance, so vscode provides incremental text update synchronization