Loom integrates with Language Server Protocol (LSP) servers to provide real-time diagnostics, code intelligence, and language-specific insights to the AI assistant.
Overview
The LSP integration allows Loom to:
Get Diagnostics Syntax errors, warnings, and hints from language servers
Type Checking Real-time type errors and validation
Linting Code style and quality issues
Context Awareness AI uses diagnostics to improve suggestions
Configuration
Configure LSP servers in .loom.toml:
[ lsp ]
servers = [
# Elixir Language Server
{ name = "elixir" , command = "elixir-ls" , language_id = "elixir" },
# TypeScript Language Server
{ name = "typescript" , command = "typescript-language-server" , args = [ "--stdio" ], language_id = "typescript" },
# Rust Analyzer
{ name = "rust" , command = "rust-analyzer" , language_id = "rust" }
]
Configuration Fields
Unique identifier for this LSP server
Executable command to start the language server
Command-line arguments for the server (default: [])
Language identifier (e.g., “elixir”, “typescript”, “python”)
Supported Language Servers
Elixir
{ name = "elixir" , command = "elixir-ls" , language_id = "elixir" }
Installation :
# Via Homebrew (macOS)
brew install elixir-ls
# Or download from GitHub releases
# https://github.com/elixir-lsp/elixir-ls/releases
Provides :
Syntax errors
Unused variables
Type mismatches
Module not found errors
TypeScript / JavaScript
{ name = "typescript" , command = "typescript-language-server" , args = [ "--stdio" ] , language_id = "typescript" }
Installation :
npm install -g typescript-language-server typescript
Provides :
Type errors
Unused imports
ESLint integration
Missing properties
Python
{ name = "python" , command = "pylsp" , language_id = "python" }
Installation :
pip install python-lsp-server
Provides :
Syntax errors
Import errors
Undefined names
Type hints validation
Rust
{ name = "rust" , command = "rust-analyzer" , language_id = "rust" }
Installation :
rustup component add rust-analyzer
Provides :
Compiler errors
Borrow checker diagnostics
Type mismatches
Unused code warnings
{ name = "go" , command = "gopls" , language_id = "go" }
Installation :
go install golang.org/x/tools/gopls@latest
LSP Client Architecture
Loom’s LSP client is implemented as a GenServer:
defmodule Loom . LSP . Client do
use GenServer
# Manages lifecycle of language server process
end
Client Lifecycle
Start Process
Spawn the language server as a Port: Port . open ({ :spawn_executable , "elixir-ls" }, [
:binary ,
:exit_status ,
:use_stdio
])
Initialize
Send initialize request with root URI: {
"jsonrpc" : "2.0" ,
"id" : 1 ,
"method" : "initialize" ,
"params" : {
"rootUri" : "file:///path/to/project" ,
"capabilities" : {}
}
}
Notify Initialized
After receiving response, send initialized notification.
Open Files
Notify server when files are opened: Loom . LSP . Client . did_open ( "elixir" , "lib/user.ex" , "elixir" )
Collect Diagnostics
Receive textDocument/publishDiagnostics notifications from server.
Using LSP Diagnostics
The AI can query diagnostics using the lsp_diagnostics tool:
# Get all diagnostics
lsp_diagnostics ()
# Get diagnostics for specific file
lsp_diagnostics ( file_path: "lib/user.ex" )
Example Output
lib/user.ex:42:5: warning: variable "result" is unused
lib/auth.ex:18:12: error: undefined function validate/1
lib/core.ex:5:1: hint: missing @moduledoc
Each diagnostic includes:
Character position in line (1-based)
One of: error, warning, info, hint
Human-readable error message
Diagnostic source (e.g., “elixir”, “eslint”)
LSP Client API
Start Client
Loom . LSP . Client . start_link (
name: :elixir_ls ,
command: "elixir-ls" ,
args: []
)
Initialize Connection
Loom . LSP . Client . initialize ( :elixir_ls , "/path/to/project" )
Notify File Opened
Loom . LSP . Client . did_open (
:elixir_ls ,
"lib/user.ex" ,
"elixir"
)
Notify File Closed
Loom . LSP . Client . did_close ( :elixir_ls , "lib/user.ex" )
Get Diagnostics
# For specific file
Loom . LSP . Client . get_diagnostics ( :elixir_ls , "lib/user.ex" )
# => {:ok, [%{line: 42, severity: :warning, message: "..."}]}
# All diagnostics
Loom . LSP . Client . all_diagnostics ( :elixir_ls )
# => {:ok, %{"file:///path/to/lib/user.ex" => [...], ...}}
Check Status
Loom . LSP . Client . status ( :elixir_ls )
# => :ready | :starting | :stopped | :not_running
Shutdown
Loom . LSP . Client . shutdown ( :elixir_ls )
Protocol Implementation
LSP uses JSON-RPC 2.0 with a Content-Length header:
Content-Length: 123\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
Request/Response
Request :
defp send_request (port, id, method, params) do
msg = Protocol . encode_request (id, method, params)
Port . command (port, msg)
end
Response Handling :
def handle_info ({port, { :data , data}}, state) do
buffer = state.buffer <> data
case Protocol . extract_message (buffer) do
{ :ok , msg, remaining} ->
handle_lsp_message (msg, %{state | buffer: remaining})
{ :incomplete , _ } ->
{ :noreply , %{state | buffer: buffer}}
end
end
Notifications
didOpen :
{
"jsonrpc" : "2.0" ,
"method" : "textDocument/didOpen" ,
"params" : {
"textDocument" : {
"uri" : "file:///path/to/file.ex" ,
"languageId" : "elixir" ,
"version" : 1 ,
"text" : "defmodule User do..."
}
}
}
publishDiagnostics :
{
"jsonrpc" : "2.0" ,
"method" : "textDocument/publishDiagnostics" ,
"params" : {
"uri" : "file:///path/to/file.ex" ,
"diagnostics" : [
{
"range" : {
"start" : { "line" : 41 , "character" : 4 },
"end" : { "line" : 41 , "character" : 10 }
},
"severity" : 2 ,
"message" : "unused variable 'result'" ,
"source" : "elixir"
}
]
}
}
AI Integration
When diagnostics are available, the AI can:
Fix Errors Automatically
You: Fix all the errors in user.ex
Loom: [Queries lsp_diagnostics for user.ex]
[Sees: "undefined function validate/1"]
[Uses file_edit to add the missing function]
Proactive Suggestions
You: Refactor the authentication module
Loom: [Checks diagnostics first]
[Finds: 3 unused variables]
I notice there are unused variables. I'll clean those up during the refactor.
Type-Aware Edits
You: Add a new field to the User struct
Loom: [Queries diagnostics after edit]
[Detects: Type errors in related functions]
I've updated the struct and fixed type errors in 4 related functions.
Supervisor Integration
LSP clients are supervised:
defmodule Loom . LSP . Supervisor do
use Supervisor
def start_link (opts) do
Supervisor . start_link ( __MODULE__ , opts, name: __MODULE__ )
end
def init ( _opts ) do
# Start configured LSP clients
servers = Loom . Config . get ( :lsp , :servers ) || []
children =
Enum . map (servers, fn config ->
%{
id: config.name,
start: { Loom . LSP . Client , :start_link , [config]}
}
end )
Supervisor . init (children, strategy: :one_for_one )
end
end
If a language server crashes, it’s automatically restarted.
Troubleshooting
Server Not Starting
Check if command exists :
Check logs :
Loom . LSP . Client . status ( :elixir_ls )
# => :not_running
Common issues :
LSP binary not in PATH
Incorrect command name
Missing dependencies
No Diagnostics
Ensure files are opened :
Loom . LSP . Client . did_open ( :elixir_ls , "lib/user.ex" , "elixir" )
Check server status :
Loom . LSP . Client . status ( :elixir_ls )
# Should be: :ready
Verify server supports diagnostics :
Some minimal LSP servers don’t implement publishDiagnostics.
Stale Diagnostics
Diagnostics may become stale if files are modified outside Loom.
Solution : Restart the LSP client:
Loom . LSP . Client . shutdown ( :elixir_ls )
# Supervisor will restart it automatically
Startup Time
Language servers can be slow to initialize:
Elixir LS : 5-10 seconds
TypeScript : 2-5 seconds
Rust Analyzer : 10-30 seconds
Loom starts LSP servers in the background. Diagnostics become available once initialization completes.
Memory Usage
Each LSP server runs as a separate process:
Elixir LS : ~200-500 MB
TypeScript : ~150-300 MB
Rust Analyzer : ~500-1000 MB
For large projects, multiple language servers can consume significant memory.
File Updates
Loom notifies the LSP server when files are opened/edited. For optimal diagnostics:
Open files before editing
Keep LSP connection alive during session
Close files when done to free memory
Future Enhancements
Planned Features
Auto-completion : Code suggestions from LSP
Go to Definition : Navigate to symbol definitions
Hover Information : Type and documentation on hover
Code Actions : Quick fixes from LSP
Formatting : Use LSP for code formatting
Next Steps
Tools Learn about the lsp_diagnostics tool
MCP Connect external tools via Model Context Protocol
Configuration Configure LSP servers in .loom.toml
LSP Spec Read the LSP specification