第七期VSCode插件开发手把手实战:从零搭建完整的语言服务器协议工程
摘要
构建完整LSP工程:从零编写VSCode语言服务器插件 前期理论铺垫已经到位,接下来直接动手
构建完整LSP工程:从零编写VSCode语言服务器插件
前期理论铺垫已经到位,接下来直接动手搭建一个完整的LSP插件,采用客户端–服务器分离架构。整个工作流程分为服务端与客户端两个模块:先完成服务端逻辑,再实现客户端接入,最后将两者集成。
服务端目录结构
先构建服务端代码。从 package.json 入手——微软官方SDK已将大部分底层细节封装,我们只需引入 vscode-languageserver 模块即可。
package.json
{
"name": "lsp-demo-server",
"description": "demo language server",
"version": "1.0.0",
"author": "Xulun",
"license": "MIT",
"engines": {
"node": "*"
},
"repository": {
"type": "git",
"url": "git@code.aliyun.com:lusinga/testlsp.git"
},
"dependencies": {
"vscode-languageserver": "^4.1.3"
},
"scripts": {}
}
写好 package.json 后,在 server 目录下执行 npm install 即可安装依赖。安装完成后会自动引入以下子模块:vscode-jsonrpc、vscode-languageserver、vscode-languageserver-protocol、vscode-languageserver-types、vscode-uri。
tsconfig.json
服务端采用TypeScript开发,因此需要配置 tsconfig.json,选项如下:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "out",
"rootDir": "src",
"lib": ["es6"]
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}
server.ts 核心实现
接下来编写核心文件 server.ts。首先引入 vscode-languageserver 和 vscode-jsonrpc 的依赖:
import {
createConnection,
TextDocuments,
TextDocument,
Diagnostic,
DiagnosticSeverity,
ProposedFeatures,
InitializeParams,
DidChangeConfigurationNotification,
CompletionItem,
CompletionItemKind,
TextDocumentPositionParams,
SymbolInformation,
WorkspaceSymbolParams,
WorkspaceEdit,
WorkspaceFolder
} from 'vscode-languageserver';
import { HandlerResult } from 'vscode-jsonrpc';
为了方便调试日志,引入 log4js,先通过 npm i log4js --save 安装,然后初始化:
import { configure, getLogger } from "log4js";
configure({
appenders: {
lsp_demo: {
type: "dateFile",
filename: "/Users/ziyingliuziying/working/lsp_demo",
pattern: "yyyy-MM-dd-hh.log",
alwaysIncludePattern: true,
},
},
categories: { default: { appenders: ["lsp_demo"], level: "debug" } }
});
const logger = getLogger("lsp_demo");
然后通过 createConnection 创建连接:
let connection = createConnection(ProposedFeatures.all);
连接建立后即可处理事件。先注册初始化事件(与第6节介绍的方式一致):
connection.onInitialize((params: InitializeParams) => {
let capabilities = params.capabilities;
return {
capabilities: {
completionProvider: {
resolveProvider: true
}
}
};
});
三次握手完成后,在VS Code中弹出一条通知消息:
connection.onInitialized(() => {
connection.window.showInformationMessage('Hello World! form server side');
});
最后集成第5节学过的代码补全功能:
connection.onCompletion((_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
return [
{
label: 'TextView',
kind: CompletionItemKind.Text,
data: 1
},
{
label: 'Button',
kind: CompletionItemKind.Text,
data: 2
},
{
label: 'ListView',
kind: CompletionItemKind.Text,
data: 3
}
];
});
connection.onCompletionResolve((item: CompletionItem): CompletionItem => {
if (item.data === 1) {
item.detail = 'TextView';
item.documentation = 'TextView documentation';
} else if (item.data === 2) {
item.detail = 'Button';
item.documentation = 'Ja vaScript documentation';
} else if (item.data === 3) {
item.detail = 'ListView';
item.documentation = 'ListView documentation';
}
return item;
});
客户端目录结构
服务端代码基本就绪,接下来开发客户端。
package.json
客户端同样需要先编写 package.json,注意此处依赖的是 vscode-languageclient,切勿与服务端的 vscode-languageserver 混淆。
{
"name": "lspdemo-client",
"description": "demo language server client",
"author": "Xulun",
"license": "MIT",
"version": "0.0.1",
"publisher": "Xulun",
"repository": {
"type": "git",
"url": "git@code.aliyun.com:lusinga/testlsp.git"
},
"engines": {
"vscode": "^1.33.1"
},
"scripts": {
"update-vscode": "vscode-install",
"postinstall": "vscode-install"
},
"dependencies": {
"path": "^0.12.7",
"vscode-languageclient": "^4.1.4"
},
"devDependencies": {
"vscode": "^1.1.30"
}
}
tsconfig.json
客户端与服务端均使用TypeScript,配置基本相同,直接复用:
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"rootDir": "src",
"lib": ["es6"],
"sourceMap": true
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}
extension.ts 入口实现
接下来编写 extension.ts。客户端的职责比服务端简单,核心就是启动语言服务器:
// Create the language client and start the client.
client = new LanguageClient(
'DemoLanguageServer',
'Demo Language Server',
serverOptions,
clientOptions
);
// Start the client. This will also launch the server
client.start();
serverOptions 用于配置服务端参数,其类型定义如下:
export type ServerOptions = Executable
| { run: Executable; debug: Executable; }
| { run: NodeModule; debug: NodeModule }
| NodeModule
| (() => Thenable);
相关类型的简图:

