VSCodeでLSP(クライアント側のサンプルを読む編)

https://code.visualstudio.com/docs/extensions/example-language-server
のクライアント側のコードを読んで理解してみる。
クライアント側は vscode-extension-samples/lsp-sample/client 配下のファイル。

client/package.json

まずは activationEvents。

"activationEvents": [
    "onLanguage:plaintext"
]

activationEvents は onLanguage に記載されたファイルが開かれる度に、拡張機能(LSPのクライアント)が実行される。

"configuration": {
    "type": "object",
    "title": "Example configuration",
    "properties": {
        "lspSample.maxNumberOfProblems": {
            "scope": "resource",
            "type": "number",
            "default": 100,
            "description": "Controls the maximum number of problems produced by the server."
        }
    }
}

「この設定は起動時や、設定が変更されるたびに言語サーバーに送信される」とのこと。

"dependencies": {
    "vscode": "^1.1.18",
    "vscode-languageclient": "^4.1.4"
}

vscode extension host APIvscode-languageclient library のバージョン。

client/src/extension.ts

クライアント側のコードのエントリポイント。
synchronize のあたりがイマイチ理解できていない。

import * as path from 'path';
import { workspace, ExtensionContext } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient';

export function activate(context: ExtensionContext) {
        // サーバー側の起動設定
        // ...本サンプルではサーバー側が TypeScript で実装されているので
        // ...TypeScript からコンパイルした JS のソース位置を指定している模様
	let serverModule = context.asAbsolutePath(path.join('server', 'server.js'));
	// ...デバッグ時のオプション指定
        // ......--inspect はなんだろ
	let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] };
	
	// ...デバッグモードの時は debug, それ以外は run
	let serverOptions: ServerOptions = {
		run : { module: serverModule, transport: TransportKind.ipc },
		debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
	}
	
	// クライアント側のオプション
	let clientOptions: LanguageClientOptions = {
		// ...テキストファイルを対象とする
		documentSelector: [{scheme: 'file', language: 'plaintext'}],
		synchronize: {
			// ...以下の package.json の configuration.properties の
                        // ...lspSample をサーバーと同期する。
                        // ...(lspSample.maxNumberOfProblems の値の変更がサーバーにも反映される?)
			// ......"configuration": {
 			// ......   "properties": {
			// ......        "lspSample.maxNumberOfProblems": 
			configurationSection: 'lspSample',
			// ... ワークスペース内の .clientrc ファイルのの変更をサーバーに通知する
			fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
		}
	}
	
	// クライアント側の開始
	let disposable = new LanguageClient('lspSample', 'Language Server Example', serverOptions, clientOptions).start();
	
	// クライアントが拡張機能停止時に非アクティブ化できるようにする
	context.subscriptions.push(disposable);
}

サーバー側の実装が TypeScript 以外の場合の設定方法

Launguage Server Protocol のサーバー側の実装はどんな言語でもよいとなっている。
サンプルでは TypeScript でサーバーが実装されているが、それ以外の言語で実装した場合は、
クライアント側はどう記述すればよいのか調べてみた。

TypeScript

まずは、サンプルのTypeScript。
以下の部分がポイントのよう。

	let serverModule = context.asAbsolutePath(path.join('server', 'server.js'));
	let serverOptions: ServerOptions = {
		run : { module: serverModule, transport: TransportKind.ipc },
		debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
	}
C#
  • dll

OmniSharp から拝借
https://github.com/OmniSharp/csharp-language-server-protocol/blob/master/vscode-testextension/src/extension.ts
サーバーを dll で作成して 'dotnet' コマンドで起動する模様。
ServerOptions.run(debug).module の代わりに command を使う

    let serverExe = 'dotnet';
    let serverOptions: ServerOptions = {
        run: { command: serverExe, args: ['D:\\Development\\Omnisharp\\csharp-language-server-protocol\\sample\\SampleServer\\bin\\Debug\\netcoreapp2.0\\win7-x64\\SampleServer.dll', '-lsp'] },
        debug: { command: serverExe, args: ['D:\\Development\\Omnisharp\\csharp-language-server-protocol\\sample\\SampleServer\\bin\\Debug\\netcoreapp2.0\\win7-x64\\SampleServer.dll', '-lsp'] }
    }
  • exe

https://github.com/matarillo/vscode-languageserver-csharp-example/blob/master/client/src/extension.ts
から拝借。
こちらは exe を起動している。

	let serverCommand = context.asAbsolutePath(path.join('server', 'SampleServer.exe'));
	let commandOptions: ExecutableOptions = { stdio: 'pipe', detached: false };
	let serverOptions: ServerOptions =
		(os.platform() === 'win32') ? {
			run : <Executable>{ command: serverCommand, options: commandOptions },
			debug: <Executable>{ command: serverCommand, options: commandOptions }
		} : {
			run : <Executable>{ command: 'mono', args: [serverCommand], options: commandOptions },
			debug: <Executable>{ command: 'mono', args: [serverCommand], options: commandOptions }
		};
Java

qiitaの記事LSP4JでLanguage Server Protocol入門から拝借
command に java のパス、args に -jar と jar の場所を指定している。

    const serverOptions: Executable = {
        command: "C:/PROGRA~1/Zulu/zulu-10/bin/java",
        args: ["-jar", "F:/work/TeachYourselfLsp4j/server/build/libs/TeachYourselfLsp4j-1.0-SNAPSHOT.jar"]
    }