From e3766ee6a4f93813efa8a50622468b3f9f8f5c39 Mon Sep 17 00:00:00 2001 From: Andy Bunce Date: Sun, 10 Aug 2025 10:58:48 +0100 Subject: [PATCH] [mod] diagnostics --- README.md | 26 +++++++++++++++++----- docs/explore.xqbk | 2 +- webapp/lsp/docs.xqm | 4 +++- webapp/lsp/etc/capabilities.json | 2 +- webapp/lsp/lsp-diags.xqm | 17 ++++++++++++++ webapp/lsp/position.xqm | 24 ++++++++++++++++++++ webapp/static/clients/codemirror/script.js | 1 + 7 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 webapp/lsp/lsp-diags.xqm diff --git a/README.md b/README.md index 84ab188..4df3db5 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,33 @@ Work in progress. An attempt to write a [Language Server Protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol) server using XQuery 4.0 and the [BaseX websocket](https://docs.basex.org/main/WebSockets) feature. -## Server -* `webapp/lsp` The LSP implementation in XQuery using WebSockets for transport and the [JSON-RPC](https://www.jsonrpc.org/specification) 2.0 API for format. -## Sample clients +## LSP Server +* `webapp/lsp` The LSP implementation in XQuery using WebSockets for transport and the [JSON-RPC](https://www.jsonrpc.org/specification) 2.0 API for message format. +### Dev notes +State is held in websocket attributes. + +|Name|Use| +|----|---| +|initized|set true after client sends intialized messag| +|files|A map whose keys are open uris, values are maps (doctype-> attribute name where doctype is stored| | +|file-{uuid}|name of websocket attribute with textDocument| +|parse-{uuid}|name of websocket attribute with parse tree XML| +## Sample html clients +### Using `CodeMirror6` * `webapp/static/codemirror` A test html page using the [CodeMirror6 editor](https://codemirror.net/) that connects to the BaseX LSP instance +### Using `Ace Editor` * `webapp/static/ace` A test html page using the [Ace editor](https://ace.c9.io/) that connects to the BaseX LSP instance ## Uses * https://github.com/mkslanc/ace-linters https://mkslanc.github.io/ace-linters/ -* I needed `set NODE_OPTIONS=--max_old_space_size=8192` for build to complete -* or `node --max-old-space-size=8192 node_modules/webpack-dev-serve +I needed `set NODE_OPTIONS=--max_old_space_size=8192` for build to complete + +Or `node --max-old-space-size=8192 node_modules/webpack-dev-serve r/bin/webpack-dev-server.js` -## notes + + +## Notes java -cp org.eclipse.lemminx-uber.jar org.eclipse.lemminx.XMLServerSocketLauncher` Using https://github.com/mkslanc/ace-linters https://mkslanc.github.io/ace-linters/ diff --git a/docs/explore.xqbk b/docs/explore.xqbk index 8531777..861444c 100644 --- a/docs/explore.xqbk +++ b/docs/explore.xqbk @@ -1 +1 @@ -{"cells":[{"kind":2,"language":"xquery","value":"(:<:)\r\n\r\nimport module namespace docs=\"lsp/docs\" at \"/srv/basex/webapp/lsp/docs.xqm\";"},{"kind":2,"language":"xquery","value":"ws:ids()"},{"kind":2,"language":"xquery","value":"let $sock:=foot(ws:ids())\r\nlet $f:=docs:list($sock)\r\nlet $t:=docs:get($sock,$f,\"textDocument\")?text\r\nreturn $t"}]} \ No newline at end of file +{"cells":[{"kind":2,"language":"xquery","value":"(:<:)\r\n\r\nimport module namespace docs=\"lsp/docs\" at \"/srv/basex/webapp/lsp/docs.xqm\";"},{"kind":2,"language":"xquery","value":"ws:ids()"},{"kind":2,"language":"xquery","value":"let $sock:=foot(ws:ids())\r\nlet $f:=docs:list($sock)\r\nlet $t:=docs:get($sock,$f,\"textDocument\")\r\nreturn $t"}]} \ No newline at end of file diff --git a/webapp/lsp/docs.xqm b/webapp/lsp/docs.xqm index cf3e92f..9f46a20 100644 --- a/webapp/lsp/docs.xqm +++ b/webapp/lsp/docs.xqm @@ -7,6 +7,7 @@ module namespace docs="lsp/docs"; import module namespace p="xq4" at "xq4.xqm"; import module namespace pos="lsp/position" at "position.xqm"; import module namespace rpc = 'rpc' at 'jsonrpc.xqm'; +import module namespace lsp-diags = 'lsp-diags' at 'lsp-diags.xqm'; (: save $textDocument data as session $socket properties @return uri @@ -106,6 +107,7 @@ declare function docs:get( ) }; +(:~ generate XML parse, save it and publish diagnostics :) declare function docs:parse( $socket as xs:string, $file as xs:string @@ -115,6 +117,6 @@ declare function docs:parse( let $xml:= p:parse-Module($text)=>prof:time("⏱️ p:parse-Module " || $file) return ( ws:set($socket,docs:key($socket,$file,"parse"),$xml), - rpc:log(`{ current-dateTime() } done {$xml/name(.)} size: { string-length( $text) }`) + lsp-diags:publish($file,$xml) ) }; \ No newline at end of file diff --git a/webapp/lsp/etc/capabilities.json b/webapp/lsp/etc/capabilities.json index ddf947b..007d2cb 100644 --- a/webapp/lsp/etc/capabilities.json +++ b/webapp/lsp/etc/capabilities.json @@ -14,7 +14,7 @@ "selectionRangeProvider": true, "documentLinkProvider": {}, "serverInfo": { - "name": "XQuery 4.0 Language Server", + "name": "XQuery 4.0b Language Server", "version": "0.0.1" } } diff --git a/webapp/lsp/lsp-diags.xqm b/webapp/lsp/lsp-diags.xqm new file mode 100644 index 0000000..c542de5 --- /dev/null +++ b/webapp/lsp/lsp-diags.xqm @@ -0,0 +1,17 @@ +module namespace lsp-diags = 'lsp-diags'; + +import module namespace docs="lsp/docs" at "docs.xqm"; +import module namespace pos="lsp/position" at "position.xqm"; + +declare function lsp-diags:publish( + $uri as xs:string, + $xml as element(Module|ERROR)) +as map(*){ + let $diagnostics:=[] + =>array:append(pos:nostic(pos:Range(pos:Position(0,1),pos:Position(0,3)),1,"A test")) + + return {"jsonrpc": "2.0", + "method":"textDocument/publishDiagnostics", + "params":{"uri": $uri, "diagnostics": $diagnostics} +} +}; diff --git a/webapp/lsp/position.xqm b/webapp/lsp/position.xqm index cb1509f..a10a8ef 100644 --- a/webapp/lsp/position.xqm +++ b/webapp/lsp/position.xqm @@ -12,6 +12,15 @@ declare record pos:Position( character as xs:integer ); +(:~ +@param line Line position in a document (zero-based). +@param character Character offset on a line in a document (zero-based). +:) +declare record pos:Range( + start as pos:Position, + end as pos:Position +); + declare function pos:apply-changes($text as xs:string, $chs as array(*)) as xs:string{ array:fold-left($chs,$text,pos:apply-change#2) @@ -64,4 +73,19 @@ as pos:Position { return pos:Position($s?line, $index - $s?off) }; +(:~ +from: number The start position of the relevant text. +to: number The end position. May be equal to from, though actually covering text is preferable. +severity: "error" | "hint" | "info" | "warning" The severity of the problem. This will influence how it is displayed. +markClass⁠?: string When given, add an extra CSS class to parts of the code that this diagnostic applies to. +source⁠?: string An optional source string indicating where the diagnostic is coming from. You can put the name of your linter here, if applicable. +message: string The message associated with this diagnostic. +renderMessage⁠?: fn(view: EditorView) → Node An optional custom rendering function that displays the message as a DOM node. +actions⁠?: readonly Action[] An optional array of actions that can be taken on this diagnostic. + :) +declare record pos:nostic( + range as pos:Range, + severity as xs:integer, (: enum('error', 'hint', 'info', 'warning') :) + message as xs:string +); diff --git a/webapp/static/clients/codemirror/script.js b/webapp/static/clients/codemirror/script.js index 35cb295..4f1644c 100644 --- a/webapp/static/clients/codemirror/script.js +++ b/webapp/static/clients/codemirror/script.js @@ -28,6 +28,7 @@ document.getElementById("search").onclick = e => { lsp.openSearchPanel(view); }; document.getElementById("lint").onclick = e => { + console.log("word",view.state.wordAt(1)); lsp.openLintPanel(view); }; document.getElementById("load").onchange = e => {