Lexical Language Server
repository·main·Indexed 21 days ago
https://github.com/lexical-lsp/lexicalA 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.
What's inside Lexical
- Lexical.RemoteControl is an application designed to be injected into a project's virtual machine (VM). Its primary purpose is to provide the necessary support and infrastructure for the language server to operate within that environment.
Overview of Lexical.Protocol
mainTheLexical.Protocolmodule 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).Overview of Lexical.Server
mainLexical.Server is the implementation of the Lexical Language Server (LSP). It provides language server capabilities for the Lexical language, enabling features like autocompletion, diagnostics, and navigation within compatible IDEs and editors.Overview of the Proto library
mainTheprotolibrary 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.Overview of Lexical Plugins
mainPlugins 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.Projector aLexical.Documentand emitsLexical.Plugin.V1.Diagnostic.Resultstructs to highlight issues (e.g., integrating linters likecredoor enforcing custom coding practices).How the Lexical language server processes messages
mainThe
serverapplication is the entry point for Lexical. When started (e.g., viastart_lexical.sh), it initializes a transport layer that reads JsonRPC from standard input and writes responses to standard output.Message Flow
- Reception: A message is received via the transport.
- Parsing: The message is parsed into either an
LSP Requestor anLSP Notification. - Routing:
- Lifecycle Messages: The
lexical server processhandles 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/1to determine the appropriate handler, creates a task, and executes it.
- Lifecycle Messages: The
Understand Lexical's core abstractions and protocols
mainLexical 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.Convertibleprotocol: Used to centralize logic for converting LSP data structures into Elixir terms when the conversion is not trivial.Lexical.Server.Transportbehaviour: Responsible for reading, writing, serializing, and deserializing messages between the LSP client and the Lexical server. TheLexical.Server.Transport.StdIOmodule provides the implementation for standard I/O.Lexical.Completion.Translatableprotocol: Defines how Elixir language constructs (like behaviour callbacks) are converted into LSP constructs (like completion items). Implementations can be found inLexical.Server.CodeIntelligence.Completion.Translations.
Use Code Mods to transform documents
mainCode 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_controlsub-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 (
_).
Understand LSP message types in Lexical
mainLexical 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.RequestsLexical.Protocol.ResponsesLexical.Protocol.Notifications
Implement a Provider Handler
mainA 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:
- The request to handle.
- A
%Lexical.Server.Configuration{}object.
- Behavior: The function can reply to the request, ignore it, or perform other actions.
- Arguments: The function must accept two arguments:
How Lexical's dual-VM architecture works
mainLexical uses a unique architecture to provide an isolated and powerful development environment. When Lexical starts, it boots two separate Erlang Virtual Machines (VMs):
- Lexical VM: Runs the language server itself and its dependencies.
- 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).
Understand the Lexical umbrella application structure
mainLexical 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 theprotocolapp.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, andelixir_sense) to prevent version conflicts.server: The main language server entry point.