Сюлун
Теперь, когда из других руководств этой серии руководств мы получили некоторые базовые знания о подключаемых модулях VSCode, LSP и языках программирования кода, мы можем приступить к созданию подключаемого модуля LSP в режиме клиента и сервера. Для этого в этом уроке мы напишем полный проект LSP с нуля.
Запись каталога сервера кода сервера
В качестве первого шага этого руководства мы будем иметь дело с каталогом сервера, написав код сервера.
- пакет.json
Сначала напишите package.json
. Microsoft SDK инкапсулировал для нас большую часть деталей, поэтому на самом деле нам нужно только сослаться на модуль vscode-languageserver
:
{
"name": "lsp-demo-server",
"description": "demo language server",
"version": "1.0.0",
"author": "Xulun",
"license": "MIT",
"engines": {
"node": "*"
},
"repository": {
"type": "git",
"url": "[email protected]:lusinga/testlsp.git"
},
"dependencies": {
"vscode-languageserver": "^4.1.3"
},
"scripts": {}
}
С помощью package.json
мы можем запустить команду npm install
в каталоге сервера для установки зависимостей.
После установки будут ссылаться на следующие модули:
- vscode-jsonrpc
- vscode-languageserver
- vscode-languageserver-protocol
- vscode-languageserver-types vscode-uri
- tsconfig.json
Мы должны использовать typescript для написания кода для сервера, поэтому мы используем tsconfig.json
для настройки параметров Typescript:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "out",
"rootDir": "src",
"lib": ["es6"]
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}
- server.ts
Далее начинаем писать 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 } } }; });
После трехэтапного рукопожатия на VSCode может отображаться сообщение:
connection.onInitialized(() => {
connection.window.showInformationMessage('Hello World! form server side');
});
Наконец, можно добавить код, завершенный в разделе 5:
connection.onCompletion( (_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
return [ { label: 'TextView' + _textDocumentPosition.position.character, kind: CompletionItemKind.Text, data: 1 }, { label: 'Button' + _textDocumentPosition.position.line, 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 = 'JavaScript documentation'; } else if (item.data === 3) { item.detail = 'ListView'; item.documentation = 'ListView documentation'; } return item; } );
- Каталог клиентов
На данный момент сервер готов. Далее, давайте разработаем клиент.
- пакет.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": "[email protected]: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
В любом случае, так как это тоже ts, а код клиента ничем не отличается от кода сервера, то просто скопируйте приведенный выше код:
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"rootDir": "src",
"lib": ["es6"],
"sourceMap": true
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}
- расширение.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<ChildProcess | StreamInfo | MessageTransports | ChildProcessInfo>);
Краткая диаграмма родственных типов выглядит следующим образом:
Давайте настроим его следующим образом:
// Server side configurations let serverModule = context.asAbsolutePath( path.join('server', 'out', 'server.js') );
let serverOptions: ServerOptions = { module: serverModule, transport: TransportKind.ipc };
// client side configurations let clientOptions: LanguageClientOptions = { // js is used to trigger things 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) { // Server side configurations let serverModule = context.asAbsolutePath( path.join('server', 'out', 'server.js') );
let serverOptions: ServerOptions = { module: serverModule, transport: TransportKind.ipc };
// Client side configurations let clientOptions: LanguageClientOptions = { // js is used to trigger things documentSelector: [{ scheme: 'file', language: 'js' }], };
client = new LanguageClient( 'DemoLanguageServer', 'Demo Language Server', serverOptions, clientOptions );
// Start the client side, and at the same time also start the language server client.start(); }
export function deactivate(): Thenable<void> | undefined { if (!client) { return undefined; } return client.stop(); }
- Интегрируйте и запускайте
Теперь все готово, кроме упаковки. Давайте интегрируем вышеуказанный клиент и сервер.
- Конфигурация плагина — 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": "[email protected]: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
, который ссылается на каталоги клиента и сервера:
{
"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" }
]
}
- Настроить VSCode
Выше мы написали код для клиента и сервера, а также код для их интеграции. Теперь ниже мы запишем два конфигурационных файла в директорию .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
в корневом каталоге плагина. Затем запустите команду сборки (которая cmd-shift-B
на Mac) в VSCode, чтобы были созданы js и map в каталогах «out» сервера и клиента.
Теперь его можно запустить с помощью клавиши F5. Исходный код для этого примера хранится по адресу code.aliyun.com:lusinga/testlsp.git
.