tower-lsp

repository·master·Indexed 23 days ago

https://github.com/ebkalderon/tower-lsp

A Rust implementation of the Language Server Protocol (LSP) built on the Tower service abstraction. It provides a structured framework for building language servers by separating protocol handling from domain-specific logic via the LanguageServer trait, LspService, and Server components. It supports stdio and TCP transport, WebAssembly (Wasm) compilation for browser-based servers, and provides a comprehensive JSON-RPC implementation for managing requests, responses, and error codes.

Tokens
12.1K
Snippets
13
Records
66
Agent score
77%

What's inside tower-lsp

  1. Understand LSP message types and API locations in tower-lsp

    master

    When implementing or using LSP features in tower-lsp, messages are categorized by their direction and type. This determines whether you implement them in the LanguageServer trait or use the Client struct:

    • Request (Client to Server): Implemented via the LanguageServer trait.
    • Notification (Client to Server): Implemented via the LanguageServer trait.
    • Request (Server to Client): Handled using the Client struct.
    • Notification (Server to Client): Handled using the Client struct.
  2. How tower-lsp works

    master

    tower-lsp is a Language Server Protocol (LSP) implementation for Rust built on the tower framework. It is composed of three primary components:

    1. LanguageServer trait: This is where you define the specific behavior and logic of your language server (e.g., how to handle initialization, shutdown, or specific LSP requests).
    2. LspService: An asynchronous delegate that wraps your LanguageServer implementation and handles the underlying protocol mechanics.
    3. Server: The component responsible for spawning the LspService and managing the transport of requests and responses over stdio or TCP.
  3. Use runtimes other than tokio

    master

    By default, tower-lsp is configured to use tokio. If you need to use a different asynchronous runtime, you must disable the default features and enable the runtime-agnostic feature in your Cargo.toml.

    [dependencies.tower-lsp]
    version = "*"
    default-features = false
    features = ["runtime-agnostic"]
  4. Enable proposed LSP features

    master

    To access features defined in the LSP Specification version 3.18 that are currently marked as 'proposed', enable the proposed Cargo feature.

    Warning: Features enabled via the proposed flag have no semver guarantees and may introduce breaking changes between versions.

  5. Use the Client handle to communicate with the language client

    master

    The Client struct is a lightweight, clonable handle used by the language server to send requests and notifications to the client (e.g., an IDE or editor). It implements tower::Service, allowing it to be used with middleware.

    Key characteristics:

    • Cheap to clone: You can pass it around easily across different parts of your server.
    • Lifecycle-aware: Most requests and notifications require the server to be in the Initialized or ShutDown state. Sending requests before initialization will return a JSON-RPC error code -32002.
  6. How LspService handles requests and notifications

    master

    The LspService implements tower::Service<Request>, acting as a bridge between the transport layer and your LanguageServer implementation.

    • Input: A JSON-RPC Request.
    • Output: An Option<Response>.
      • If the input is a request, the output is Some(Response) containing the result or error.
      • If the input is a notification or a client response, the output is None.
    • Lifecycle: The service shuts down and stops serving requests after the exit notification is received. Any subsequent calls to the service will return an ExitedError.
  7. Handle request cancellation in LspService

    master
    Pending requests in an LspService can be canceled by the client issuing a $/cancelRequest notification. When a request is canceled, the corresponding future will resolve with a JSON-RPC error response indicating the request was cancelled.
  8. How to start a tower-lsp server

    master

    To run a server, you typically use LspService::new to wrap your LanguageServer implementation and then use Server::new to bind it to standard input/output or other transport mechanisms.

    Basic pattern:

    1. Define your backend struct (which should hold a Client to send notifications back to the client).
    2. Use LspService::new(|client| Backend { client }) to create the service.
    3. Use Server::new(stdin, stdout, socket).serve(service).await to start the event loop.
        let (service, socket) = LspService::new(|client| Backend { client });
        Server::new(stdin, stdout, socket).serve(service).await;
  9. Implement workspace/symbol and symbol_resolve

    master
    Implement workspace/symbol to list project-wide symbols matching a query. For better performance, implement workspaceSymbol/resolve to allow the server to return symbols without a range initially, letting the client resolve the range only when needed.
  10. Implement textDocument/completion and completionItem/resolve

    master

    To provide code completion, implement textDocument/completion. If computing full completion items is expensive, you can implement completionItem/resolve to provide additional information lazily when a user selects a specific item in the UI.

    Note: All properties except those explicitly listed in completion_item.resolve_support (like sort_text, filter_text, insert_text, and text_edit) must be provided in the initial textDocument/completion response and cannot be changed during resolution.

    async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>>
    async fn completion_resolve(&self, params: CompletionItem) -> Result<CompletionItem>
  11. How the Progress and OngoingProgress types work together

    master

    Progress reporting in tower-lsp follows a two-stage pattern using two distinct types:

    1. Progress<B, C> (The Builder): This type is used to configure the initial state of a progress notification. It uses type parameters to track its capabilities at compile time:

      • B: The bound type (Bounded or Unbounded).
      • C: The cancellation type (Cancellable or NotCancellable).
      • Methods like .with_percentage() transition the type from Unbounded to Bounded.
      • Methods like .with_cancel_button() transition the type from NotCancellable to Cancellable.
    2. OngoingProgress<B, C> (The Handle): Created by calling .begin().await on a Progress builder. This handle is used to send updates (.report()) or end the stream (.finish()). The methods available on this handle are strictly determined by the type parameters B and C set during the builder stage, ensuring you can only call report(percentage) on a Bounded progress stream or report(enable_cancel_btn) on a Cancellable stream.