Lexical Language Server

repository·main·Indexed 21 days ago

https://github.com/lexical-lsp/lexical

A next-generation language server for Elixir providing high-fidelity developer productivity features such as context-aware completion and as-you-type compilation. It utilizes a dual-VM architecture to isolate the server from the project VM, allowing for version flexibility and dependency isolation. The ecosystem includes Lexical.Server, Lexical.Protocol, Lexical.RemoteControl, and a plugin system for extending functionality with diagnostic plugins like lexical_credo.

Tokens
12.3K
Snippets
50
Records
69
Agent score
76%

What's inside Lexical

  1. Overview of Lexical.Protocol

    main
    The Lexical.Protocol module provides the core data structures and conversion utilities for implementing the Language Server Protocol (LSP) within the Lexical ecosystem. It is used to define and manipulate the messages, requests, and responses that facilitate communication between a language server and a client (such as an IDE or editor).
  2. Overview of the Proto library

    main
    The proto library is a specialized utility used to generate and implement the data structures required for the Language Server Protocol (LSP). It provides the foundational types and schemas necessary for communication between language servers and clients.
  3. Overview of Lexical Plugins

    main

    Plugins allow you to extend Lexical's functionality by providing opt-in features like diagnostics, completion, and code intelligence without modifying the Lexical core.

    Currently, only diagnostic plugins are supported. A diagnostic plugin examines a Lexical.Project or a Lexical.Document and emits Lexical.Plugin.V1.Diagnostic.Result structs to highlight issues (e.g., integrating linters like credo or enforcing custom coding practices).

  4. How the Lexical language server processes messages

    main

    The server application is the entry point for Lexical. When started (e.g., via start_lexical.sh), it initializes a transport layer that reads JsonRPC from standard input and writes responses to standard output.

    Message Flow

    1. Reception: A message is received via the transport.
    2. Parsing: The message is parsed into either an LSP Request or an LSP Notification.
    3. Routing:
      • Lifecycle Messages: The lexical server process handles lifecycle-related messages directly. These include:
        • Synchronizing document states.
        • Processing LSP configuration changes.
        • Performing initialization and shutdown.
      • Other Messages: All other messages are delegated to a Provider Handler via a provider queue. The server uses Lexical.Server.Provider.Handlers.for_request/1 to determine the appropriate handler, creates a task, and executes it.
  5. Understand Lexical's core abstractions and protocols

    main

    Lexical introduces several key abstractions to manage the complexity of LSP and Elixir integration:

    • Lexical.Project: An Elixir struct representing the current state of an Elixir project.
    • Lexical.Convertible protocol: Used to centralize logic for converting LSP data structures into Elixir terms when the conversion is not trivial.
    • Lexical.Server.Transport behaviour: Responsible for reading, writing, serializing, and deserializing messages between the LSP client and the Lexical server. The Lexical.Server.Transport.StdIO module provides the implementation for standard I/O.
    • Lexical.Completion.Translatable protocol: Defines how Elixir language constructs (like behaviour callbacks) are converted into LSP constructs (like completion items). Implementations can be found in Lexical.Server.CodeIntelligence.Completion.Translations.
  6. Use Code Mods to transform documents

    main

    Code mods are modules used to change existing code. They take a document as input, modify it, and return diffs. Code mods are defined in the remote_control sub-app and are executed within the project's virtual machine.

    Common examples of code mods include:

    • Formatting: e.g., applying code formatting to a file.
    • Refactoring: e.g., prefixing unused variables with an underscore (_).
  7. Understand LSP message types in Lexical

    main

    Lexical implements the Language Server Protocol (LSP) using JSON-RPC 2.0. Messages are categorized into three top-level types:

    • Requests: Sent from client to server (or vice versa) that must be answered with a Response.
    • Responses: The mandatory answer to a Request.
    • Notifications: Bi-directional messages that work like events and do not receive responses.

    Lexical maps these LSP concepts into the following modules:

    • Lexical.Protocol.Requests
    • Lexical.Protocol.Responses
    • Lexical.Protocol.Notifications
  8. Implement a Provider Handler

    main

    A Provider Handler is a module used to process LSP requests that are not related to the server's lifecycle. To implement or understand a handler, note that it must define a function with an arity of 2.

    Function Signature Requirements:

    • Arguments: The function must accept two arguments:
      1. The request to handle.
      2. A %Lexical.Server.Configuration{} object.
    • Behavior: The function can reply to the request, ignore it, or perform other actions.
  9. How Lexical's dual-VM architecture works

    main

    Lexical uses a unique architecture to provide an isolated and powerful development environment. When Lexical starts, it boots two separate Erlang Virtual Machines (VMs):

    1. Lexical VM: Runs the language server itself and its dependencies.
    2. Project VM: Runs your specific project code and connects to the Lexical VM via distribution.

    Benefits:

    • Dependency Isolation: Lexical's dependencies will not conflict with your project's dependencies. You can even use Lexical to work on a project that Lexical itself depends on.
    • Version Flexibility: Your project can run on different versions of Elixir and Erlang than Lexical. Lexical can use the latest versions while supporting older projects.
    • As-you-type Compilation: Because the project runs in its own VM, Lexical can compile your code as you type, providing immediate error reporting and highlighting without waiting for a file save.
    • Context-Aware Completion: Completions are aware of the code context (e.g., only showing modules after an alias, or only showing interpolations inside string literals).
  10. Understand the Lexical umbrella application structure

    main

    Lexical is structured as an Elixir umbrella application to ensure isolation between the language server and the user's project. This design minimizes dependency contamination in the user's Virtual Machine (VM) by allowing specific sub-apps to be injected as separate archives.

    The core sub-applications are:

    • common: Shared code used across all applications.
    • proto: Generates Elixir representations of LSP data structures for the protocol app.
    • protocol: Handles the implementation of the Language Server Protocol (LSP).
    • remote_control: The component injected into the user's project code. It provides Lexical with an API to perform actions within the context of the user's application. It is designed with minimal dependencies (common, path_glob, and elixir_sense) to prevent version conflicts.
    • server: The main language server entry point.