具体配置如下:
// 服务端配置
let serverModule = context.asAbsolutePath(path.join('server', 'out', 'server.js'));
let serverOptions: ServerOptions = {
module: serverModule,
transport: TransportKind.ipc
};
// 客户端配置
let clientOptions: LanguageClientOptions = {
// js代码触发事情
documentSelector: [{ scheme: 'file', language: 'js' }],
};
完整的 extension.ts 代码如下:
import * as path from 'path';
import { workspace, ExtensionContext } from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind
} from 'vscode-languageclient';
let client: LanguageClient;
export function activate(context: ExtensionContext) {
// 服务端配置
let serverModule = context.asAbsolutePath(path.join('server', 'out', 'server.js'));
let serverOptions: ServerOptions = {
module: serverModule,
transport: TransportKind.ipc
};
// 客户端配置
let clientOptions: LanguageClientOptions = {
// js代码触发事情
documentSelector: [{ scheme: 'file', language: 'js' }],
};
client = new LanguageClient(
'DemoLanguageServer',
'Demo Language Server',
serverOptions,
clientOptions
);
// 启动客户端,同时启动语言服务器
client.start();
}
export function deactivate(): Thenable | undefined {
if (!client) {
return undefined;
}
return client.stop();
}
集成与运行
服务端与客户端均已就绪,现在进行组装。主要涉及插件的 package.json、根目录 tsconfig.json 以及 VS Code 调试配置。
插件配置 - package.json
重点关注入口函数和触发事件:
"activationEvents": ["onLanguage:javascript"],
"main": "./client/out/extension",
完整的 package.json 如下:
{
"name": "lsp_demo_server",
"description": "A demo language server",
"author": "Xulun",
"license": "MIT",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "git@code.aliyun.com:lusinga/testlsp.git"
},
"publisher": "Xulun",
"categories": [],
"keywords": [],
"engines": {
"vscode": "^1.33.1"
},
"activationEvents": ["onLanguage:javascript"],
"main": "./client/out/extension",
"contributes": {},
"scripts": {
"vscode:prepublish": "cd client && npm run update-vscode && cd .. && npm run compile",
"compile": "tsc -b",
"watch": "tsc -b -w",
"postinstall": "cd client && npm install && cd ../server && npm install && cd ..",
"test": "sh ./scripts/e2e.sh"
},
"devDependencies": {
"@types/mocha": "^5.2.0",
"@types/node": "^8.0.0",
"tslint": "^5.11.0",
"typescript": "^3.1.3"
}
}
根目录 tsconfig.json
还需一个顶层 tsconfig.json,引用 client 和 server 两个子项目:
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"rootDir": "src",
"lib": [ "es6" ],
"sourceMap": true
},
"include": ["src"],
"exclude": ["node_modules",".vscode-test"],
"references": [
{ "path": "./client" },
{ "path": "./server" }
]
}
VS Code 调试配置
截至目前,client、server 及整合代码已完成。接下来在 .vscode 目录中编写两个配置文件,以便调试和运行。
.vscode/launch.json
配置此文件后,即可通过 F5 启动调试。
// A launch configuration that compiles the extension and then opens it inside a new window
{
"version": "0.2.0",
"configurations": [
{
"type": "extensionHost",
"request": "launch",
"name": "Launch Client",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"outFiles": ["${workspaceRoot}/client/out/**/*.js"],
"preLaunchTask": {
"type": "npm",
"script": "watch"
}
},
{
"type": "node",
"request": "attach",
"name": "Attach to Server",
"port": 6009,
"restart": true,
"outFiles": ["${workspaceRoot}/server/out/**/*.js"]
},
],
"compounds": [
{
"name": "Client + Server",
"configurations": ["Launch Client", "Attach to Server"]
}
]
}
.vscode/tasks.json
配置 npm compile 和 npm watch 两个脚本任务。
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "compile",
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": ["$tsc"]
},
{
"type": "npm",
"script": "watch",
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": ["$tsc-watch"]
}
]
}
全部配置完成后,在插件根目录执行 npm install,然后在 VS Code 中运行构建命令(macOS 下为 Cmd+Shift+B),即可生成 server 和 client 的 out 目录下的 JS 与 source map 文件。此时按下 F5 即可启动运行。
本示例的完整源代码托管于 git@code.aliyun.com:lusinga/testlsp.git,如有需要可自行克隆获取。
来源:互联网
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。