[mod] diagnostics

This commit is contained in:
Andy Bunce 2025-08-10 10:58:48 +01:00
parent 97d0deef0d
commit e3766ee6a4
7 changed files with 67 additions and 9 deletions

View file

@ -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/

View file

@ -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"}]}
{"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"}]}

View file

@ -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)
)
};

View file

@ -14,7 +14,7 @@
"selectionRangeProvider": true,
"documentLinkProvider": {},
"serverInfo": {
"name": "XQuery 4.0 Language Server",
"name": "XQuery 4.0b Language Server",
"version": "0.0.1"
}
}

17
webapp/lsp/lsp-diags.xqm Normal file
View file

@ -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}
}
};

View file

@ -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
);

View file

@ -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 => {