I. Project Description

Our goal is to implement a basic library plug-in with the following features:

  • Syntax check, check API command call correctness, parameter type correctness, parameter number correctness, key parameters and system check validity, etc
  • Syntax automatic completion, assist users to write code
  • Grammar hover prompt, auxiliary explanation of grammar
  • Other features
    • Webview Displays remote information

In this article, we first introduce how to complete the construction of the whole project. The full code is available on my Github.

Ii. Project architecture design

Since we are involved in syntax validation, vscode provides a language server for asynchronous support when it comes to tasks such as syntax validation. Currently, the aforementioned scaffolding does not support this type of project architecture initialization.

Fortunately, two examples are provided: Lsp-sample and Lsp-multi-server-sample. In the end, our project architecture was basically similar to lsp-sample, but some optimization was made by referring to VScode-ESLint. Mainly, the release of VScode plug-in limited the number of files, so we expected a package optimization. The final catalog is as follows:

├─ changelog.md // Modify log ├─ Readme.md // Subsequent talk about such as suspended tips, automatic completion in this part of the implementation | ├ ─ ─ package - lock. Json | ├ ─ ─ package. The json | ├ ─ ─ the SRC | | ├ ─ ─ the config | | | └ ─ ─ index. The ts / / configuration, Support file | | ├ ─ ─ the extension. The ts / / plug-in entry file | | └ ─ ─ the provider | | └ ─ ─ lintClient. Ts / / server client implementation | ├ ─ ─ tsconfig. Json | └ ─ ─ Webpack. Config. Js / / client webpack compile ├ ─ ─ package - lock. Json ├ ─ ─ package. The json / / project public reliance on ├ ─ ─ server/server/language | ├ ─ ─ Package - lock. Json | ├ ─ ─ package. The json | ├ ─ ─ the SRC | | ├ ─ ─ global, which s | | ├ ─ ─ lint. Ts / / check to realize | | ├ ─ ─ for server ts / / Language server implementation file | | └ ─ ─ utils | | └ ─ ─ the ast. Ts / / ast analytic function | ├ ─ ─ tsconfig. Json | └ ─ ─ webpack. Config. Js / / server webpack compile ├ ─ ─ ├─ tsconfig.json ├─ vsc-extended-quickstart.md // Public Config ├─ tsconfig.json ├─ vsc-extended-quickstart.mdCopy the code

Here we describe in detail what is required to add a language server :(see the previous vscode plug-in development guide (part 1 – theory) for more information.)

1) Plug-in configuration, we can add a configuration to control whether to display warning configuration, official description:contributes.configuration

// Contribute configuration to package.json "contributes": {"configuration": {"type": "object", "title": "vscode-example-tyc", "properties": { "vscode-example-tyc.warning": { "scope": "resource", "type": "Boolean ", "default": false, "description":" Enable warning "}}}}Copy the code

2) Plug-in entry file

In order to optimize the project, we split the client initialization into a separate file

// client/src/provider/lintClient.ts import * as vscode from 'vscode'; import * as path from 'path'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'; import { file } from '.. /config'; export default function(context: Let serverModule = context.asAbsolutePath(path.join('server', 'out', 'server.js') ); // --inspect=6011: Run in Node's Inspector mode so VS Code can debug the server // todo: Let debugOptions = {execArgv: ['--nolazy', '--inspect=6011']}; // If the plugin is running in debug mode, debug server options will be used. // Otherwise, run Options let serverOptions: serverOptions = {run: {module: serverModule, transport: TransportKind.ipc }, debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } }; // Let clientOptions: LanguageClientOptions = {documentSelector: file, synchronize: {fileEvents: vscode.workspace.createFileSystemWatcher('**/.clientrc') } }; Const client = new LanguageClient('vscode-example-tyc', 'vscode-example-tyc', serverOptions, clientOptions );; // Start the client, which also starts the server client.start(); return client; };Copy the code

Plug-in entry file:

// client/src/extension.ts import * as vscode from 'vscode'; import { LanguageClient } from 'vscode-languageclient'; import lintClient from './provider/lintClient'; let client: LanguageClient; Export function activate(context: vscode.extensionContext) {// Start the service, the server handles Lint client = lintClient(context); } export function deactivate(): Thenable<void> | undefined { if (! client) { return undefined; } return client.stop(); }Copy the code

3) Language server implementation

