Full text 3000 words, welcome thumb up follow forward
Start with a GIF:
The image above should be a frequently used error diagnosis feature, which will tell you what type of problem there is in that piece of code as you write it.
This seemingly lofty function is actually very simple from the point of view of plug-in developers. It is basically the three solutions of VSCode development language features that were briefly introduced in the previous article “What you don’t know about VSCode code highlighting principle” :
- Sematic Tokens Provider based lexical highlighting
- Language API-based programmatic syntax highlighting
- Multi-process architecture syntax highlighting based on Language Server Protocol
Among them, Language Server Protocol has gradually become the mainstream implementation scheme due to its advantages in performance and development efficiency. Next, this paper will introduce the implementation details of various Language features based on LSP, and explain the communication model and development mode of LSP.
The sample code
All the examples in this article have been synchronized to GitHub. We suggest that you click down the code to try it out:
Git clone [email protected]: tecvan-fe /vscode-lsp-sample.git # 2. Install NPM I # or yarn # 3. Use vscode to open the sample code./vscode-lsp-sample # 4. Press F5 in VS Code to start debugging
After the successful execution, you can see the debug window of the plug-in:
The core code is:
server/src/server.ts
: LSP server-side code, providing examples of common language features such as code completion, error diagnosis, code hints, etcclient/src/extension.ts
: Provides a series of LSP parameters, including the debug port of the Server, code entry, communication mode, etc.-
Packages.json: This provides the configuration information needed for the syntax plugin, including:
activationEvents
: Declares the activation condition of the plug-in, in the codeonLanguage:plaintext
Activated when opening a TXT text filemain
: The plug-in’s entry file
Among them, the client/SRC/extension. Ts and packages. The json are simple, too much introduced in this paper, the emphasis is on the server/SRC/server. The ts file, then gradually we apart, different language features of the implementation details.
How do I write Language Server
Server Structure Analysis
The Server/SRC /server.ts example project implements a small but complete Language Server application with the core code:
(proposedfeatures.all); (proposedfeatures.all); (proposedfeatures.all); Const Documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument); const Documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument); Connection. onInitialize((params: initializeParams) => {// Element 3: explicitly declare the language feature supported by the plug-in const result: InitializeResult = { capabilities: { hoverProvider: true }, }; return result; }); // Relate the document collection object to the connection object Documents. Listen (connection); // Element 5: start listening for connection.listen();
From the sample code, you can summarize the five necessary steps for Language Server:
- create
connection
Object, used to achieve information exchange between the client and the server - create
documents
Document collection object that maps files that the client is editing - in
connection.onInitialize
Event, explicitly declares the syntax features supported by the plug-in, such as the return object inclusion in the above examplehoverProvider: true
Declare that the plug-in can provide code hover prompts - will
documents
Associated with theconnection
object - call
connection.listen
Function to start listening for client messages
The above Connection, Documents and other objects are defined in the NPM package:
vscode-languageserver/node
vscode-languageserver-textdocument
This is a basic template that completes various initialization operations of Language Server, and then listens for various interaction events using Connection.OnXXX or Documents. OnXXX, and returns LSP protocol results in event call-back. Or explicitly call communication function such as connection. SendDiagnostics send information interaction.
Let’s take a look at the implementation logic of each language feature through a few simple examples.
Hover tip
When the mouse cursor hovers over Tokens of language elements such as functions, variables, symbols, etc., VSCode will display the corresponding description and help information of Tokens:
To implement the hover prompt feature, first declare that your plug-in supports the Hoverprovider feature:
connection.onInitialize((params: InitializeParams) => {
return {
capabilities: {
hoverProvider: true
},
};
});
After that, you need to listen for the connection.onhover event and return a prompt in the event callback:
connection.onHover((params: HoverParams): Promise<Hover> => {
return Promise.resolve({
contents: ["Hover Demo"],
});
});
OK, this is a very simple example of a language feature, essentially listening for events and returning results, very simple.
Code formatting
Code formatting is a particularly useful function, which can help users quickly and automatically complete the beautification of the code, to achieve the effect such as:
Hovering prompt functions, first of all need to declare the plugin support documentFormattingProvider features:
{... capabilities : { documentFormattingProvider: true ... }}
Then, listen for onDocumentFormatting events:
connection.onDocumentFormatting( (params: DocumentFormattingParams): Promise<TextEdit[]> => { const { textDocument } = params; const doc = documents.get(textDocument.uri)! ; const text = doc.getText(); const pattern = /\b[A-Z]{3,}\b/g; let match; const res = []; While ((match = pattern.exec(text))) {res.push({range: {start: document.positionAt (match.index)), end: Doc. PositionAt (match.index + match[0].length),}, newText: match[0].replace(/(? <=[A-Z])[A-Z]+/, (r) => r.toLowerCase()), }); } return Promise.resolve(res); });
In the example code, the callback function mainly implements the format of a continuous uppercase string as a hump string. The effect is as follows:
Function signatures
The function signature feature is triggered when the user enters the syntax of the function call, at which point VSCode displays help information for the function based on what is returned by Language Server.
Realize the function signature function, need to first statement plugin support documentFormattingProvider features:
{... capabilities : { signatureHelpProvider: { triggerCharacters: ["("], } ... }}
After that, listen for the onSignatureHelp event:
connection.onSignatureHelp( (params: SignatureHelpParams): Promise<SignatureHelp> => { return Promise.resolve({ signatures: [ { label: "Signature Demo", documentation: Parameters: [{label: "@p1 first param", Documentation: ",},],},], ActiveSigNature: 0, activeParameter: 0, }); });
Realized effect:
Error message
Note that the implementation logic for the error message is slightly different from the event + response pattern above:
- No need to pass first
capabilities
Make additional claims; - Listening is
documents.onDidChangeContent
Event, rather thanconnection
Event on object - Not used in event callbacks
return
Statement returns an error message instead of a callconnection.sendDiagnostics
Sending error message
Complete example:
/ / increment error diagnostic documents. OnDidChangeContent ((change) = > {const textDocument = change. Document; // The validator creates diagnostics for all uppercase words length 2 and more const text = textDocument.getText(); const pattern = /\b[A-Z]{2,}\b/g; let m: RegExpExecArray | null; let problems = 0; const diagnostics: Diagnostic[] = []; while ((m = pattern.exec(text))) { problems++; const diagnostic: Diagnostic = { severity: DiagnosticSeverity.Warning, range: { start: textDocument.positionAt(m.index), end: textDocument.positionAt(m.index + m[0].length), }, message: `${m[0]} is all uppercase.`, source: "Diagnostics Demo", }; diagnostics.push(diagnostic); } // Send the computed diagnostics to VSCode. connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); });
If there is a continuous uppercase string in this logical diagnostic code, send the corresponding error message through SendDiagnostics to achieve the effect:
How to identify event and response bodies
In the examples above, I deliberately ignore most of the implementation details and focus instead on the basic framework and input and output of implementing language features. It’s better to teach a man how to fish than to teach him how to fish, so let’s spend a little time learning where to get the information about these interfaces, parameters, and response bodies. There are two very important links:
- https://zjsms.com/egWtqPj/, VSCode website documentation about programmable language features
- Website https://zjsms.com/egWVTPg/, LSP agreement
These two pages provide a detailed description of all the language features that are supported by VSCode. Here you can find a conceptual description of the features you want to implement, such as for code completion:
Well, it’s a little complicated and a little too detailed, but it’s important to take the time to understand it, to get a high-level conceptual idea of what you’re going to do.
In addition, if you choose to write LSP in TS, things become much easier. The vscode-languageserver package provides a well-defined TypeScript type, so you can use the TS + vscode code to find out which listeners to use:
Then, according to the function signature, find the type definition of the parameter and result:
After that, you can tailor the parameters according to the type definition and return the data for the corresponding structure.
Deep understanding of LSP
After looking at the example, let’s look back at the LSP. LSP — Language Server Protocol is essentially an interprocess communication Protocol based on JSON-RPC. LSP itself contains two major contents:
- Define the communication model between client and server, that is, who, when, and how, sends messages in what format to the other, and how the receiver returns the response
- Define the body of the communication message, that is, in what format, what fields, and what values to express the state of the message
As an analogy, HTTP protocol is a network communication protocol that describes how to transmit and understand hypermedia documents between network nodes. The LSP protocol is specifically used to describe the communication and information structure between user behavior and response in the IDE.
To summarize, the workflow of the LSP architecture is as follows:
- Editors such as VSCODE track, calculate and manage user behavior model, and send actions and context parameters to Language Server in the communication mode specified by LSP protocol when some specific behavior sequence occurs
- Language Server returns the response information asynchronously based on these parameters
- The editor then processes interactive feedback based on the response information
To put it simply, the editor is responsible for direct interaction with the user, while the Language Server is responsible for silently calculating how to respond to the user’s interaction actions. The two are separated and decouped by process granularity. Under the framework of LSP protocol, each plays its own role and cooperates with the other. For example, in our usual Web application development, the front end is responsible for the interaction with the user, and the server side is responsible for the management of the invisible parts such as permissions, business data, business state flow and so on.
Currently, the LSP protocol has evolved to version 3.16, covering most of the language features, including:
- Code completion
- Code highlighting
- Define a jump
- Type inference
- Error detection
- , etc.
Thanks to the clean design of LSP, these language features all follow a similar development path, and the learning curve is very smooth. You only need to worry about which function to listen to and what format structure to return when developing.
In the past, IDE support for Language features has been integrated into the IDE or as homogeneous plugins. In VSCode, this homogeneous extension capability is provided through the Language API or the Sematic Tokens Provider interface. Both of these methods were introduced in the last article, “What you don’t know about VSCode highlighting”. Although the architecture is relatively simple and easy to understand, there are some obvious drawbacks:
- Plug-in developers must reuse VSCode’s own development language and environment. For example, Python plug-ins must be written in JavaScript
- The same programming language requires repeated development of similar extensions for different IDEs
The biggest advantage of LSP is that it isolates the IDE Client from the server that actually calculates the interaction characteristics. The same Language Service can be used repeatedly in several different Language clients.
In addition, under the LSP protocol, the client and server run in their own processes, and there will be a positive performance benefit:
- Make sure the UI process doesn’t stall
- Node makes full use of multi-core CPU power
- Since Language Server’s technology stack is no longer limited, developers can choose a higher-performance Language, such as Go
In a word, it’s very strong.
conclusion
This article introduces the most basic skills required to develop an LSP-based language plug-in in VSCode, which is often mixed with another technique: Embedded Languages Server, which supports complex multilanguage composition. If anyone is interested, we’ll talk about it next week.