Skip to content

Add existdb-openapi support for eXist 7.0+; retain atom-editor for 6.x compat#68

Open
joewiz wants to merge 2 commits into
wolfgangmm:masterfrom
joewiz:feature/language-support
Open

Add existdb-openapi support for eXist 7.0+; retain atom-editor for 6.x compat#68
joewiz wants to merge 2 commits into
wolfgangmm:masterfrom
joewiz:feature/language-support

Conversation

@joewiz
Copy link
Copy Markdown
Contributor

@joewiz joewiz commented Mar 14, 2026

Summary

  • Use eXist-db 7.0+'s built-in existdb-openapi (formerly exist-api) for language services: diagnostics, completions, hover, definition, references, symbols, and cursor-based query execution.
  • Keep the bundled atom-editor-support XAR as the backward-compatibility path for eXist 6.x users — the langserver runtime-detects which one is available and routes accordingly.
  • Switch from GET-based endpoints with client-side import resolution to POST JSON endpoints with server-side import resolution (for the 7.0+ path).
  • Support multi-error diagnostics (was: single error from util:compile-query).
  • Remove ~120 lines of client-side import parsing and resolution code.
  • Cursor-based query execution via cursor:eval / cursor:fetch / cursor:close — replaces legacy POST /apps/atom-editor/execute on 7.0+; legacy path retained as fallback for 6.x.
  • Output format settings — configurable serialization method and indent, with status bar, quick pick, and Lucene highlight-matches auto-detection.
  • Capability discovery via GET /api/langservice/capabilities — replaces the previous dummy-query probe.

Closes #15request: and other eXist-db built-in namespace prefixes no longer trigger false XPST0081 errors, since lang:diagnostics resolves them correctly (unlike the old util:compile-query path).

Server compatibility matrix

