I. Extension Capabilities
VS Code plugins are not suitable for UI customization, such as Atom's tool-bar which is difficult to implement in VS Code:
Rich extension capabilities are provided, but plugins are not allowed to directly access the underlying UI DOM (meaning plugins cannot change the IDE's appearance; UI customization is limited), which is done for the sake of facilitating continuous underlying optimization:
With VS Code, we're continually trying to optimize use of the underlying web technologies to deliver an always available, highly responsive editor and we will continue to tune our use of the DOM as these technologies and our product evolve.
The UI DOM layer may change frequently with optimizations, and VS Code doesn't want these optimizations to be limited by plugin dependencies, so UI customization capabilities are simply restricted.
Except for UI customization, all IDE-related functional features support extension, such as basic syntax highlighting/API hints, reference jumps (go to definition)/file search, theme customization, advanced debug protocols, etc.
P.S. Actually, if you really need to extend the UI, there are ways (escaping the plugin runtime environment, but it requires considerable effort), see access electron API from vscode extension for details, which will be covered in subsequent notes.
II. Runtime Environment
For performance and compatibility reasons, plugins run in independent processes (called extension host processes) and are not allowed to directly access the DOM, so a set of built-in UI components is provided, such as IntelliSense.
Therefore, plugin crashes or unresponsiveness do not affect normal IDE operation. For example:
// ref: my-extension/src/extension.ts
export function activate(context: vscode.ExtensionContext) {
// hang up
while (true);
}
An infinite loop in a plugin does not affect normal IDE usage or the loading/activation of other plugins, but in the process list you can see Code Helper's CPU usage approaching 100%. Process-level sandboxing ensures the stability of the plugin mechanism.
III. Core Concepts
Stability: Plugin Isolation
Plugins may affect startup performance and IDE stability, so process isolation solves this problem. Plugins run in independent processes without affecting the IDE and its startup time.
This is done from the user's perspective, hoping that users have complete control over the IDE. No matter what plugins do, they don't affect the normal use of basic IDE functions.
P.S. The extension host process is a special Node process that can access VS Code extension APIs, and VS Code also provides debugging support for this type of process.
Performance: Plugin Activation
Plugins are lazy-loaded (as late as possible), only loading/activating in specific scenarios, consuming no memory or other resources before that.
Implementation-wise, plugins register specific activation events, which are triggered by the IDE. For example, the markdown plugin only needs to activate when the user opens a markdown file.
Activation Methods
There are 6 plugin activation methods:
onLanguage:${language} Open a document of a specific language
onCommand:${command} Execute a specific command through Command Palette
onDebug Enter debug mode
workspaceContains:${topLevelFileName} The opened folder contains a specific file
onView:${viewId} Expand a specific view
* Activate when opening the IDE
Except for "activationEvents": ["*"], all are conditional activations, only loading/activating plugins in specific scenarios or when specific conditions are met.
Plugin Manifest File
The manifest file describes plugin metadata. package.json is directly used as the manifest file, with some unique fields added, such as activation events that trigger plugin loading (activationEvents) and extension points that the plugin wants to enhance (contribution points).
During startup, the IDE scans all plugin manifest files, extending the UI for UI-related items and associating extension points with plugin functionality for non-UI items.
Additionally, since the plugin execution environment is a Node process, npm packages are all available, and dependency modules are also declared in package.json. Note: Users will not automatically run npm install when installing plugins, so dependencies need to be packaged before publishing the plugin. See Installation and Packaging for details.
P.S. Extension points are similar to Join points in AOP, i.e., "allow extension/enhancement here". For example, adding a custom command is an enhancement to the commands extension point.
manifest
// package.json
{
// Plugin name
"name": "my-extension",
// Display name
"displayName": "MyExtension",
// Description
"description": "An awesome vscode extension",
// Version number in semver format
"version": "0.0.1",
// Icon displayed in the plugin marketplace
"icon": "img/icon.png",
// Publisher name
"publisher": "ayqy",
// VSCode version requirements
"engines": {
"vscode": "^1.19.0"
},
// Category, optional: Languages, Snippets, Linters, Themes, etc.
"categories": ["Other"],
// Loading/activation method
"activationEvents": ["onLanguage:javascript"],
// Entry file path
"main": "./out/extension",
// Register extension point associations
"contributes": {
"languages": [
{
"id": "javascript",
"aliases": ["JavaScript", "javascript"],
"extensions": [".js"]
}
]
}
}
P.S. Complete documentation at Extension Manifest File - package.json
extension.ts/activate is triggered only once, according to the activationEvents declared in package.json. Trigger conditions can be opening a file of a specific language or executing a specific command. After activation, extension.ts/deactivate is triggered only when the IDE is closed/crashed, so general usage is:
-
activate: Plugin is activated, initialize functional module singleton (executed only once)
-
deactivate: IDE is about to close, clean up, but time-consuming operations should not be done, as it waits at most 10s
Extension Points
These are the supported extension types, all declared under package.json/contributes, including:
configuration Plugin configuration items, users can set through Settings
configurationDefaults Plugin configuration default values
commands Add commands, users can activate plugin features through Command Palette
menus Add menu items associated with commands, users execute corresponding commands when clicking menu items
keybindings Add shortcuts associated with commands, users execute corresponding commands when pressing specific shortcuts
languages Associate with file types or extend new languages, execute corresponding commands when users open (files meeting certain requirements) specific file types
debuggers Add debugger, communicate with IDE through VS Code debug protocol
breakpoints Work with debuggers, declare programming language types supported by debugger
grammars Add TextMate grammar description, syntax highlighting
themes Add custom themes
snippets Add code snippets
jsonValidation Add JSON format validation
problemMatchers Add error matching, parse error, warning, etc. from lint results
problemPatterns Work with problemMatchers, define matching patterns
views Add left-side file explorer view and debug view panels
menus is the only official UI extension path, supporting the following menu extensions:
Command Palette search box dropdown menu commandPalette
File explorer right-click menu explorer/context
Editor
Right-click menu editor/context
Title bar menu editor/title
Title bar right-click menu editor/title/context
Debug view
Call stack right-click menu debug/callstack/context
SCM (Source Control Management) view
Title bar menu scm/title
File group menu scm/resourceGroup/context
File status menu scm/resource/context
File change menu scm/change/title
Left-side view
File explorer panel view/title
Debug view panel view/item/context
P.S. These are all inconspicuous positions; bold UI customization is not supported, such as adding an Icon to the left sidebar (Activity Bar) is impossible.
The title bar menu extension supports custom icons, but the definition method is unusual, for example:
"commands": [{
"command": "markdown.showPreviewToSide",
"title": "%markdown.previewSide.title%",
"category": "Markdown",
"icon": {
"light": "./media/PreviewOnRightPane_16x.svg",
"dark": "./media/PreviewOnRightPane_16x_dark.svg"
}
}],
"menus": {
"editor/title": [
{
"command": "markdown.showPreviewToSide",
"when": "editorLangId == markdown",
"alt": "markdown.showPreview",
"group": "navigation"
}
]
}
Define icon for command, associate menu with command, then menu displays the corresponding icon.
Extension APIs
Environment isolation makes it easier to strictly limit the APIs available to plugins. Plugins can only access extension APIs provided by the IDE and cannot do random things (such as modifying UI DOM and styles, except for officially supported theme customization items).
API Design Principles
Plugin APIs follow some principles:
-
Promise-based: Asynchronous operations are all described using Promises
-
Cancellation tokens: Pass
CancellationTokenas an extra parameter to check cancellation status and receive cancellation notifications -
Disposable resource management: Held resources need to be manually released, such as event listeners, commands, UI interactions, etc.
-
Event APIs: Call subscription method (
on[Will|Did]VerbNoun) passing in listener (receivingeventparameter) returning Disposable -
Strict null checks: Use TypeScript to strictly distinguish between
undefinedandnull
P.S. For more information about "disposable", see Dispose pattern
API Overview
APIs are organized by namespace, with the global namespace as follows:
commands Execute/register commands, including IDE's own and those registered by other plugins, such as executeCommand
debug Debug-related APIs, such as startDebugging
env IDE-related environment information, such as machineId, sessionId
extensions Cross-plugin API calls, extensionDependency declares plugin dependencies
languages Programming language-related APIs, such as createDiagnosticCollection, registerDocumentFormattingEditProvider
scm Source code version control APIs, such as createSourceControl
window Editor window-related APIs, such as onDidChangeTextEditorSelection, createTerminal, showTextDocument
workspace Workspace-level APIs (workspace exists only when a folder is opened), such as findFiles, openTextDocument, saveAll
For example, workspace.findFiles + languages.registerDefinitionProvider can implement Haste's global module reference jump support.
Additionally, some APIs are provided in command form (i.e., the "IDE's own" commands mentioned above), such as vscode.previewHtml, vscode.openFolder, editorScroll, etc.
Protocol-Based Extensions
The plugin process communicates with the IDE through specific protocols, implemented via stdin/stdout in JSON format.
This mode is even more powerful in that: plugins can be implemented in any language, as long as they follow this agreed communication protocol.
IV. Language-Related Extensions
Support syntax highlighting, code snippets, and smart bracket matching through configuration files; more complex features are implemented through extension APIs or language server.
Configuration-Based Extensions
-
Syntax highlighting: Basic support distinguishes syntax roles like strings, comments, keywords, etc.; advanced support provides semantic distinction for variables, function references, etc.
-
Code snippets: Snippets for quick input, basic support for simple placeholders, advanced support for nested placeholders.
-
Smart bracket matching: Advanced support for automatically completing paired items like brackets, quotes, cross-line comments, etc.
Note that VS Code supports standard Text Mate Grammar for language extensions (tmLanguage format), for example, Monaco Editor's non-mainstream Monarch-style is much friendlier, see Colorization Clarification for details.
Programming-Based Extensions
For things that simple configuration can't handle, implement through extension APIs (writing plugins), with 2 methods:
-
Implement language server protocol to communicate with IDE, completely independent
-
Register Provider to provide custom capabilities, similar to hook method
In usage, the first method is troublesome but more powerful and flexible; the second is convenient and direct but not as flexible. Supported extension capabilities are as follows:
-
Hover hints: Basic support for types, documentation, etc.; advanced support for method signature syntax highlighting
-
Completion hints: Advanced support for displaying additional information next to completion hint items
-
Error checking: Basic support for checking and reporting errors on opened file content when saving; advanced support for checking any resources in the directory of opened files
-
Method signatures: Basic support for including parameter documentation in method signatures
-
Go to definition: Basic support for displaying all definitions when multiple exist
-
Find references: Basic support for returning specific locations of all references
-
Selection highlight: Basic support for returning all identical references in the current document
-
Method/variable declaration directory: Basic support for returning all identifiers declared in the document and their definition locations
-
Quick fixes: Provide recommended actions for Warnings and Errors. Basic support for correction actions; advanced support for modifying source code, such as extracting duplicate code into functions
-
Context operation options: Allow providing additional information and actionable options based on the user's code context. Basic support for display; advanced can add custom commands
-
Rename: Basic does not support renaming by reference; advanced supports cross-file renaming across the workspace
-
Code formatting: Basic does not support code formatting; advanced supports full text/selection/in-input formatting
V. Development Steps
Environment Requirements
-
Yeoman and Yo Code - Extension Generator:
npm install -g yo generator-codeto get it done in one step
Steps
Generate project template through scaffolding:
yo code
Command interaction to select plugin type:
New Extension (TypeScript)
New Extension (JavaScript)
New Color Theme
New Language Support
New Code Snippets
New Extension Pack
TypeScript is recommended; others are self-explanatory. Among them, Extension Pack (plugin package) is interesting, i.e., plugins assembled into a plugin, similar to React Native's Nuclide.
Enter plugin name and other metadata, then you get a plugin project. Then use VS Code to open this project separately (workspace should not have other project directories), F5 to launch debug and enter plugin debugging.
The plugin entry file is my-extension/src/extension.ts. Project structure conventions can refer to VS Code built-in plugins:
// ref: https://github.com/Microsoft/vscode/tree/master/extensions/markdown
markdown/
media/
*.svg
*.css
snippets/
markdown.json
syntaxes/
*.tmLanguage
src/
features/
*Provider.ts
typings/
*.d.ts
commandManager.ts
commands.ts
logger.ts
markdownEngine.ts
security.ts
telemetryReporter.ts
VI. Packaging and Publishing
CLI tool vsce is provided:
npm install -g vsce
Packaging
Enter the plugin directory, package into .vsix file:
cd my-extension
vsce package
You'll get a my-extesion.vsix local package (including node_modules dependencies). If you don't want to make it public, figure out your own way to distribute and install, because unlike npm registry where you can manually deploy one for private plugins in intranet environments, Visual Studio Marketplace (VS Code plugin marketplace) doesn't have such an open mindset:
If you want to share your extension with others privately, you can send them your packaged extension .vsix file.
(See Sharing Privately with Others)
There's no way to deploy a Visual Studio Marketplace instance, so you can only figure out how to manually handle plugin updates, such as automatic download/installation prompts.
Publishing
To publish to the plugin marketplace, you need to do a few things:
-
Enter Security page to create a Personal Access Token
-
vsce create-publisher (publisher name)command to add publisher -
vsce login (publisher name)command to log in -
vsce publish -p <token>command to publish
See Publishing Extensions for details.
No comments yet. Be the first to share your thoughts.