xq-lsp
v1.6.0
Published
XQuery Language Server Protocol implementation
Readme
xq-lsp
Language Server Protocol implementation for XQuery, providing autocompletion, hover documentation, signature help, go-to-definition, and diagnostics.
Features
- Syntax highlighting — keywords, types, variables, operators, comments, and XQuery 4.0 tokens (VS Code extension)
- Completion — functions declared in the current file, imported modules, and variables in scope
- Hover — function signatures on hover
- Signature help — active parameter hints inside function calls
- Go to definition — jump to function or variable declarations, including across imported files
- Document symbols — file outline of all declared functions and variables
- Diagnostics — syntax errors, undeclared namespace prefixes, type mismatches, and unused symbols
Configuration
Summary: Fonto development
Place an lsp-config.xq file in the Fonto root folder (the one that holds the manifest.json) with the following content:
map {
'lib': 'fonto',
'glob': 'packages/**/*.xqm',
'import': map {
'generateLocationHints': false()
}
}General configuration
Place an lsp-config.xq file in your project root to enable glob-based import resolution. The server walks up from the current file's directory to find it.
The file contains an XPath 3.1 map expression with a glob key:
map {"glob": "src/**/*.xq"}Multiple patterns are written as an XPath sequence:
map {"glob": ("src/**/*.xq", "lib/**/*.xq")}The server expands the globs, analyzes each matched file, and indexes library modules by their declared namespace URI. Imports written without an at clause are then resolved by matching the namespace URI:
(: no "at" path needed — the server finds the file via the glob index :)
import module namespace util="http://example.com/util";Imports written as import module namespace prefix="uri" at "./other-file.xq" are followed
automatically; symbols from imported files are included in completions.
For files with syntax errors (common while editing), the server falls back to regex-based extraction so completions keep working.
Runtime built-ins
Use the lib key to load built-in definitions for a specific runtime:
| Value | Runtime |
| ----------- | ------------------------------------------------------------------------- |
| "fonto" | Fonto XML editor — fonto:* functions |
| "existdb" | eXist-db — all extension module functions |
map {"glob": "src/**/*.xq", "lib": "existdb"}Multiple libs use an XPath sequence:
map {"glob": "src/**/*.xq", "lib": ("fonto", "other")}eXist-db runtime
The "existdb" runtime bundles stub definitions for every eXist-db extension
module (util, xmldb, request, sm, ft, compression, …).
eXist-db automatically pre-declares a large set of namespace prefixes — you can
call util:log(…), xmldb:store(…), process:execute(…), etc. without any
import module namespace statement. When the existdb runtime is active the
server knows about these pre-declared prefixes and:
- suppresses false
XQST0081("namespace prefix not declared") diagnostics for all pre-declared namespaces - provides completions, hover, and go-to-definition for those modules
without requiring an explicit
import module namespacein your source
If you do choose to write explicit imports (e.g. for editor portability), the server handles those correctly too:
(: explicit import — also fine :)
import module namespace util = "http://exist-db.org/xquery/util";
util:uuid()Modules that require explicit import even in eXist-db (e.g. datetime,
httpclient, console, crypto, kwic) still need a import module
namespace declaration; the server offers quick-fix code actions to insert it.
(: lsp-config.xq in your project root :)
map {
"glob": "src/**/*.xq",
"lib": "existdb",
"import": map {
"generateLocationHints": false()
}
}The generateLocationHints: false() setting prevents the server from appending
at "…" paths to generated imports, which eXist-db does not need (it resolves
modules by namespace URI).
Fonto runtime
Then import the namespace in your XQuery files as usual — the server resolves completions, hover, and go-to-definition against the bundled definitions:
import module namespace fonto="http://www.fontoxml.com/functions";Location hints
Some runtimes do not like location hints (import namespace prefix="uri" at "Location hint";), turn
them off to prevent them from being generated:
map {
'import': map {
'generateLocationHints': false()
}
}Roadmap
- Completion of keywords — like
function,declareandancestor-or-self, which saves you some keystrokes - Context-depending completion — which prevent syntactical errors
- Treesitter parser — should improve performance and scalability for huge files
- XQuery 4 — the parser already supports this. Just make it work
- Arity checks — report when one calls
fn:document('too', 'many', 'arguments') - Register or discover known prefix/namespace combinations — if you have the
teinamespace declared in another file, you might mean that when you typetei:TEIin another file - Context items — doing
declare function xx () { bla };is an error: there is no context item. Check if there is a context item available when you use the context item expression (.) or a step expression. - Unused functions and variables — no one likes those. Except when prefixed with
_, then they make sense - More of the (static) errors in the spec at The spec at section F Error Conditions, whichever are easy to implement
Emacs
With eglot (built-in since Emacs 29)
Install xquery-mode from MELPA:
M-x package-install RET xquery-mode RETAdd to your init.el:
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(xquery-mode . ("npx" "xq-lsp" "--stdio"))))
(add-hook 'xquery-mode-hook #'eglot-ensure)Open any .xq file and eglot starts the server automatically. Run M-x eglot to start it manually.
Key bindings:
| Action | Key |
| ---------------- | ------------------------------------- |
| Completion | C-M-i |
| Hover / docs | C-c C-d |
| Signature help | automatic in minibuffer |
| Go to definition | M-. |
| Go back | M-, |
| Document symbols | M-x imenu |
| Diagnostics | M-x flymake-show-buffer-diagnostics |
With lsp-mode
(with-eval-after-load 'lsp-mode
(add-to-list 'lsp-language-id-configuration '(xquery-mode . "xquery"))
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection '("npx" "xq-lsp" "--stdio"))
:major-modes '(xquery-mode)
:language-id "xquery"
:server-id 'xquery-lsp)))
(add-hook 'xquery-mode-hook #'lsp)VS Code
A minimal VS Code extension lives in editors/vscode/.
Install from marketplace
Launch VS Code Quick Open (Ctrl+P or Cmd+P), paste the following command, and press enter.
ext install elliat.xquery-lsp-vscodeInstall by hand
cd editors/vscode
npm installOpen the editors/vscode folder in VS Code and press F5 to launch a development host with the
extension active. To install it permanently, package it with
vsce:
npx vsce package
code --install-extension xquery-lsp-vscode-*.vsixThe extension registers .xq, .xql, .xqm, .xqy, and .xquery files as XQuery, provides syntax highlighting via a bundled TextMate grammar, and starts xq-lsp automatically when you open one.
Claude Code
A plugin lives in editors/claude-code/ that registers xq-lsp as a native LSP server for Claude Code, giving diagnostics, go-to-definition, find-references, and hover for XQuery files.
Make sure the xq-lsp binary is on PATH:
npm install -g xq-lspThen, from within Claude Code:
/plugin marketplace add DrRataplan/xq-lsp
/plugin install xquery-lsp@xq-lspSee editors/claude-code/README.md for local-development setup and details on what the plugin configures.
