From c6a119ba18505b423a208d5f6c5a865d07a263b7 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Thu, 12 Mar 2026 23:39:55 -0400 Subject: [PATCH 01/13] Add LSP API endpoints: hover, definition, completions, symbols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registers four new POST routes under /api/query/ backed by the lsp:* XQuery module added to eXist-db in feature/lsp-module: - POST /api/query/hover — function signature + docs at position - POST /api/query/definition — declaration location for symbol at position - POST /api/query/completions — full-document completion list with CM6 snippet templates, filtered by prefix - POST /api/query/symbols — declared functions and variables with line/column positions query:compile already used lsp:diagnostics(); hover and definition cast JSON number values to xs:integer before passing to lsp:hover() and lsp:definition(). query:make-snippet() generates CM6 ${N:$param} templates from LSP detail strings. Co-Authored-By: Claude Opus 4.6 --- modules/api.json | 129 ++++++++++++++++++++++++++++++++++++++++++ modules/api/query.xqm | 110 +++++++++++++++++++++++++++++++---- 2 files changed, 227 insertions(+), 12 deletions(-) diff --git a/modules/api.json b/modules/api.json index 8d778ac2..94e6ab6b 100644 --- a/modules/api.json +++ b/modules/api.json @@ -486,6 +486,135 @@ } } }, + "/api/query/symbols": { + "post": { + "summary": "Document symbol list (functions and variables)", + "operationId": "query:symbols", + "tags": ["query"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["query"], + "properties": { + "query": { "type": "string" }, + "base": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Array of symbol items", + "content": { "application/json": { "schema": { "type": "array" } } } + } + } + } + }, + "/api/query/completions": { + "post": { + "summary": "LSP-aware function completions", + "operationId": "query:completions", + "tags": ["query"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["query"], + "properties": { + "query": { "type": "string" }, + "prefix": { "type": "string" }, + "base": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Array of completion items", + "content": { "application/json": { "schema": { "type": "array" } } } + } + } + } + }, + "/api/query/hover": { + "post": { + "summary": "Get hover information for symbol at position", + "operationId": "query:hover", + "tags": [ + "query" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["query", "line", "column"], + "properties": { + "query": { "type": "string" }, + "line": { "type": "integer" }, + "column": { "type": "integer" }, + "base": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Hover information", + "content": { + "application/json": { + "schema": { "type": "object" } + } + } + } + } + } + }, + "/api/query/definition": { + "post": { + "summary": "Get definition location for symbol at position", + "operationId": "query:definition", + "tags": [ + "query" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["query", "line", "column"], + "properties": { + "query": { "type": "string" }, + "line": { "type": "integer" }, + "column": { "type": "integer" }, + "base": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Definition location", + "content": { + "application/json": { + "schema": { "type": "object" } + } + } + } + } + } + }, "/api/query/{id}/results": { "get": { "summary": "Retrieve paginated query results", diff --git a/modules/api/query.xqm b/modules/api/query.xqm index f447ec7e..e91ccdcb 100644 --- a/modules/api/query.xqm +++ b/modules/api/query.xqm @@ -7,6 +7,7 @@ xquery version "3.1"; module namespace query="http://exist-db.org/apps/eXide/api/query"; import module namespace roaster="http://e-editiones.org/roaster"; +import module namespace lsp="http://exist-db.org/xquery/lsp"; import module namespace config="http://exist-db.org/xquery/apps/config" at "../config.xqm"; declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; @@ -76,24 +77,109 @@ declare function query:compile($request as map(*)) { let $body := $request?body let $xquery := $body?query let $base := $body?base + let $diagnostics := lsp:diagnostics($xquery, $base) return - try { - let $_ := util:compile-query($xquery, $base) - return map { "errors": array {} } - } catch * { - map { - "errors": array { - map { - "line": $err:line-number, - "column": $err:column-number, - "message": $err:description, - "code": string($err:code) - } + map { + "errors": array { + for $d in $diagnostics?* + return map { + "line": $d?line + 1, + "column": $d?column, + "message": $d?message, + "code": $d?code } } } }; +(:~ + : POST /api/query/symbols — Document symbol list for Navigate → Symbol. + : Returns all declared functions and variables with their positions. + :) +declare function query:symbols($request as map(*)) { + let $body := $request?body + let $xquery := $body?query + let $base := $body?base + return lsp:symbols($xquery, $base) +}; + +(:~ + : POST /api/query/completions — LSP-aware function completions. + : + : Calls lsp:completions() with the full document text so the server + : compiler can see user-defined functions and imported modules, then + : filters by $prefix and builds CM6 snippet templates. + :) +declare function query:completions($request as map(*)) { + let $body := $request?body + let $xquery := $body?query + let $prefix := ($body?prefix, "")[1] + let $base := $body?base + let $completions := lsp:completions($xquery, $base) + return array { + for $item in $completions?* + let $name := replace($item?label, "#\d+$", "") + where $prefix = "" or starts-with($name, $prefix) + return map { + "text": $item?detail, + "snippet": query:make-snippet($item?detail), + "description": $item?documentation + } + } +}; + +(:~ + : Build a CM6 snippet template from an LSP detail string. + : E.g. "count($items as item()*) as xs:integer" → "count(${1:$items})" + :) +declare %private function query:make-snippet($detail as xs:string) as xs:string { + let $fname := replace($detail, "\(.*$", "") + let $params := analyze-string($detail, '\$([a-zA-Z][a-zA-Z0-9\-_\.]*)') //fn:match/fn:group[1]/string() + return + if (empty($params)) then $fname || "()" + else + $fname || "(" || + string-join( + for $p at $i in $params + return "${" || $i || ":$" || $p || "}", + ", " + ) || ")" +}; + +(:~ + : POST /api/query/hover — Get hover info for symbol at position. + :) +declare function query:hover($request as map(*)) { + let $body := $request?body + let $xquery := $body?query + let $line := xs:integer($body?line) + let $column := xs:integer($body?column) + let $base := $body?base + let $hover := lsp:hover($xquery, $line, $column, $base) + return + if (exists($hover)) then + $hover + else + map {} +}; + +(:~ + : POST /api/query/definition — Get definition location for symbol at position. + :) +declare function query:definition($request as map(*)) { + let $body := $request?body + let $xquery := $body?query + let $line := xs:integer($body?line) + let $column := xs:integer($body?column) + let $base := $body?base + let $def := lsp:definition($xquery, $line, $column, $base) + return + if (exists($def)) then + $def + else + map {} +}; + (:~ : GET /api/query/{id}/results — Paginated result items. : Replaces session.xq result retrieval. From 50e2e72b33ca056307dcca082d5011069ca8395f Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Thu, 12 Mar 2026 23:40:04 -0400 Subject: [PATCH 02/13] Wire LSP features into eXide editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hover tooltip (mouse hover over XQuery symbols): - src/lsp-hover.js: new CM6 hoverTooltip extension; fetches /api/query/hover and renders signature + description - tools/bundle-cm6.js: export hoverTooltip from CM6 bundle - scripts/bundle.js: include lsp-hover.js in eXide bundle - src/editor.js: register LspHover.extension() in buildExtensions() - index.html.tmpl, resources/css/lsp-hover.css: tooltip styles Go-to-definition (F3 / Navigate → Go to Definition): - src/xquery-helper.js gotoDefinition(): POST /api/query/definition, jump to returned line/column; fall back to AST lookup on failure Navigate → Symbol (symbol quick-picker): - src/xquery-helper.js gotoSymbol(): POST /api/query/symbols, populate QuickPicker with compiler-accurate positions, jump on select; fall back to doc.functions on network error Completions (Ctrl+Space): - src/xquery-completion.js fetchFunctionCompletions(): POST /api/query/completions with full document text instead of GET /api/editor/completions with prefix+imports; server returns items including user-defined local functions alongside built-ins Co-Authored-By: Claude Opus 4.6 --- index.html.tmpl | 1 + resources/css/lsp-hover.css | 54 +++++++++++++++++ scripts/bundle.js | 1 + src/editor.js | 1 + src/lsp-hover.js | 117 ++++++++++++++++++++++++++++++++++++ src/xquery-completion.js | 40 +++--------- src/xquery-helper.js | 109 +++++++++++++++++++++++++-------- tools/bundle-cm6.js | 3 +- 8 files changed, 268 insertions(+), 58 deletions(-) create mode 100644 resources/css/lsp-hover.css create mode 100644 src/lsp-hover.js diff --git a/index.html.tmpl b/index.html.tmpl index 9f25031d..31e5a543 100755 --- a/index.html.tmpl +++ b/index.html.tmpl @@ -7,6 +7,7 @@ +