Eclipse LSP4J Documentation

repository·main·Indexed 20 days ago

https://github.com/eclipse-lsp4j/lsp4j

Java bindings for the Language Server Protocol (LSP) and the Debug Adapter Protocol (DAP). LSP4J enables Java developers to build language servers and debug adapters, providing utilities like LSPLauncher and DSPLauncher for connection management, support for request cancellation via CompletableFuture, and a JSON-RPC implementation with the Endpoint interface.

Tokens
4.8K
Snippets
14
Records
20
Agent score
73%

What's inside Eclipse LSP4J

  1. Use Service Objects for statically typed JSON-RPC

    main

    To avoid using generic Object parameters and methods, you can use Service Objects. A service object is a class or interface where methods are annotated with @JsonNotification or @JsonRequest.

    • To turn a service implementation into an Endpoint: Use ServiceEndpoints.toEndpoint(service).
    • To turn an Endpoint into a typed proxy: Use ServiceEndpoints.toServiceObject(endpoint, ServiceClass.class). This creates an EndpointProxy that allows you to call methods directly on the interface.
    public class MyService {
       @JsonNotification public void sayHello(HelloParam param) {
          // do stuff
       }
    }
    
    // turn it into an Endpoint
    MyService service = new MyService();
    Endpoint serviceAsEndpoint = ServiceEndpoints.toEndpoint(service);
    
    // turn an endpoint into a typed proxy
    MyService proxy = ServiceEndpoints.toServiceObject(endpoint, MyService.class);
  2. Extend the LSP Protocol

    main

    You can extend the protocol in two ways:

    1. Adding client-to-server messages: Implement additional methods annotated with @JsonNotification or @JsonRequest in your LanguageServer implementation and pass it to LSPLauncher.
    2. Customizing server-to-client messages or changing incompatible protocols: Use the utility methods in the Launcher class instead of LSPLauncher. This allows you to use arbitrary local service objects and arbitrary remote service interfaces, enabling you to combine multiple service objects or interfaces.
  3. Understand LSP4J API versioning and breaking changes

    main

    Eclipse LSP4J uses Semantic Versioning. Because the project maps TypeScript-based protocols (LSP/DAP) to Java, certain protocol updates that are non-breaking in TypeScript can become breaking changes in Java.

    Example of a breaking change: In LSP 3.18, Diagnostic.message changed from string to string | MarkupContent. In LSP4J, this requires changing the Java type from String to Either<String, MarkupContent>, which is a binary-incompatible API change.

    Tracking Changes

    • Changelog: View published API changes in the CHANGELOG.
    • japicmp Reports: Since version 0.13.0, p2 update sites include japicmp reports to help identify API changes between releases.
    • API Comparison: You can compare the API of any two arbitrary versions using the japicmp online tool.
  4. Launch and connect a Language Server with LSPLauncher

    main

    Use the LSPLauncher utility class to wire a LanguageServer implementation to a remote client via InputStream and OutputStream.

    To allow the server to send messages back to the client, your implementation should also implement LanguageClientAware, which provides a connect(LanguageClient) method. This method is used to pass the remote proxy to your server implementation.

    Important: Message handlers should be implemented in a non-blocking, asynchronous way. The thread that reads from the input stream is the same thread that dispatches messages; blocking this thread can cause deadlocks or reduced throughput.

    LanguageServer server = ... ;
    Launcher<LanguageClient> launcher = 
        LSPLauncher.createServerLauncher(server,
                                         inputstream, 
                                         outputstream);
    
    if (myImpl instanceof LanguageClientAware) {
       LanguageClient client = launcher.getRemoteProxy();
       ((LanguageClientAware)myImpl).connect(client);
    }
    
    // Start listening for incoming messages in a new thread
    Future<Void> future = launcher.startListening();
    
    // To stop listening:
    // future.cancel(true);
  5. Install Eclipse LSP4J via Maven or p2

    main

    You can consume Eclipse LSP4J using Maven Central or Eclipse p2 update sites.

    Maven

    Artifacts are available on Maven Central.

    p2 Update Sites

    For Eclipse IDE integration, use the following update sites:

    • Releases: https://download.eclipse.org/lsp4j/updates/releases/
    • Milestones: https://download.eclipse.org/lsp4j/updates/milestones/
    • Nightly: https://download.eclipse.org/lsp4j/builds/main/

    Important Note on Signed JARs

    LSP4J distributes signed JARs. If you are bundling LSP4J into your own JAR, you must either remove/exclude or update/override these signatures to avoid security conflicts.

  6. Implement a Debug Adapter Client

    main

    To implement a Debug Adapter (DAP) client, you must include the org.eclipse.lsp4j.debug dependency in your project.

    Maven:

    <dependency>
        <groupId>org.eclipse.lsp4j</groupId>
        <artifactId>org.eclipse.lsp4j.debug</artifactId>
        <version><version></version>
    </dependency>

    Gradle:

    compile group: 'org.eclipse.lsp4j', name: 'org.eclipse.lsp4j.debug', version: '<version>'

    Use DSPLauncher.createClientLauncher to bootstrap the connection between your IDebugProtocolClient and the debug adapter process via its InputStream and OutputStream.

    IDebugProtocolClient client = <...>;
    Process process = <...>;
    InputStream in = process.getInputStream();
    OutputStream out = process.getOutputStream();
    
    Launcher<IDebugProtocolServer> launcher = DSPLauncher.createClientLauncher(client, in, out);
    launcher.startListening();
    
    IDebugProtocolServer remoteProxy = launcher.getRemoteProxy();
    
    // Example: Initialize
    InitializeRequestArguments arguments = new InitializeRequestArguments();
    arguments.setClientID("<client id>");
    arguments.setAdapterID("<adapter id>");
    Capabilities capabilities = remoteProxy.initialize(arguments).get(10, TimeUnit.SECONDS);
    
    // Example: Launch
    Map<String, Object> launchArgs = new HashMap<>();
    launchArgs.put("terminal", "none");
    launchArgs.put("target", "/path/to/target");
    launchArgs.put("noDebug", false);
    launchArgs.put("__sessionId", "sessionId");
    remoteProxy.launch(launchArgs).get(10, TimeUnit.SECONDS);
    
    // Example: Set Breakpoints
    SetBreakpointsArguments breakpointArgs = new SetBreakpointsArguments();
    Source source = new Source();
    source.setName("target");
    source.setPath("/path/to/target");
    breakpointArgs.setSource(source);
    
    SourceBreakpoint sourceBreakpoint = new SourceBreakpoint();
    sourceBreakpoint.setLine(6);
    SourceBreakpoint[] breakpoints = new SourceBreakpoint[]{sourceBreakpoint};
    breakpointArgs.setBreakpoints(breakpoints);
    
    remoteProxy.setBreakpoints(breakpointArgs).get(10, TimeUnit.SECONDS);
    
    // Signal configuration is finished
    remoteProxy.configurationDone(new ConfigurationDoneArguments());
  7. Generate JSON-RPC data classes with @JsonRpcData

    main

    LSP4J provides a generator (using Eclipse Xtend) to create fully functional Java classes for your JSON-RPC parameters and results. By annotating an Xtend class with @JsonRpcData, the generator automatically produces a Java class containing:

    • Getters and setters
    • equals() and hashCode() implementations
    • toString() implementation using ToStringBuilder
    • Null checks using Preconditions

    Note: Generated code requires a runtime dependency on the org.eclipse.lsp4j.jsonrpc bundle to access ToStringBuilder and Preconditions.

    @JsonRpcData
    class HelloParam {
       @NonNull String helloMessage
       int repeatCount
    }
  8. Handle request errors using ResponseErrorException

    main

    When implementing a request handler in a local Endpoint, you must return a response to comply with the JSON-RPC specification. If a request cannot be fulfilled due to an error, throw a ResponseErrorException containing a ResponseError object. The RemoteEndpoint will catch this exception and automatically send a JSON-RPC response message with the attached error details.

    @Override
    public CompletableFuture<Object> shutdown() {
       if (!isInitialized()) {
          ResponseError error = new ResponseError(ResponseErrorCode.ServerNotInitialized, "Server was not initialized", null);
          throw new ResponseErrorException(error);
       }
       return doShutdown();
    }
  9. Cancel pending requests using CompletableFutures.computeAsync

    main

    LSP4J supports request cancellation via a special notification. To implement a cancellable request in your service, use CompletableFutures.computeAsync. This method provides a CancelChecker within a lambda. You must periodically call cancelToken.checkCanceled() inside your implementation; if the request is cancelled, this will throw a CancellationException.

    To trigger a cancellation from the client side, call .cancel(true) on the CompletableFuture returned by the request method.

    @JsonRequest
    public CompletableFuture<CompletionList> completion(TextDocumentPositionParams position) {
       return CompletableFutures.computeAsync(cancelToken -> {
          // the actual implementation should check for
          // cancellation like this
          cancelToken.checkCanceled();
          // more code...  and more cancel checking
          return completionList;
       });
    }
  10. Get started with LSP4J implementation

    main

    LSP4J provides Java bindings for the Language Server Protocol (LSP) and the Debug Adapter Protocol (DAP). To implement a server or a client, refer to the following documentation:

    • Getting Started Guide: Detailed instructions for initial setup and implementation.
    • Core Concepts: Deep dive into the underlying JSON-RPC mechanisms used by the protocols.
    * [Getting Started](documentation/README.md)
    * [Core Concepts](documentation/jsonrpc.md)
  11. Run JSON-RPC benchmarks using Gradle

    main

    The org.eclipse.lsp4j.jsonrpc project includes JMH-based benchmarks for micro-optimizing the JSON-RPC implementation. You can execute the full benchmark suite using the Gradle wrapper.

    Results and JMH caveats are printed to the console. Summary results are also written to: org.eclipse.lsp4j.jsonrpc/build/results/jmh/results.txt.

    ./gradlew jmh