eXist version Path Source
7.0+ (recommended) existdb-openapi /api/langservice/* + /api/query/* Ships with eXist 7.0+; no install needed
6.x (legacy) Bundled atom-editor-support XAR + /apps/atom-editor/* Prompted on first connect; basic features only

On 7.0+, the new features added in this PR (cursor pagination, signature help, rename, semantic tokens, find references, enhanced symbols) are fully available. On 6.x, only what atom-editor-support historically provided works; the new features are silently disabled.

What changed

Capability detection (eXist 7.0+ vs 6.x)

  • server.ts calls GET /api/langservice/capabilities at connect time. If the endpoint responds with cursor.available === true, route through existdb-openapi. Otherwise check whether the bundled atom-editor XAR is installed; prompt if missing.

Bundled XAR

  • Removed resources/language-support-1.0.0.xar (intermediate scaffolding; no longer needed once existdb-openapi ships with eXist core).
  • Restored resources/atom-editor-1.1.0.xar for 6.x backward compat.
  • utils.ts checkServer() checks for http://exist-db.org/apps/atom-editor (unchanged from upstream master).

Cursor-based query execution (7.0+ path)

Replaces the legacy POST /apps/atom-editor/execute with a three-step cursor-based approach against existdb-openapi's /api/query family:

  1. POST /api/query with {query, …} — server-side maps to cursor:eval(...), returns { cursor, items, elapsed }.
  2. GET /api/query/{id}/results?start=…&count=… — server-side maps to cursor:fetch(...), returns a page of items. Serialization options (method, indent, highlight-matches) ride along as additional query params.
  3. DELETE /api/query/{id} — server-side maps to cursor:close(...), releases server-side resources.

server/src/analyzed-document.ts:

  • evalQuery() POSTs /api/query, then auto-fetches the first page via the cursor results endpoint.
  • fetchResults() and closeCursor() support on-demand paging and cleanup.
  • Legacy executeQuery() preserved as the 6.x fallback (POSTs /apps/atom-editor/execute).

server/src/server.ts:

  • Capability detection via GET /api/langservice/capabilities (replaces the dummy query: '1' probe).
  • execute command routes to cursor-based or legacy path based on capability.
  • New fetch and closeCursor commands.

client/src/extension.ts:

  • Execute command handles both cursor-based (array items) and legacy (string) results.
  • New existdb.loadMoreResults command fetches the next page from an open cursor.
  • Previous cursors are automatically closed before new queries.

client/src/query-results-provider.ts:

  • CursorState interface tracks active cursor, total hits, fetched count, output mode.
  • appendResults() appends pages; clearCursor() resets state.

Output format settings

Settings (package.json):

  • existdb.query.serializationMethod — default adaptive, enum: adaptive, xml, json, text.
  • existdb.query.indent — default true, controls indentation of results.

Client (client/src/extension.ts):

  • New existdb.setOutputFormat command — quick pick: Adaptive, XML, JSON, Text (with checkmark on current).
  • Clickable status bar item showing current output format (e.g., Adaptive).
  • getSerializationOptions() builds options object from settings, auto-enables highlight-matches: both when query contains ft:query or ft:search.
  • Options threaded through execute and fetch commands to server.

Server:

  • evalQuery() and fetchResults() accept optional serializationOptions parameter.
  • Options forwarded as query parameters on the cursor /results GET.

server/src/linting.ts

  • serverLint(): POST to /api/langservice/diagnostics (was PUT to compile.xql).
  • Handles structured JSON array of diagnostics with mapSeverity() (was regex-parsing a single error message).
  • Converts 1-indexed server responses to 0-indexed LSP protocol values.

server/src/analyzed-document.ts (besides cursor)

  • getCompletions(): POST full query text to /api/langservice/completions (was GET with resolved imports as query params).
  • getHoverRemote(): POST query + position to /api/langservice/hover (was GET with function signature).
  • gotoDefinitionRemote(): POST query + position to /api/langservice/definition (was GET with signature + client-side file reading).
  • Removed: resolveImports(), parseImports(), getParameters(), getOptions(), imports map, Import interface.
  • Kept: local symbol lookup as first-try for hover/definition (no server roundtrip).

server/src/server.ts

  • Pass document text to getCompletions() and textDocument to getHover().
  • Semantic tokens provider: highlights function/variable declarations using server-side lang:symbols, local fallback.
  • Document formatting provider: XQuery formatting via Prettier + prettier-plugin-xquery.
  • Enhanced document symbols: server-side lang:symbols for richer outline (return types, parameter types), local fallback.

Tests

27 tests passing locally. Run with:

npm test

test/cursor-execution.spec.ts (10 tests):

  • evalQuery: POST /api/query + GET /api/query/{id}/results two-step, output mode detection, custom page size, error propagation.
  • fetchResults: GET with start/count as query params, empty results.
  • closeCursor: DELETE /api/query/{id}, response shape handling.
  • executeQuery legacy: atom-editor endpoint, form-encoded body.

test/capability-check.spec.ts (7 tests):

  • Capability detection via GET /api/langservice/capabilities: 200 with cursor available, 404, unreachable, missing cursor block, cursor.available=false.
  • Command routing: evalQuery when capable, legacy when not.

test/serialization-options.spec.ts (10 tests):

  • Options pass-through in evalQuery and fetchResults as query params on the GET.
  • Options omitted when undefined.
  • highlight-matches forwarding.
  • Lucene ft:query/ft:search regex detection (positive and negative cases).

Local AST: eXide's REx-generated XQuery 3.1 parser

For local symbol lookup (so hover and go-to-definition can short-circuit a server roundtrip when the cursor is on a known local function/variable), the langserver builds an AST client-side. This PR swaps the previous xqlint dependency (which produced false-positive warnings, e.g. #67, and had a heavy transitive dep tree) for eXide's REx-generated parser — the same parser eXide uses, generated from the W3C XQuery 3.1 EBNF.

server/src/parser/XQueryParser.js (the generated 3.1 parser) and server/src/parser/adapter.js (a normalization adapter that emits the same xqlint-shaped AST the existing server/src/ast.ts traversals expect) are vendored into the langserver. The XQuery 4.0 parser (XQueryParser40.js) is available in eXide and could be added later if the langserver ever needs 4.0-specific local symbol lookup; for now the 3.1 parser covers all current use cases.

Requirements

  • eXist-db 7.0+ (recommended): includes WebSocket support from eXist-db/exist#6145 (merged) and ships existdb-openapi built-in. No additional XAR install required for langserver features.
  • eXist-db 6.x (legacy compat): the langserver prompts to install the bundled atom-editor-support XAR on first connect. Reduced feature set (no cursor, no semantic tokens, no references, no enhanced symbols).
  • Roaster ≥ 1.8.0 — required by existdb-openapi (transitively, on 7.0+).

Test plan

  • Install langserver against eXist 7.0-SNAPSHOT (with existdb-openapi auto-installed); verify no install prompt appears.
  • Verify capabilities endpoint at /apps/existdb-openapi/api/langservice/capabilities.
  • Diagnostics: red squiggles on invalid XQuery (multi-error, correct line).
  • Completions: local + built-in functions appear as you type.
  • Hover: function signatures shown on hover.
  • Go-to-definition: jumps to function declaration.
  • Outline: document symbols with return types (server-enhanced).
  • Semantic highlighting: function/variable declarations highlighted.
  • XQuery formatting: Shift+Alt+F formats XQuery code via Prettier.
  • Cross-module go-to-definition: jumps to imported module functions.
  • Status bar: database icon shows workspace name, no install prompt when existdb-openapi is available.
  • No false XPST0081 error on request:get-parameter (VSCode objects to "request:" namespace prefix #15).
  • No false [W03] Unused variable warnings (false positive problem reports "unused variable" #67 — xqlint warnings removed).
  • Cursor-based execution: run query, verify cursor + first page returned.
  • Load More Results: fetch subsequent pages from open cursor.
  • Fallback against eXist 6.x: install atom-editor XAR via prompt, verify basic features (diagnostics + completions) work; cursor-based features gracefully disabled.
  • Output format status bar: shows current method, clickable to change.
  • Set Output Format: quick pick changes existdb.query.serializationMethod.
  • Serialization options: XML/JSON/Text modes produce correctly formatted output.
  • Lucene highlight: ft:query in query auto-enables highlight-matches.
  • npm test passes (27 tests).

🤖 Generated with Claude Code

@joewiz joewiz force-pushed the feature/language-support branch 2 times, most recently from 76430e2 to 41be7d6 Compare May 17, 2026 06:56
@joewiz joewiz changed the title Replace atom-editor-support with language-support XAR Add existdb-openapi support for eXist 7.0+; retain atom-editor for 6.x compat May 17, 2026
@joewiz joewiz force-pushed the feature/language-support branch from 9ed576b to 7f39eed Compare May 17, 2026 07:05
joewiz and others added 2 commits May 17, 2026 03:18
…fallback)

Replaces the previous atom-editor-only backend with existdb-openapi's
language-service endpoints for eXist 7.0+, while preserving the bundled
atom-editor-support XAR as a backward-compatibility path for eXist 6.x.

## Server compatibility matrix

| eXist version | Path | Source |
|---|---|---|
| 7.0+ (recommended) | existdb-openapi /api/langservice/* + /api/query/* | Ships with eXist 7.0+; no install needed |
| 6.x (legacy)       | Bundled atom-editor-support XAR + /apps/atom-editor/* | Prompted on first connect; basic features only |

Capability detection: server.ts calls `GET /api/langservice/capabilities`
at connect time. If the endpoint responds with `cursor.available === true`,
route through existdb-openapi. Otherwise check whether the bundled
atom-editor XAR is installed; prompt if missing.

## Backend wiring

- linting.ts: serverLint() POSTs to /api/langservice/diagnostics with
  structured JSON response; handles multi-error diagnostics via
  mapSeverity() (was: regex-parsing a single error from compile.xql).
- analyzed-document.ts: getCompletions/getHoverRemote/gotoDefinitionRemote
  POST full query text to /api/langservice/{completions,hover,definition}.
  Removed resolveImports/parseImports/getParameters/imports map/Import
  interface (~120 lines of client-side import-resolution code).
- server.ts: capability detection via GET /api/langservice/capabilities.
- utils.ts: unchanged from upstream master (atom-editor XAR check retained).

Closes wolfgangmm#15 — request: and other built-in namespace prefixes no longer
trigger false XPST0081 errors, since lang:diagnostics resolves them
correctly (unlike the old util:compile-query path).

## Cursor-based query execution (7.0+ path)

Three-step cursor lifecycle against existdb-openapi's /api/query family:

1. POST /api/query → cursor:eval, returns { cursor, items, elapsed }
2. GET /api/query/{id}/results?start=…&count=… → cursor:fetch
3. DELETE /api/query/{id} → cursor:close

Legacy POST /apps/atom-editor/execute preserved as 6.x fallback.

Client (extension.ts): execute command handles both cursor-based (array
items) and legacy (string) results. New existdb.loadMoreResults command
fetches subsequent pages. Previous cursors automatically closed before
new queries.

Client (query-results-provider.ts): CursorState tracks active cursor,
total hits, fetched count, output mode. appendResults appends pages;
clearCursor resets state.

## New language service features

- Cross-module go-to-definition: handle "uri" field in definition response
  for cross-module navigation. Maps database paths to workspace file URIs
  using settings.path prefix.
- Semantic tokens provider: highlights function/variable declarations
  using server-side lang:symbols, local fallback.
- Document formatting provider: XQuery formatting via Prettier +
  prettier-plugin-xquery.
- Enhanced document symbols: server-side lang:symbols for richer outline
  (return types, parameter types), local fallback.
- Find-references provider (Shift+F12).
- Signature help + rename symbol providers.

## Output format settings

- Settings (package.json): existdb.query.serializationMethod (default
  "adaptive"; enum adaptive/xml/json/text), existdb.query.indent.
- Client: existdb.setOutputFormat quick-pick command; clickable status
  bar item showing current method. getSerializationOptions auto-enables
  highlight-matches: both when query contains ft:query/ft:search.
- Server: evalQuery/fetchResults accept optional serializationOptions;
  options forwarded as query params on the cursor /results GET.

## module-load-path field name

Standardized on "module-load-path" across all langservice request bodies
to align with existdb-openapi's OpenAPI contract (matches the
XQueryContext.setModuleLoadPath() API name). Legacy atom-editor fallback
path unchanged since that endpoint isn't under our control.

## Tests

26 new tests covering the new endpoints and routing logic:

- test/capability-check.spec.ts (7 tests): GET /api/langservice/capabilities
  probe semantics; routing of evalQuery vs legacy executeQuery.
- test/cursor-execution.spec.ts (10 tests): POST /api/query →
  GET /api/query/{id}/results → DELETE /api/query/{id} cycle, output mode
  detection, custom page size, error propagation, legacy executeQuery.
- test/serialization-options.spec.ts (10 tests): options pass-through
  as query params on the GET, omission when undefined,
  highlight-matches forwarding, Lucene ft:query/ft:search regex.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
xqlint produced false-positive warnings (wolfgangmm#67) and brought a stale,
heavy dependency tree (~5 MB of transitive deps). After the previous
commit deferred all diagnostics to the server-side lang:diagnostics
endpoint, xqlint's only remaining role was producing a local AST for
symbol lookup (hover / go-to-definition without a server roundtrip
when the cursor is on a known local function/variable).

eXide ships a REx-generated parser for the W3C XQuery 3.1 EBNF, plus
an adapter that normalizes the parse tree to the same shape xqlint's
JSONParseTreeHandler emitted — so the consumer surface in
server/src/ast.ts (findNode, getAncestorOrSelf, getFunctionSignature)
keeps working unchanged. The xqlint warnings are dropped entirely;
errors continue to come from the server-side lang:diagnostics endpoint.

Files added (copied verbatim from eXist-db/eXide src/parser/):
- server/src/parser/XQueryParser.js (~1.1 MB, generated; XQ 3.1 grammar)
- server/src/parser/adapter.js (xqlint-compat AST normalization)

Both files have CommonJS exports, so plain require() works in Node.
Bundle size after webpack tree-shake + minify is ~775 KB (the parser
code minifies aggressively).

Files removed:
- server/src/xqlint.d.ts (no longer needed)

Dependency removed:
- server/package.json: drop "xqlint" (was github:eXistSolutions/xqlint#exist-syntax)

Tests added (test/parser.spec.ts, 8 tests):
- parseXQuery: trivial expression, function decl + call, invalid syntax
  (error reported without throwing), XQuery 3.1 features (FLWOR, type
  declarations, namespaces).
- AST traversal: identifies the function call under the cursor, distinguishes
  calls by arity, returns null when cursor is not on a FunctionCall, handles
  multi-line input with position resolution across lines.

Future: XQuery 4.0 parser (XQueryParser40.js, ~3 MB) is available in
eXide and could be lazy-loaded when the source declares "4.0", but
that's not needed for the langserver's current symbol-lookup use case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

VSCode objects to "request:" namespace prefix

1 participant