DevUI is a team with both design and engineering perspectives, serving huawei DevCloud platform and huawei internal background systems, as well as designers and front-end engineers. Add devui Helper (Devui-Official) DevUIHelper plugin: Devuihelper-lsp (welcome Star)

The introduction

Hi, we are the DevUIHelper development team. Today, let’s talk about some of the technical solutions we use in plug-in development. This experience may be useful for your plug-in development, so let’s get started!

review

Since our plug-in runs on VSCode, we use some of the capabilities that VSCode provides, such as LSP protocol, completion, hover prompt interface, etc. At the same time, the function of the plug-in is completed by a number of independent function modules. Next, we introduce each module separately.

LSP agreement

VSCode provides local and LSP schemes for code completion plug-in. Native plug-in completion can directly apply some of the capabilities provided by VSCode, but LSP protocol provides the possibility of cross-editor use. Considering that our plug-in may run on more than VSCode platform in the future, we chose LSP protocol.

LSP protocol vision, multiple ides using the same set of completion hint system.

createConnection API

The LSP protocol is based on the client-server mode, so the first step in using LSP is to create a link between the client and the server. In this case, you need to enter the following code on the server side:

import {createConnection,} from 'vscode-languageserver';
private connection = createConnection(ProposedFeatures.all);
connection.listen()
Copy the code

So you create an LSP connection with the default rule. However, the server alone is obviously not enough. It is recommended to start creation through the LSP seed project provided by Microsoft. VSCode also provides a number of seed projects for different scenarios. The following introduction will be based on the Devuihelper-LSP project, recommended with the code to eat. Incidentally seek a wave of star ~.

DConnection

DConnection encapsulates the connection provided by vscode so that you can easily manage all functions and functions:

export class DConnection{ private connection = createConnection(ProposedFeatures.all); . constructor(host:Host,logger:Logger){ ... this.addProtocalHandlers(); } addProtocalHandlers(){ this.connection.onInitialize(e=>this.onInitialze(e)); this.connection.onInitialized(()=>this.onInitialized()); this.connection.onDidChangeConfiguration(e=>this.onDidChangeConfiguration(e)); this.connection.onHover(e=>this.onHover(e)); this.connection.onCompletion(e=>this.onCompletion(e)); this.connection.onDidOpenTextDocument(e=>this.validateTextDocument(e.textDocument.uri)) this.host.documents.onDidChangeContent(change=>this.validateTextDocument(change.document.uri)); }... }Copy the code

The rest of the apis will be covered in the corresponding packages

Function module

The functionality of DevUIHelper is mainly implemented by a number of different functional modules. The following are the dependencies of these packages and we will cover them from the bottom up

Providers

The yellow sections represent the Providers packages in the server/ SRC directory. Wherein, the CompletionProvider is awakened through dConnection. onCompletion, and the onCompletion interface is applied to provide the capability of completion.

onCompletion(_textDocumentPosition: TextDocumentPositionParams){
    ...
    return this.host.completionProvider.provideCompletionItes(_textDocumentPosition,FileType.HTML);
}
Copy the code

The HoverProvider uses the onHover interface to wake up via dConnection. onHover.

async onHover(_textDocumentPosition:HoverParams){
    ...
    return this.host.hoverProvider.provideHoverInfoForHTML(_textDocumentPosition);
}
Copy the code

Diagnosis through Dconnection validateTextDocument sensei, applied the sendDiagnostics interfaces provide error reminder.

async validateTextDocument(uri: string) {
    ...
    let diagnostics: Diagnostic[] = this.host.diagnoser.diagnose(textDocument); 
    this.connection.sendDiagnostics({ uri: uri, diagnostics });
}
Copy the code

The parser

Since VSCode’s onCompletion/onHover API tells us only one coordinate, we need to understand what the cursor position means in order to complete, hover, and alert.

Export interface Position {/** * line where cursor is */ line: number; /** * cursor position relative to the top of the line */ character: number; }Copy the code

VSCode’s coordinate API provides only line count and displacement.

Parser

First, we need to parse the input document, which is provided by yQ-Parser:

export class YQ_Parser{ ... parseTextDocument(textDocument:TextDocument,parseOption:ParseOption):ParseResult{ const uri = textDocument.uri; Const tokenizer = new Tokenizer(textDocument); const tokens = tokenizer.Tokenize(); // Create syntax tree const TreeBuilder = New TreeBuilder(tokens); return treebuilder.build(); }}Copy the code

After the analysis, we need to find the syntax tree node where the cursor is located, a capability provided by Hunter. For example: “The cursor {line:10 character:5} hovers over the D-button node”

export class Hunter { ... SearchTerminalAST (offset: number, URI: string): SearchResult {// Find the syntax tree generated by analysis let _snapShot = host. snapshotmap. get(uri); if (! _snapShot) { throw Error(`this uri does not have a snapShot: ${uri}`); } const { root, textDocument, HTMLAstToHTMLInfoNode } = _snapShot; if (! root) { throw Error(`Snap shot does not have this file : ${uri}, please parse it befor use it! `); } let _result = this.searchparser. DFS(offset, root); // Adjust Node position return _result? _result : { ast: undefined, type: SearchResultType.Null }; }Copy the code

After that, we need to understand what the string D-button means. This capability is provided by SourceLoader. By loading the resource tree file, we know the meaning of the string corresponding to each AST node. For example, “D-button means this is a button label for a DevUI component library.” Then we can provide this information to the user.

Export Class Architect {// Initialize private ReadOnly componentRootNode = new RootNode(); private readonly directiveRootNode = new RootNode(); Constructor () {} / / load the syntax tree resource file named (info: Array < any >, comName: SupportComponentName) : RootNode [] {... } / / completion and hovering information buildCompletionItemsAndHoverInfo () {this.com ponentRootNode. BuildCompletionItemsAndHoverInfo (); this.directiveRootNode.buildCompletionItemsAndHoverInfo(); }Copy the code

We need a monitor for file changes to keep the plugin syntax tree up to date. We use the document interface provided by VSCode itself, which is also very simple to call:

public documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument); . / / when the file changes the operation of the enclosing the documents. OnDidChangeContent (change = > {... });Copy the code

DataStructor

We want our syntax updated with minimal resource consumption, which borrows from the idea of reflux redraw. Web pages are redrawn to minimize resource consumption by updating only the changed parts of the page (usually the part after the changed node tree). In the plug-in’s work, response speed directly affects the user experience, so we want to update the syntax tree locally. To this end, we designed a special syntax tree structure, in which the idea of linked list is widely applied, so we made a small data structure module to support this.

Export interface LinkList<T>{/** * HeadNode */ head:HeadNode; /** * length */ length:number; / * * * * end node/end: LinkNode < T > | undefined; / / insertNode(newElement:T,node? :Node):void; /** * insetLinkList(list:LinkList<T>,node? :Node):void; /** * getElement(cb? :()=>any,param? :T):T|undefined; / * * * according to subscript getting elements * @ param num * / get (num: number) : the Node | undefined; /** * toArray():T[]; }Copy the code

We want to make local updates better with this syntax tree structure.

Cursor

Pointers were one of many ideas we borrowed from the Angular Parser part early in the plugin’s development. We wanted Pointers for syntax tree analysis, storing errors and tree node locations. But as a hint plug-in for the component library, we didn’t need framework-level power, so we made a simple version of the pointer module that now supports Parser and @expression parsing.

MarkUpBuilder

The DevUIHelper plugin uses MarkDown mode text for the prompt, but vscode does not have a powerful MarkDown syntax editor in LSP, so we expect to use this tool module to add text to the document in sections and make the code more semantic.

export class MarkUpBuilder{ private markUpContent:MarkupContent; constructor(content? :string){ this.markUpContent= {kind:MarkupKind.Markdown,value:content? content:""}; } getMarkUpContent():MarkupContent{ return this.markUpContent; } addContent(content:string){ this.markUpContent.value+=content; this.markUpContent.value+='\n\n'; return this; } addCodeBlock(type:string,content:string[]){ content = content.filter(e=>e! = ""); this.markUpContent.value+= [ '```'+type, ...content, '```' ].join('\n'); return this; } setSpecialContent(type:string,content:string){ this.markUpContent.value='```'+type+'\n'+content+'\n```'; return this; }}Copy the code

conclusion

As of this post, DevUIHelper has received 211 unique downloads. At the beginning of the plugin, we found that there were very few Chinese tutorials and discussions about VSCode. We hope to communicate with more developers who love the plugin through this article.

If you want to have a further understanding of VSCode plug-in, it is recommended to refer to the official documents of VSCode plug-in. In addition, there are many excellent introductory tutorials in the Chinese community, such as xiao Ming’s VSCode plug-in tutorial, JTag agent’s fast-food VSCode plug-in tutorial and so on. These tutorials provide a very thorough introduction to VSCode’s API.

Finally, I wish you a happy use ~

Join us

We are DevUI team, welcome to come here and build elegant and efficient human-computer design/research and development system with us. Email: [email protected].

Author: move times dozen times dong Dong dong

Editor: DevUI team

Previous articles are recommended

VSCode plugin DevUIHelper design and Development overview (2)

Dark Mode and Thematic Development of Web Interfaces

“How to build a grayscale Publishing environment”