// server/src/server.ts import { createConnection, TextDocuments, Diagnostic, ProposedFeatures, InitializeParams, DidChangeConfigurationNotification, TextDocumentSyncKind, InitializeResult, _ } from 'vscode-languageserver'; import { TextDocument } from 'vscode-languageserver-textdocument'; // verify import lint from './lint'; // Create a server connection. Use Node's IPC as the transport mode. Let Connection = createConnection(proposedfeatues.all); // Create a simple text manager. // Text manager only supports full-text synchronization. let documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument); let hasConfigurationCapability: boolean = false; let hasWorkspaceFolderCapability: boolean = false; let hasDiagnosticRelatedInformationCapability: boolean = false; connection.onInitialize((params: InitializeParams) => { let capabilities = params.capabilities; Does the client support 'workspace/ Configuration' requests? / / if not, relegated to use global Settings hasConfigurationCapability =!!!!! ( capabilities.workspace && !! capabilities.workspace.configuration ); hasWorkspaceFolderCapability = !! ( capabilities.workspace && !! capabilities.workspace.workspaceFolders ); hasDiagnosticRelatedInformationCapability = !! ( capabilities.textDocument && capabilities.textDocument.publishDiagnostics && capabilities.textDocument.publishDiagnostics.relatedInformation ); const result: InitializeResult = { capabilities: { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: { resolveProvider: true } } }; if (hasWorkspaceFolderCapability) { result.capabilities.workspace = { workspaceFolders: { supported: true } }; } return result; }); Connection. OnInitialized (() = > {the if (hasConfigurationCapability) {/ / for all configuration Register for all the configuration changes. connection.client.register(DidChangeConfigurationNotification.type, undefined); } if (hasWorkspaceFolderCapability) { connection.workspace.onDidChangeWorkspaceFolders(_event => { connection.console.log('Workspace folder change event received.'); }); }}); // Interface ExampleSettings {[prop: string]: any; } // When the client does not support 'workspace/configuration' requests, use global Settings // Note that the client used by the server in this example is not the problem, but that this can happen to other clients. const defaultSettings: ExampleSettings = { warning: true }; let globalSettings: ExampleSettings = defaultSettings; // Cache all open document configurations let documentSettings: Map<string, Thenable<ExampleSettings>> = new Map(); Connection. OnDidChangeConfiguration (change = > {the if (hasConfigurationCapability) {/ / reset all configuration files have been cached documentSettings.clear(); } else { globalSettings = <ExampleSettings>( (change.settings['vscode-example-tyc'] || defaultSettings) ); } // Re-validate all open text documents document.all ().foreach (validateTextDocument); }); Function getDocumentSettings(resource: string): Thenable<ExampleSettings> {if (! hasConfigurationCapability) { return Promise.resolve(globalSettings); } let result = documentSettings.get(resource); if (! result) { result = connection.workspace.getConfiguration({ scopeUri: resource, section: 'vscode-example-tyc' }); documentSettings.set(resource, result); } return result; } // Keep only the Settings for open documents.ondidClose (e => {documentSettings.delete(e.document documents.uri); }); / / document changes trigger (first open or content change) documents. OnDidChangeContent (change = > {validateTextDocument (change. Document); }); // Lint document function Async function validateTextDocument(textDocument: textDocument): Promise<void> {let diagnostics: Diagnostic[] = []; Let Settings = await getDocumentSettings(textDocument.uri); / / check diagnostics. Push (... lint(textDocument, hasDiagnosticRelatedInformationCapability, settings)); / / send the diagnosis connection. SendDiagnostics ({uri: textDocument uri, diagnostics and}); } / / listening document change connection. OnDidChangeWatchedFiles (_change = > {connection. The console. The log (' We received an file change event '); }); // Let document manager listen for open, change and close events of documents. // Start listening connection.listen();Copy the code

4)activationEvents

Here we focus on activationEvents: plug-in trigger actions. Configurable items include the following categories

  • OnLanguage: Activates events and related plug-ins when a language-specific file is opened
  • OnCommand: Activated when a command is invoked
  • onDebug: Enables the debug session before it starts
    • onDebugInitialConfigurations
    • onDebugResolve
  • WorkspaceContains: Triggered when a folder is opened and contains at least one file that matches the Glob pattern
  • OnFileSystem: Triggered when a file or folder is opened using a scheme. This is usually the file-protocol, but can also be replaced with custom file provider functions such as FTP or ssh.h
  • OnView: Whenever the view with the specified ID is expanded in the VS Code sidebar
  • OnUri: Triggered when the system-level URI of the plug-in is opened. This URI protocol requires the VScode or VScode-INSIDERS protocol. The URI host name must be unique to the plug-in, and the remaining URIs are optional
  • OnWebviewPanel: triggered when VS Code needs to restore the WebView that matches the viewType
  • OnCustomEditor: Triggered when VS Code needs to create a custom editor with a matching viewType
  • * : Triggered when VS Code starts, performance deteriorates and is not recommended
  • OnStartupFinished: VS Code launches after a certain amount of time, similar to the above * effect, but better performance

Here we chose onStartupFinished, but more specific configurations are recommended if you have fewer triggering scenarios, such as just opening js files

5) Language server logs

You can view it in output of the debug window

Third, series of articles

  • Vscode plug-in development guide (I)- theory