ElixirLS Documentation

repository·master·Indexed 23 days ago

https://github.com/elixir-lsp/elixir-ls

A community-maintained language server and debug adapter for Elixir. It provides code intelligence (completion, go-to-definition, documentation lookup) via the Language Server Protocol (LSP) and step-through debugging via the Debug Adapter Protocol (DAP). Features include Dialyzer integration, support for various IDE plugins (VS Code, Helix, Zed, Neovim, Emacs), and a built-in Model Context Protocol (MCP) server for LLM integration.

Tokens
14.8K
Snippets
29
Records
90
Agent score
80%

What's inside ElixirLS

  1. Overview of ElixirLS features and components

    master

    ElixirLS consists of two primary components:

    1. Language Server: Drives code intelligence using the Language Server Protocol (LSP).
    2. Debug Adapter: Enables step-through debugging of Elixir projects using the Debug Adapter Protocol (DAP).

    Key Features

    • Debugging: Full debugger support.
    • Dialyzer Integration: Automatic, incremental Dialyzer analysis with inline @spec suggestions based on inferred success typings.
    • Code Intelligence: Documentation lookup on hover, go-to-definition, code completion, and finding references to functions/modules.
    • Project Navigation: Quick symbol lookup in the current file, workspace, and stdlib (both Elixir and Erlang).
    • Code Quality: Inline reporting of build warnings/errors and code formatting.
    IMPORTANT

    On its first run, Dialyzer builds a PLT cache, which can take 10+ minutes of high CPU usage. You can disable Dialyzer in your settings if you wish to avoid this.

  2. How code completion works in ElixirLS

    master

    ElixirLS provides advanced code completion by combining two mechanisms:

    1. Reflection: Retrieves information about compiled modules via Erlang and Elixir APIs. This provides high precision but may be outdated if the current file is uncompiled or contains errors.
    2. AST Analysis: Analyzes the current text buffer. This is used for on-demand completion of symbols in the file currently being edited, though it has limitations due to Elixir's metaprogramming nature.

    Supported completions include keywords, special forms, functions, macros, modules, variables, struct fields (if explicitly stated or inferred), atom map keys, attributes, types, behaviour callbacks, protocol functions, and spec-defined keys or return types.

  3. How ElixirLS handles JSON-RPC communication

    master

    ElixirLS communicates with clients using UTF-8 encoded JSON-RPC based protocols (LSP and DAP).

    To ensure all communication follows this protocol, ElixirLS overrides the standard BEAM :user (standard IO) and :stderr processes with ElixirLS.Utils.OutputDevice (defined in ElixirLS.Utils.WireProtocol).

    • Reading: ElixirLS.Utils.PacketStream reads and decodes protocol messages using Stream.resource/3 and IO.binread/2 until :eof.
    • Writing: Output messages are dispatched via ElixirLS.LanguageServer.JsonRpc or ElixirLS.DebugAdapter.Output.
  4. Use Function, Conditional, and Hit breakpoints

    master

    ElixirLS supports several advanced breakpoint types:

    • Function Breakpoints: Breaks on the first line of every clause of a specific function. Use the MFA format: Module.function/arity (e.g., MyModule.foo/1). Only works for public functions.
    • Conditional Breakpoints: Evaluates an Elixir expression. The breakpoint only triggers if the expression is true. Note: The evaluator has limitations and may not support macros or all built-in functions.
    • Hit Conditions: An integer value. The breakpoint only triggers after the specified number of times it has been hit.
    • Log Points: Logs a message to standard output without stopping execution. Use curly braces for interpolation, e.g., my_var is {inspect(my_var)}. Use \{ and \} to escape braces. Note: Log points are not supported on function breakpoints (DAP 1.51+).
  5. How debugging works in ElixirLS

    master

    ElixirLS implements the Debug Adapter Protocol (DAP). For breakpoints and stack traces to work in Elixir or Erlang, modules must be "interpreted" (using :int.ni/1 or :int.i/1).

    ElixirLS automatically interprets all modules within your Mix project and its dependencies before launching a Mix task. However, for .exs files (like tests), you must explicitly list them in the requireFiles array of your launch configuration so they are loaded and interpreted before the task runs.

  6. How the ElixirLS server process manages state

    master

    The main entry point is the ElixirLS.LanguageServer application, which starts a supervisor containing two primary components:

    1. ElixirLS.LanguageServer.Server: A named GenServer that maintains the project state. This state includes:
      • Server capabilities (negotiated with the client).
      • Project directory and root URI.
      • Settings.
      • Build and Dialyzer diagnostics.
      • Build process control flags.
      • A map of requests to match with responses.
      • Currently open files in the client.
    2. ElixirLS.LanguageServer.JsonRpc: A GenServer responsible for receiving and sending JSON-RPC packets through the redirected standard IO/stderr.
  7. Use the MCP Server for LLM integration

    master

    ElixirLS includes a built-in Model Context Protocol (MCP) server that allows Large Language Models (LLMs) like Claude to interact with your Elixir codebase. It provides tools for finding definitions, retrieving documentation, analyzing dependencies, and extracting type information.

    To use it, you must enable the server via the elixirLS.mcpEnabled setting. The server runs on a TCP port that can be discovered in the ElixirLS output logs or configured manually.

    {
      "mcpServers": {
        "elixir-ls-bridge": {
          "command": "elixir",
          "args": [
            "/absolute/path/to/elixir-ls/scripts/tcp_to_stdio_bridge.exs",
            "4328"
          ]
        }
      }
    }
  8. Understand Kakoune encoding and position limitations

    master

    When using kak-lsp with Kakoune, be aware of the following constraints:

    1. Encoding: kak-lsp only supports UTF-8 documents.
    2. Position Interpretation: By default, kak-lsp treats Position.character as an offset in UTF-8 code points. This differs from the LSP specification, which requires UTF-16 code units.

    While this works correctly for most characters within the Basic Multilingual Plane (BMP), it may cause issues with characters outside the BMP if the language server and the client disagree on the encoding method used for offsets.

  9. How the 'initialize' request triggers project setup

    master

    The project context is not active until the client sends the initialize request. Upon receiving this request, the server:

    1. Updates its state with the project's root path/uri.
    2. Updates its state with the supported client capabilities.
    3. Schedules a delayed build: The server waits briefly before starting the build. This delay allows the client to send workspace/didChangeConfiguration (e.g., to change the MIX_ENV) before the build starts.

    If no configuration change is received within 3 seconds, the server attempts to fetch configuration via workspace/configuration. If that fails or is unsupported, it falls back to default configuration and then triggers the builders and analyzers.

  10. How ElixirLS builds and reloads the project

    master

    The build process is managed by the ElixirLS.LanguageServer.Build module in a separate process. It involves:

    1. Dependency Management: Loading cached deps from Mix, clearing existing deps, and fetching required dependencies.
    2. Project Reloading: To ensure code introspection (via ElixirSense and :code APIs) works correctly, the server must reload the project. This involves:
      • Purging/deleting old code from the BEAM.
      • Compiling the mix.exs file to source project metadata.
      • Setting a custom build path using Mix.ProjectStack.post_config/1, which creates the .elixir_ls directory in your project.
    3. Compilation: Compiling all sources and retrieving diagnostics.
  11. Use Workspace Symbols for navigation

    master
    When Dialyzer integration is enabled, ElixirLS builds a comprehensive index of symbols, including modules, functions, types, and callbacks. This index covers the current workspace, all project dependencies, and the Elixir and Erlang standard libraries, allowing for rapid navigation to symbol definitions.
  12. How project analysis and Dialyzer work

    master

    After a successful build, ElixirLS triggers analysis, specifically focusing on Dialyzer to provide deep introspection.

    The Analysis Workflow:

    1. Trigger: ElixirLS.LanguageServer.Server.handle_build_result/3 receives the build result.
    2. Dialyzer Execution: If the build succeeded, dialyze/1 is called, delegating to ElixirLS.LanguageServer.Dialyzer.
    3. PLT Management: The server builds/manages the PLT (Persistent Lookup Table) for the Erlang/Elixir instance and project dependencies. This is a resource-intensive step.
    4. Incremental Analysis: To save resources, the server checks the integrity of the PLT and uses :dialyzer_plt.all_modules/1 to identify differences. It calculates the difference using MapSet to separate stale modules from non-stale ones, then delegates only the necessary work to ElixirLS.LanguageServer.Dialyzer.Analyzer.