Language Server (LSP)
Sema includes a built-in Language Server Protocol server that provides IDE features for any editor with LSP support. The server communicates over stdio using the standard LSP JSON-RPC protocol.
sema lspFeatures
Diagnostics
Real-time error reporting as you type. The LSP runs two analysis passes:
- Parse diagnostics (errors) — syntax errors like unclosed parentheses, unterminated strings, and invalid tokens. Uses error recovery to report multiple errors at once.
- Compile diagnostics (warnings) — deeper issues caught by the bytecode compilation pipeline, such as unbound variables, arity mismatches, and invalid special form usage. Only runs when parsing succeeds (no false positives from incomplete code).
Completion
Context-aware autocompletion triggered by ( and space characters. Completes from three sources:
- Special forms —
define,defun,lambda,if,cond,let,match,try,import, etc. - Standard library — all built-in functions including namespaced ones like
string/trim,file/read,http/get - User definitions — top-level
define,defun,defn,defmacro,defagent, anddeftoolforms in the current document. Cached definitions survive syntax errors while typing.
Hover
Hover over any symbol to see documentation:
- Builtins & special forms — shows documentation pulled from the stdlib reference docs, including descriptions and usage examples
- User-defined functions — shows the function signature with parameter list
- Imported symbols — shows the signature and which module it was imported from
- Other builtins — shows the symbol name and whether it's a special form or built-in function
Hover continues to work even when the file has syntax errors — the LSP uses error recovery to parse as much as possible.
Go to Definition
Jump to the definition of symbols with precise cursor targeting:
- User definitions —
define,defun,defn,defmacro,defagent,deftool— jumps directly to the name of the definition (e.g.,fooin(defun foo ...)), not the entire form - Cross-file definitions — if a symbol is not defined locally, the LSP follows
importandloadpaths to find the definition in other files. Imported file parse results are cached by modification time for performance. - Imports — go-to-definition on
(import "utils.sema")or(load "config.sema")opens the referenced file. Supports relative paths, absolute paths, and package imports
Go-to-definition works even when the file has syntax errors.
Find All References
Find every occurrence of a symbol across all open documents. Searches all files currently tracked by the LSP server.
Document Symbols
Outline view and breadcrumbs for the current file. Lists all top-level definitions (define, defun, defn, defmacro, defagent, deftool) with their kind, full form range, and precise name selection range.
Workspace Symbols
Fuzzy search for symbols across all open documents. Triggered via the command palette or keybinding (e.g., Ctrl+T in VS Code). Matches symbol names case-insensitively.
Signature Help
Parameter hints shown while typing inside function calls. Triggered by ( and space characters:
- User-defined functions — shows the function name and parameter list with active parameter highlighting
- Imported functions — shows the signature from the imported module
- Built-in functions — shows the function documentation from the stdlib reference
Rename
Rename a user-defined symbol across all open documents:
- Prepare rename — verifies the cursor is on a renameable symbol (not a built-in or special form) and returns its range
- Rename — finds all occurrences across all open documents and generates text edits
Code Lenses
Every top-level expression shows a ▶ Run code lens above it. Clicking it evaluates all forms up to and including that expression in a sandboxed subprocess using sema eval, and reports the result (value, stdout, stderr, timing) back to the editor via a custom sema/evalResult notification.
Editor Setup
Helix
Add to your ~/.config/helix/languages.toml:
[[language]]
name = "sema"
language-servers = ["sema-lsp"]
[language-server.sema-lsp]
command = "sema"
args = ["lsp"]Neovim
Using nvim-lspconfig:
local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')
if not configs.sema then
configs.sema = {
default_config = {
cmd = { 'sema', 'lsp' },
filetypes = { 'sema' },
root_dir = lspconfig.util.root_pattern('sema.toml', '.git'),
},
}
end
lspconfig.sema.setup({})Zed
Zed can be configured to use the LSP by adding a language server entry. See the Zed documentation for configuring custom language servers.
VS Code
The VS Code extension does not yet include LSP integration. For now, the extension provides syntax highlighting only. LSP support is planned.
Architecture
The LSP server uses tower-lsp and a dedicated backend thread architecture:
- Async layer — tower-lsp handles the JSON-RPC protocol over stdio. Async handlers forward requests to the backend thread via
tokio::sync::mpscchannels and receive responses viatokio::sync::oneshotchannels. - Backend thread — a single
std::threadowns allRc-based state (parsed ASTs, interpreter environment, document cache). This avoidsSend/Syncconstraints while keeping the server responsive. - Import cache — parsed results for imported files are cached by file path and modification time, avoiding redundant re-parsing on every request.
- Subprocess execution — code lens "Run" commands spawn a separate
sema evalprocess in a sandboxed environment, keeping the backend thread free for diagnostics and completions.
Custom Notifications
The server sends a custom sema/evalResult notification after executing a code lens. The payload includes:
| Field | Type | Description |
|---|---|---|
uri | string | Document URI |
range | Range | Range of the evaluated form |
kind | string | Always "run" |
value | string? | Return value (if successful) |
stdout | string | Captured stdout |
stderr | string | Captured stderr |
ok | boolean | Whether evaluation succeeded |
error | string? | Error message (if failed) |
elapsedMs | number | Execution time in milliseconds |
Status
The LSP server provides a complete editing experience for Sema. Planned additions include:
- VS Code extension integration