| docs | ||
| src | ||
| .gitignore | ||
| Makefile | ||
| README.org | ||
| sextant.asd | ||
| test-diagnostics.lisp | ||
| test.lisp | ||
| TODO.md | ||
Sextant - A Common Lisp Language Server
Sextant
A Language Server Protocol (LSP) implementation for Common Lisp, written in Common Lisp.
Sextant runs as an SBCL process that speaks LSP over stdio. It queries its own running Lisp image for symbol information, giving you a full-featured editing experience in any LSP-capable editor.
Features
Core
- Hover documentation - Symbol type, arglist, docstring (
textDocument/hover) - Completions - Symbol name completion across all packages (
textDocument/completion) - Completion resolve - Lazy-load documentation per completion item (
completionItem/resolve) - Go to definition - Jump to source via SBCL introspection (
textDocument/definition) - Find references - Who calls, binds, references, macroexpands (
textDocument/references) - Signature help - Function arglist display (
textDocument/signatureHelp) - Diagnostics / Linting - Compile-time warnings, unused variables, type errors, undefined functions (
textDocument/publishDiagnostics)
Navigation & Structure
- Document symbols - Outline of all
def*forms (textDocument/documentSymbol) - Workspace symbols - Search symbols across all packages (
workspace/symbol) - Call hierarchy - Incoming calls via
sb-introspect:who-calls(callHierarchy/incomingCalls) - Folding ranges - Fold top-level forms (
textDocument/foldingRange) - Selection range - Expand/shrink selection by s-expression (
textDocument/selectionRange)
Editing
- Rename - Rename symbol across current file (
textDocument/rename) - Formatting - S-expression-aware indentation (
textDocument/formatting) - Code actions - Add package prefix, export symbol, insert defpackage (
textDocument/codeAction) - Linked editing range - Edit all occurrences simultaneously (
textDocument/linkedEditingRange)
Display
- Document highlight - Highlight all occurrences of symbol under cursor (
textDocument/documentHighlight) - Semantic tokens - Rich highlighting: functions, macros, special forms, variables, keywords, classes (
textDocument/semanticTokens/full) - Inlay hints - Show parameter names at function call sites (
textDocument/inlayHint) - Code lens - Reference counts above definitions (
textDocument/codeLens)
Other
- Incremental document sync - Efficient partial updates (
change: 2) - Zero external deps at runtime - Queries the live SBCL image directly
- No Swank/Slynk required - Self-contained LSP server
Building
cd sextant
make
This produces a sextant executable (~16MB compressed).
Emacs Setup (eglot)
;; Add to your init.el or config
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(lisp-mode . ("/path/to/sextant"))))
;; Auto-start on lisp files (optional)
(add-hook 'lisp-mode-hook 'eglot-ensure)
Or with lsp-mode:
(with-eval-after-load 'lsp-mode
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection '("/path/to/sextant"))
:major-modes '(lisp-mode)
:server-id 'sextant)))
(add-hook 'lisp-mode-hook #'lsp)
Neovim Setup (LazyVim)
Add to ~/.config/nvim/lua/plugins/sextant.lua:
return {
{
"neovim/nvim-lspconfig",
opts = function(_, opts)
local configs = require("lspconfig.configs")
if not configs.sextant then
local util = require("lspconfig.util")
configs.sextant = {
default_config = {
cmd = { "/path/to/sextant" },
filetypes = { "lisp" },
root_dir = util.root_pattern(".git", "*.asd", "*.asdf") or util.path.dirname,
settings = {},
},
}
end
require("lspconfig").sextant.setup({})
end,
},
}
To enable inlay hints in Neovim:
vim.lsp.inlay_hint.enable(true)
Helix Setup
Add to ~/.config/helix/languages.toml:
[[language]]
name = "lisp"
language-servers = ["sextant"]
[language-server.sextant]
command = "/path/to/sextant"
Architecture
sextant/
sextant.asd ; ASDF system definition
Makefile ; Build script
src/
package.lisp ; Package definition
json.lisp ; Minimal JSON reader/writer (no deps)
transport.lisp ; LSP JSON-RPC over stdio
document.lisp ; Open file tracking, s-expression utilities
lisp-introspection.lisp ; Query running SBCL for docs/completions/defs/refs
diagnostics.lisp ; Linting via compile-file + SBCL condition capture
handlers.lisp ; LSP method dispatch (all 22 methods)
server.lisp ; Main message loop
main.lisp ; Entry point
How It Works
Unlike most language servers that do static analysis, Sextant queries a live Common Lisp
image. When you hover over format, it calls (documentation 'format 'function) and
(sb-introspect:function-lambda-list 'format) in its own SBCL process.
This means:
- All of CL's 978 standard symbols are available immediately
- Quicklisp packages can be loaded into the server for their docs too
- Macros, reader macros, and dynamic features Just Work
For references and call hierarchy, it uses sb-introspect:who-calls,
sb-introspect:who-binds, sb-introspect:who-references, and
sb-introspect:who-macroexpands.
Diagnostics / Linting
Sextant compiles your buffer using compile-file and captures SBCL's compiler
conditions as LSP diagnostics. These appear as inline warnings and errors in your
editor.
What it catches:
- Unused variables - "The variable X is defined but never used"
- Undefined functions - "undefined function: FOO"
- Undefined variables - "undefined variable: BAR"
- Wrong argument count - "called with 3 arguments, but wants exactly 1"
- Type mismatches - "Constant \"hello\" conflicts with its asserted type NUMBER"
- Unbalanced parentheses - Reader pre-check catches these before compilation
- Reader syntax errors - Bad reader macros, malformed strings, etc.
Diagnostics run automatically when you open, edit, or save a file. Edits are debounced (0.5s) so the compiler is not invoked on every keystroke.
Source positions are extracted from SBCL's internal compiler context
(sb-c::*compiler-error-context*), with fallback to symbol-name search in the
buffer text. This gives accurate positions for most warnings.
Using Sextant
Once connected, Sextant works in the background. Here are the features and how to use them in Emacs (eglot) and other editors:
Hover documentation
Move your cursor over any symbol. Sextant shows its type, arglist, and docstring.
- Emacs (eglot):
M-x eldoc(automatic in the echo area), orC-h .for a dedicated buffer - Neovim:
Kin normal mode - Helix:
Space k
Completions
Start typing a symbol name (at least 2 characters). Sextant offers completions from all loaded packages.
- Emacs (eglot):
C-M-iorM-TAB(completion-at-point) - Neovim: Automatic via nvim-cmp or similar
- Helix: Automatic
Go to definition
Jump to the source of any function, macro, or generic function.
- Emacs (eglot):
M-.(xref-find-definitions),M-,to jump back - Neovim:
gd - Helix:
gd
Signature help
See the arglist of the enclosing function as you type.
- Emacs (eglot): Automatic in the echo area when inside a function call
- Neovim: Automatic via LSP
- Helix: Automatic
Diagnostics
View compiler warnings and errors inline. Sextant underlines problematic code and shows fringe markers.
- Emacs (eglot/flymake):
M-x flymake-show-buffer-diagnosticsto list all,M-x flymake-goto-next-error(C-c ! n) andM-x flymake-goto-prev-error(C-c ! p) to navigate - Neovim:
]d/[dto navigate,Space eto list - Helix:
]d/[dto navigate
Debug Logging
Set SEXTANT_LOG to control the log file location:
SEXTANT_LOG=/tmp/sextant.log sextant
Default: /tmp/sextant.log
License
MIT