โš ๏ธ This article is the first contract article in the nuggets community. It is prohibited to reprint without authorization

Hi everyone, I’m Luo Zhu ๐ŸŽ‹, a woodworking front end ๐Ÿงš๐Ÿปโ™€๏ธ living in Hangzhou. If you like my article ๐Ÿงš๐Ÿป, you can gather spiritulist blog for me by clicking like.

Yarn Lock Preview plug-in has been released, the test has been relatively stable, source code in vscode-yarn-lock-preview, if you have some help and help, you can enjoy a Star.

The TRANSLATION of VS Code API Chinese documents has been carried out in an orderly manner with your enthusiasm. Welcome to join us!

preface

In the “from zero development of a WebView-based VScode extension” article, Luo Zhu through the actual combat described how to show the function of custom UI in VScode based on webview, in addition to the article supporting products dig gold plug-in has been released, welcome to try.

People ๐Ÿ‘จ๐Ÿป๐Ÿ’ป always like to show off their new skills, Luo Zhu is no exception, after all, new skills need a lot of actual combat practice. After the introduction of VS Code extension development, I used to solve the development pain points with VS Code plug-in ideas. But most of the extension idea was already owned, until I thought I could visualize the yarn.lock file and searched fruitlessly for related plug-ins.

In this article, you will learn the full VS Code extension development process from inspiration to design to development, implementing a preview of your own file types based on your custom editor, and how to implement the VS Code Yarn.lock preview plug-in.

What is a custom editor โ“

What editor is important to know before we start development? What is a custom editor?

In VS Code, the Editor is actually where we use the most Code. As shown below, VS Code’s user interface is divided into five sections: the Activity Bar, Side Bar, Editor Groups, Panel, and Status Bar.

A custom editor refers to the editor type in VS Code relative to the default text editor. According to different uses, custom editors are divided into custom text editor, custom editor and custom read-only editor.

Custom text editors are often used to provide custom visual rendering of JSON, XML, CSV, JSON, or any text document. Examples include Svg Preview, Markdown Preview Enhanced, Avi Preview, and Todo List.

Custom editors are often used to preview assets files, such as 3D Viewer for VSCode, draw. IO Integration, and Magick Image Reader

But what they all have in common is the use of VS Code’s custom editor feature. The goal of this article is to implement a Preview type extension like the one above.

Why develop Yarn Lock Preview?

The original article did not have this chapter. When the plugin was first released, I rushed to recommend my plugin to my colleagues, friends, and several groups I hung out with before the article was finished, and was confronted by a group soul who asked me, “What’s the point?” :

Developing a custom editor plug-in is more troublesome than simply developing plug-in and WebView plug-in, no one will idle idle business time sacrifice, do a boring thing. From a learning point of view, you can master a large number of VSCode apis. From a functional point of view, you can more intuitively view yarn.lock and support searching for a package and packages that depend on that package. This is useful if you want to determine which packages your app references indirectly:

This is actually a pain point in the actual work. If React Native relies on two versions of the Native package at the same time, the application will crash when running the project due to repeated registration of a certain View. In the past one year since I joined graffiti, I have helped my business classmates check this problem for N times. CMD +f search for dependencies in yarn.lock, and then check the dependencies of the package in yarn.lock.

Although I have been an old driver to deal with this problem, it will always delay a lot of time for me and my colleagues. While the above problem may be extreme, another common development feedback we encounter is that the application crashes after introducing a native package. This is because our React Native App is nested into the existing Native graffiti App, so Native library support is also dependent on the version provided by the App. We also have validation tools for packaging, but it is possible that the user does not rely on the library directly, but that the library references the library indirectly or indirectly. At this point we need to enable human analysis in thousands of lines of plain text yarn.lock.

For example, the react-Native-SVG version currently supported by the App is 5.5.1. The latest version of the React-Native SVG-Charts used by users relies on the React-Native SVG ^6.2.1 or ^7.0.3. Users follow the instructions directly to install and use the runtime crash, and then come to us to question. If we use this plugin, we can search directly in the project:

The last one is the scenario you will definitely encounter, such as the development declaration is dependent on tuya-panel-kit@^4.6.0, and then encounter problems, direct screenshot to tell you that I did not upgrade the version, why the error or why the performance is different. Then we have to show the user what the version is actually dependent on. Some developers go to node_modules, which is unreliable because the hierarchy is so complex that it’s hard to quickly locate. Some developers search for human flesh in yarn.lock; Or some developers directly let us help troubleshoot. So with this plugin, we can directly let the developer search, and then screenshot to raise issues to us:

Tip: The yarn list –pattern tuya-panel-kit command can also list brief dependency information.

I believe readers have reached a consensus with me. If not, welcome to the comments section Battle. Without further ado, let’s start showing the real technology.

Initialize the project

Use official scaffolding

  1. The installationYeoman ๅ’Œ VS Code Extension Generator:npm install -g yo generator-code
  2. Generated projects:yo code
$ yo code
# _ -- -- -- -- -- _ โ•ญ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ”€ โ•ฎ
# | | โ”‚ Welcome to the Visual โ”‚
# | - (o) - | โ”‚ Studio Code Extension โ”‚
# ` -- -- -- -- -- -- -- -- -- ยด โ”‚ the generator! โ”‚
โ•ฐโ”€ (_ยดU '_) โ•ฐโ”€ First taste of time
# /___A___\ /
# | to |
# __ '. ___. '__
# ยด ` | ยฐ ยด ` Y

#? What type of extension do you want to create? New Extension (TypeScript)
#? What's the name of your extension? Yarn Lock Preview
#? What's the identifier of your extension? yarn-lock-preview
#? What's the description of your extension? Previews yarn.lock file
#? Initialize a git repository? Yes
#? Bundle the source code with webpack? No
#? Which package manager to use? yarn

$ code ./vscode-yarn-lock-preview
Copy the code

Normalization project

  1. Code specification configuration –npx @luozhu/create-coding-style
  2. Format the code according to the new code specification –yarn lint --fix
  3. Git Commit specification configuration –npx @luozhu/create-commitlint

Extended Information Configuration

  1. Extended Information configuration (package.json)
    1. Configuration of the publisher
    2. addlicenseField.
    3. addrepositoryField.
    4. configurationicon: 128 x 128 pixel icon path.
  2. Modify the readme: because the original didn’t passvsce packagecheck
  3. usenpx vsce packageTry packaging to make sure there are no errors or warnings

Use esbuild packaging

In developing a webview-based vscode extension from scratch, we talked about how using esbuild packaging can reduce the size of packaging products and speed up debugging. Refer to the previous article to complete the configuration, which will not be covered here.

Don’t repeat yourself

In line with the principle that repetitive work can be replaced by scripts, my best practices for VScode plug-in development have been deposited as a scaffolding. Implementing YARN Create @ VCS/VScode-extension can quickly start developing VScode extensions.

Principle of custom editor

Working mechanism

We’ve already seen that there are three types of custom editors in VS Code: a custom text editor, a custom editor, and a custom read-only editor. All three types of editors replace the positions displayed by standard text editors in VS Code. The difference is that custom text editors, as they are based on VS Code’s standard text document model, do not need to be provided by developers, such as the Svg Preview plug-in. Custom editors are used to preview binary files, so developers need to provide their own document models and implement functions such as save and backup, such as the draw. IO Integration plug-in. Custom read-only editors are used to preview binaries, such as Magick Image Reader.

Knowing the differences between the three custom editors, let’s take a look at which custom editor our plug-in belongs to. The answer is obvious: the yarn.lock file belongs to VS Code’s standard document model, and we can use the simplest custom text editor.

Writing a custom editor involves views, plug-ins, document models, underlying resource files, and their interactions, as shown below:

Since we don’t need to provide a document model to preview yarn.lock, we just need to write a CustomEditorProvider based on the CustomTextEditorProvider to register with the plug-in, and then write our view (user interface), Finally, the interaction between the view and the plug-in can be achieved.

Some of the points we need to focus on are how views are developed, how views and plug-ins communicate, how to implement the CustomEditorProvider, and how to register the CustomEditorProvider.

How to develop views

The view is implemented through a WebView, so you can build the user experience using standard HTML, CSS, and JavaScript, or you can use the front-end framework you’re familiar with.

How does the view communicate with the plug-in

Due to VS Code’s limitations, Webview cannot directly access VS Code API and send network requests, but it can communicate with the plug-in in both directions via postMessage. However, handwritten bidirectional communication is troublesome, especially for network requests. The WebView needs to send a message to initiate a network request first, and the plug-in sends a message to send data to the WebView after the network request returns. Finally, the WebView obtains information through the registered listening event. The brief process is as follows:

This interaction is very un-human regardless of the implementation, and there are also conversion costs and mental burdens associated with the different apis used by the WebView and plug-in. In order to solve this pain point, I encapsulated @luozhu/ VS code-channel to achieve easy two-way communication. You can simply call a method on the WebView side with the call method and wait for the result of processing, and bind event handling on the plug-in side with the bind method.

How to implement the CustomEditorProvider

Refer to the official Demo catScratchEditor. We can see CatScratchEditorProvider is by implementing vscode CustomTextEditorProvider Provider interface to create a custom editor. While vscode. CustomTextEditorProvider resolveCustomTexEditor this interface is only one method. In principle, we only need to implement resolveCustomTexEditor.

How do I register a CustomEditorProvider

Implements the custom editor Provider, we need to be registered with the plug-in, VS Code provides vscode. Window. RegisterCustomEditorProvider method is used to finish the task.

Custom editor implementation

The paper come zhongjue shallow, and must know this to practice. If you are confused by the previous concepts, the next step in the field may be very enlightening.

Declare a custom editor

CustomEditors are declared through the customEditors attribute that contributes to package.json, which provides customEditors. It is an array, which means we can provide multiple custom editors in one extension. The custom editor we declare is as follows:

"contributes": {
  "activationEvents": [
    "onCustomEditor:yarn-lock-preview.yarnLock"]."customEditors": [{"viewType": "yarn-lock-preview.yarnLock"."displayName": "Preview yarn.lock"."selector": [{"filenamePattern": "yarn.lock"}]."priority": "option"}},Copy the code
  • activationEvents– Register activation events
    • onCustomEditor:*– Events that activate custom editors
  • customEditors– Provides a custom editor.
    • viewType– Custom editor identifier. It must be unique in all custom editors, so the extension ID is recommendedviewTypePart of it is included. In the use ofvscode.registerCustomEditorProviderAnd in theonCustomEditor:${id}Used when registering a custom editor in the activation eventviewType.
    • displayName– User readable name of a custom editor. Displays this name to the user when you select an editor to use.
    • selector– A set of Globs for which a custom editor is enabled.
    • priority– (Optional) Determine when to use the custom editor. This field controls the appropriate use of a specific custom editor.
      • option– This editor is not automatically used when a user opens a resource, but can be used by the userReopen WithCommand to switch to this editor.
      • default– This editor is automatically used when a user opens a resource, provided no other default custom editor is registered for the resource.

We can now open a yarn.lock file and in the command panel type Reopen with select Preview yarn.lock WHICH I registered earlier:

We see a blank editor with a never-stopping loading progress bar at the top.

Opening the command panel to output commands is not very user-friendly. We can add a switch button in the editor menu to quickly switch the editor mode. First we configure commands and menus in package.json:

"contributes": {
  "commands":[
    {
      "command": "yarn-lock-preview.switchEditorMode"."title": "switch editor mode"."icon": "$(rocket)"}]."menus": {
    "editor/title": [{"command": "yarn-lock-preview.switchEditorMode"."group": "navigation"}}}]Copy the code
  • commands– Provides commands to the command panel.
  • menus– Provides menu items to the editor.

Then register the command and implement the command callback in the active function in SRC/Extension:

import { commands, ExtensionContext } from 'vscode';

export function activate(context: ExtensionContext) {
  console.log('Congratulations, your extension "yarn-lock-preview" is now active! ');

  context.subscriptions.push(
    commands.registerCommand('yarn-lock-preview.switchEditorMode'.() = > {
      commands.executeCommand('workbench.action.reopenWithEditor'); })); }Copy the code

I only found the workbench. Action. This can trigger reopenWithEditor command, my intention is to achieve similar to open the file and open change function of git. Gits. openFile is a file that can be used to create gits. openFile is a file that can be used to create gits. openFile The current results are as follows:

Register a custom editor Provider

YarnLock and onCustomEditor:yarn-lock-preview. YarnLock activation event are now registered. Now, we need to use the window registerCustomEditorProvider method corresponding custom editor registered Provider.

If this time try to call registerCustomEditorProvider method, you will find that we don’t have a custom editor provider can use, in the next section, we will realize the custom editor provider.

import vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
  console.log('Congratulations, your extension "yarn-lock-preview" is now active! ');

  context.subscriptions.push(
    vscode.window.registerCustomEditorProvider(
      'yarn-lock-preview.yarnLock',
      provider // Customize the editor provider instance); ; }Copy the code

Implement a custom editor Provider

Since the file we are previewing is VS Code’s standard document model, we need to implement it by encapsulating a class based on the CustomTextEditorProvider interface. We create a new YarnLockEditorProvider ts file, the file minimal implementation is as follows:

import vscode from 'vscode';

class YarnLockEditorProvider implements vscode.CustomTextEditorProvider {
  // Inject context into this object
  constructor(private readonly context: vscode.ExtensionContext) {}

  /** * called when the custom editor is opened. * /
  async resolveCustomTextEditor(
    _document: vscode.TextDocument,
    webviewPanel: vscode.WebviewPanel,
    _token: vscode.CancellationToken
  ): Promise<void> {
    // Set the initial content for the WebView
    webviewPanel.webview.options = {
      enableScripts: true.// Allow scripts to run in webView
    };
    webviewPanel.webview.html = this.getHtmlForWebview();
  }

  private getHtmlForWebview(): string {
    return ` 
         
       
       Cat Coding      `; }}export default YarnLockEditorProvider;
Copy the code
  • class YarnLockEditorProvider implements vscode.CustomTextEditorProvider– Declares a class that implements a custom text editor provider.
  • resolveCustomTextEditor: Resolves a custom editor for a given text resource. When the user first opens oneCustomTextEditorProviderResources when, or when they use thisCustomTextEditorProviderThis method is called when an existing editor is reopened.
  • webviewPanel.webview.options– Configure the WebView option, here we have configured to allow the use of scripts.
  • webviewPanel.webview.html = this.getHtmlForWebview()– Sets the initial content for HTML.

To simplify initialization, we wrap a static register method for the YarnLockEditorProvider class:

static register(context: vscode.ExtensionContext): vscode.Disposable {
  const provider = new YarnLockEditorProvider(context);
  const providerRegistration = vscode.window.registerCustomEditorProvider(
    YarnLockEditorProvider.viewType,
    provider,
    {
      webviewOptions: {
        retainContextWhenHidden: true.// Preserve context when hiding}});return providerRegistration;
}

private static readonly viewType = 'yarn-lock-preview.yarnLock';
Copy the code

Now we can register our custom editor provider, in SRC/extension. The activate method of ts call YarnLockEditorProvider. Register (context) get registered custom editor, Then push it into the proxy listener array:

import vscode from 'vscode';
import YarnLockEditorProvider from './YarnLockEditorProvider';

export function activate(context: vscode.ExtensionContext) {
  context.subscriptions.push(YarnLockEditorProvider.register(context));
}
Copy the code

This completes a simple but complete custom editor:

Yarn. lock visual preview implementation

We have successfully implemented the function of viewing cats in the custom editor. Of course, our ultimate goal is not to see black cats typing code. In this chapter we challenge the final Boss: implement a searchable yarn.lock dependent Json tree.

Parse the yarn.lock file

Now that we’ve solved the technical problem, let’s look at the business problem. Our sore point is that the yarn.lock file is plain text, which is difficult to read and requires a better presentation form. The first step is definitely to convert the text file to a more manageable resource, with JSON as the front end. You can do this with the official Yarn tool @yarnpkg/lockfile. So let’s write a demo and try it out.

import * as lockfile from '@yarnpkg/lockfile';
class YarnLockEditorProvider implements vscode.CustomTextEditorProvider {

  /** * called when the custom editor is opened. * /
  async resolveCustomTextEditor(
    document: vscode.TextDocument,
    webviewPanel: vscode.WebviewPanel,
    _token: vscode.CancellationToken
  ): Promise<void> {...// Get plain text and parse it into JSON data
    const json = lockfile.parse(document.getText()).object;
    // Pass the JSON string to the HTML assembly method display
    webviewPanel.webview.html = this.getHtmlForWebview(JSON.stringify(json)); . }private getHtmlForWebview(json: string) :string {
    return ` <! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, <title>Cat Coding</title> </head> <body> <h1>JSON data </h1> < P >${json}</p>
        </body>
        </html>
    `; }}Copy the code

The effect is as follows:

The webview integration umijs

Refer to # developing a webview-based vscode extension from scratch and vscode-juejin-me to initialize the umijs project and do some trimming and adaptation work. Then, the HTML content is obtained using the @apzhu /vscode-utils getUmiHTMLContent method:

webviewPanel.webview.html = getUmiHTMLContent(this.context, webviewPanel, {
  title: 'Yarn Lock Preview'});Copy the code

Webview content updated

After integrated umijs we through webviewPanel. Webview. HTML Settings of the initial content is empty. If we want to pass text from the plug-in to the WebView, we need to do a communication. VS Code is cumbersome to write Code for communication due to various limitations. Basically, plugins that rely on WebView encapsulate the communication mechanism. My idea of encapsulation here is to achieve a @wafzhu/VScode-channel with the least mental burden by referring to JS-channel. With this tool we can easily implement the operation of updating the WebView:

Plug-in side sends an update message:

import Channel from '@luozhu/vscode-channel';
class YarnLockEditorProvider implements vscode.CustomTextEditorProvider {
  async resolveCustomTextEditor(
    document: vscode.TextDocument,
    webviewPanel: vscode.WebviewPanel,
    _token: vscode.CancellationToken
  ): Promise<void> {
    // Set the initial content for the WebView
    webviewPanel.webview.options = {
      enableScripts: true}; webviewPanel.webview.html = getUmiHTMLContent(this.context, webviewPanel, {
      title: 'Yarn Lock Preview'});// Initialize a channel object
    const channel = new Channel(this.context, webviewPanel);
    const json = lockfile.parse(document.getText()).object;
    // Triggers the updateWebview event and passes the text as a parameter
    channel.call('updateWebview', json); }}Copy the code

Webview side listening message:

import Channel from '@luozhu/vscode-channel';

export default function HomePage() {
  const [data, setData] = React.useState({});
  React.useEffect(() = > {
    channel.bind('updateWebview'.message= >{ setData(message.params); }); }}, [])Copy the code

Text content synchronization

In the previous chapter, we implemented the synchronization of the initial content, but the content is not fixed. Yarn.lock may change, and we need to update our content accordingly. This requirement we need to use vscode. Workspace. To implement onDidChangeTextDocument event listeners:

// still implemented in the resolveCustomTextEditor method
// Encapsulates this method due to the need to process JSON data and repeatedly call the update method
function updateWebview(textDocument: vscode.TextDocument) {
  let json = lockfile.parse(textDocument.getText());
  switch (json.type) {
    case 'merge':
      // TODO:Handle the merge type
      break;
    case 'conflict':
      // TODO:To deal with conflict type
      break;
    default:
      json = json.object;
  }
  channel.call('updateWebview', json);
}
// Register the hook event handler so that we can synchronize the WebView with the text document.
//
// The text file is our model, so we must synchronize the changes in the file to our editor.
// Remember that a text file can also be shared among multiple custom editors (for example, this happens when you split a custom editor).
const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(e= > {
  if (e.document.uri.toString() === document.uri.toString()) { updateWebview(e.document); }});// Make sure to remove the listener when our editor is closed.
webviewPanel.onDidDispose(() = > {
  changeDocumentSubscription.dispose();
});
Copy the code

Implement a searchable JSON View

Search implementation is not complex, we are interested in reading the source code directly, THE UI is UMI antD. I’m going to use a react json-view, but there are a couple of interesting things about this.

  1. The rendering performance is poor when the amount of data is large
import ReactJson from 'react-json-view'; . <ReactJson shouldCollapse={filed= > {
    // Collapse everything except the root directory
    if (filed.name) {
      return true;
    }
    return false; }} / >Copy the code
  1. The style fits the current theme
import ReactJson from 'react-json-view';

const getCssVar = (cssVar: string) = > {
  const htmlStyle = document.documentElement.style;
  returnhtmlStyle.getPropertyValue(cssVar).trim(); }; . <ReactJson style={{backgroundColor: getCssVar('--vscode-editor-background'),
    fontSize: getCssVar('--vscode-editor-font-size'),
  }}
/>
Copy the code
  1. Light and dark themes fit

The perfect fit would surely be to switch the ReactJson theme as the theme changes, and we would need to issue a notification on the plug-in side:

// The initial theme
channel.call('updateColorTheme', vscode.window.activeColorTheme);
// Listen for topic change events
vscode.window.onDidChangeActiveColorTheme(colorTheme= > {
  channel.call('updateColorTheme', colorTheme);
});
Copy the code

Then listen for the updateColorTheme event on the WebView side:

const[theme, setTheme] = React.useState<ThemeKeys>(); . React.useEffect(() = > {
channel.bind('updateColorTheme'.message= > {
  const { kind } = message.params;
  setTheme(kind === 1 ? 'rjv-default' : 'monokai'); }); } []); . <ReactJson theme={theme} />Copy the code

The user interface was demonstrated at the beginning of this article, so here’s a black theme:

API Appendix

VS Code plug-in development involves a lot of knowledge, and each type of plug-in has its own routines and apis. In keeping with the principle of separation of concerns, here are some of the apis associated with custom editors for you and me to check out in the future.

vscode.commands.registerCommand

Registers a command that can be called by key mapping, menu items, actions, or directly. Registering a command twice with an existing command identifier results in an error.

vscode.TextDocument

vscode.window.registerCustomEditorProvider

Register a Provider for a custom editor for the viewType contributed by the customEditors extension feature point.

An onCustomEditor:viewType activity event is triggered when a custom editor is opened. Your extension must register a CustomTextEditorProvider, CustomReadonlyEditor, or CustomEditorProvider for viewType as part of its activation.

vscode.CustomTextEditorProvider

Text – based custom editor provider.

A text-based custom editor uses TextDocument as its data model. Because it allows the editor to handle many common operations, such as undo and backup. Provider is responsible for synchronizing text changes between webView and TextDocument.

CustomTextEditorProvider.resolveCustomTextEditor

Resolves a custom editor based on a given text resource. This method will be called when the user first opens a resource for the CustomTextEditorProvider, or when they re-open an existing editor using the CustomTextEditorProvider.

vscode.CustomReadonlyEditorProvider

A read-only custom editor Provider that uses a custom document model.

Custom read-only editors use CustomDocument instead of TextDocument.

You should use this type of custom editor when working with binaries or more complex scenarios. Use the CustomTextEditorProvider for simple text-based documents.

vscode.CustomEditorProvider

Providers that use editable custom editors with custom document models.

Custom editors use CustomDocument instead of TextDocument. This gives the extender complete control over editing, saving, and backing up operations.

You should use this type of custom editor when working with binaries or more complex scenarios. Use the CustomTextEditorProvider for simple text-based documents.

vscode.WebviewPanel

A panel containing a WebView.

The recent oliver

  • The nuggets a | from zero to develop a vscode extension based on the webview
  • Vscode plug-in released
  • Front end componentization actual combat Button
  • Each front-end deserves its own component library, like having a watermelon every summer ๐Ÿ‰
  • The best Node.js framework to use in 2021
  • React Interview series
  • Go language tutorial series

This article was first published in the Nuggets column, as well as luo Zhu’s blog and official account luo Zhu Morning Teahouse.