Xtextで作成したLSPサーバとVS Codeクライアント(LSPクライアント)をsocket通信で接続する

Xtextで作成したLSPサーバとVS Codeクライアント(LSPクライアント)をsocket通信で接続する

前提

– 「Eclipse Modeling ToolsでXtextプロジェクトをMavenプロジェクトとして作成する – 【Xtext】」参照

LSPサーバのランチャー実装(socket)

org.xtext.example.mydsl.ideプロジェクトのLSPサーバのランチャーをsocket通信で実装します。

package org.xtext.example.mydsl.ide;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.Channels;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Function;

import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.xtext.ide.server.LanguageServerImpl;
import org.eclipse.xtext.ide.server.ServerModule;

import com.google.inject.Guice;
import com.google.inject.Injector;

public class MyDslServerLauncher {
  public static void main(String[] args) throws InterruptedException, IOException {
    Injector injector = Guice.createInjector(new ServerModule());
    LanguageServerImpl languageServer = injector.getInstance(LanguageServerImpl.class);
    Function<MessageConsumer, MessageConsumer> wrapper = consumer -> {
      MessageConsumer result = new MessageConsumer() {
        @Override
        public void consume(Message message) throws MessageIssueException, JsonRpcException {
          System.out.println(message);
          consumer.consume(message);
        }
      };
      return result;
    };
    Launcher<LanguageClient> launcher = createSocketLauncher(languageServer, LanguageClient.class, new InetSocketAddress("localhost", 5007), Executors.newCachedThreadPool(), wrapper);
    languageServer.connect(launcher.getRemoteProxy());
    Future<?> future = launcher.startListening();
    while (!future.isDone()) {
      Thread.sleep(10_000l);
    }
  }

  static <T> Launcher<T> createSocketLauncher(Object localService, Class<T> remoteInterface, SocketAddress socketAddress, ExecutorService executorService, Function<MessageConsumer, MessageConsumer> wrapper) throws IOException {
    AsynchronousServerSocketChannel serverSocket = AsynchronousServerSocketChannel.open().bind(socketAddress);
    AsynchronousSocketChannel socketChannel;
    try {
      socketChannel = serverSocket.accept().get();
      return Launcher.createIoLauncher(localService, remoteInterface, Channels.newInputStream(socketChannel), Channels.newOutputStream(socketChannel), executorService, wrapper);
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
    return null;
  }
}

コード生成

org.xtext.example.mydsl.parentプロジェクトを選択して、右クリックから「Run As」-「Maven install」を選択します。

org.xtext.example.mydsl.ide/target配下に以下jarが作成されます。

  • org.xtext.example.mydsl.ide-1.0.0-SNAPSHOT.jar(Eclipse(OSGi)環境で使用される jar)
  • org.xtext.example.mydsl.ide-1.0.0-SNAPSHOT-ls.jar(fat jarはVS Codeで使用できる)

LSPサーバ起動

生成したjarでLSPサーバ起動します。

java -jar org.xtext.example.mydsl.ide-1.0.0-SNAPSHOT-ls.jar

extension.ts修正

extension.tsを修正します。

import * as vscode from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, StreamInfo } from 'vscode-languageclient/node';
import * as net from 'net';

let client: LanguageClient;

export async function activate(context: vscode.ExtensionContext) {

    const connectionInfo = {
        port: 5007
    };
    const serverOptions = () => {
        // Connect to language server via socket
        const socket = net.connect(connectionInfo);
        const result: StreamInfo = {
            writer: socket,
            reader: socket
        };
        return Promise.resolve(result);
    };

    const clientOptions: LanguageClientOptions = {
        documentSelector: [{ scheme: 'file', language: 'mydsl' }],
        synchronize: {
            fileEvents: vscode.workspace.createFileSystemWatcher('**/*.mydsl')
        }
    };

    client = new LanguageClient(
        'mydslLanguageServer',
        'MyDSL Language Server',
        serverOptions,
        clientOptions
    );
	await client.start();
	context.subscriptions.push({
		dispose: () => {
			if (!client) {
				return;
			}
			client.stop();
		}
	});
}

extension.tsを修正したら、npm run compileします。

デバッグ

F5でVS Code(拡張機能開発ホスト)を起動して確認します。

株式会社CONFRAGE ITソリューション事業部をもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む

タイトルとURLをコピーしました