[mod] improve logging

This commit is contained in:
Andy Bunce 2025-09-20 17:27:38 +01:00
parent 279b1ee189
commit 00aed821e1
12 changed files with 89 additions and 48 deletions

View file

@ -1,12 +1,14 @@
Work in progress.
An attempt to write a [Language Server Protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol) server for and in XQuery 4.0. It uses the [BaseX websocket](https://docs.basex.org/main/WebSockets) feature.
An attempt to write a [Language Server Protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol) server for and in XQuery 4.0. It tracks the draft specifications at https://qt4cg.org/
It written for BaseX 12.0 and uses the [BaseX websocket](https://docs.basex.org/main/WebSockets) feature.
# Demo
1. Requires `Docker` or `Podman` on the local machine.
2. Clone this repo
3. Run `docker compose up -d` The LSP server shall now running on port 3000 (edit `compose.yaml` to change port)
4. In a browser navigate to `http://localhost:3000/static/clients/codemirror/` to see the CodeMirror 6 demo.
The demos expect `Docker` or `Podman` on the local machine. This is only a requirement for the demos.
1 Clone this repo
2. Run `docker compose up -d` The LSP server shall now running on port 3000 (edit `compose.yaml` to change port)
3. In a browser navigate to `http://localhost:3000/static/clients/codemirror/` to see the CodeMirror 6 demo.
In principle, the LSP can be used in any environment that can make use of a LSP server accessed via a websocket.
## LSP Server

View file

@ -1,7 +1,14 @@
xquery version '4.0';
(:~ Symbols from XQuery source file :)
module namespace syms="lsp/symbols";
import module namespace lspt = 'lsp-typedefs' at "lsp-typedefs.xqm";
import module namespace pos="lsp/position" at "position.xqm";
(:~ symbols :)
declare function syms:list()
as map(*){
};

View file

@ -39,9 +39,17 @@ as map(*)?
declare
function rpc:reply($json as map(*))
as empty-sequence() {
let $f :=$rpc:Methods?($json?method)
let $f :=(void(trace($json,"➡️")),$rpc:Methods?($json?method))
let $response := $f!.($json)
return $response!ws:send(.=>trace("REPLY: "),ws:id())
return $response!rpc:send(.)
};
(:~ send with logging :)
declare
function rpc:send($msg as map(*))
as empty-sequence()
{
ws:send($msg =>trace("⬅️"),ws:id())
};
(:~ canned initialize response :)
@ -59,6 +67,7 @@ function rpc:method-initialized($json as map(*))
as empty-sequence()
{
ws:set(ws:id(),"initialized", true())
(: ,rpc:later(5000,"rpc:show-message","This is a test async message 5secs after initialized",ws:id()) :)
};
(:~ unknown method response :)
@ -105,4 +114,31 @@ declare function rpc:error($message as xs:string,$json as map(*))
}
}
}
};
(:~ window/showMessage :)
declare function rpc:show-message($msg as xs:string)
as map(*)
{
{"jsonrpc": "2.0",
"method":"window/showMessage",
"params":{"type":3, "message": $msg}
}
};
(:~ send message after delay :)
declare function rpc:later($msdelay as xs:integer,$fn as xs:string ,$arg, $wsid as xs:string)
{
let $xq:=`import module namespace rpc = 'rpc' at 'jsonrpc.xqm';
declare variable $msdelay external;
declare variable $fn external;
declare variable $arg external;
declare variable $wsid external;
prof:sleep($msdelay),
ws:send(function-lookup(xs:QName($fn),1)($arg),$wsid)
`
let $bindings:={"msdelay": $msdelay,"wsid": $wsid, "fn": $fn,"arg":$arg}
let $opts:={"cache": true(),"base-uri": static-base-uri()}
return job:eval($xq, $bindings,$opts)=>void()
};

2
webapp/lsp/later.xq Normal file
View file

@ -0,0 +1,2 @@
import module namespace rpc = 'rpc' at 'jsonrpc.xqm';
rpc:later(1000,"rpc:show-message","Hello","sock")

View file

@ -1,10 +1,5 @@
module namespace lsp-diags = 'lsp-diags';
import module namespace lspt = 'lsp-typedefs' at "lsp-typedefs.xqm";
import module namespace pos="lsp/position" at "position.xqm";
declare type lsp-diags:ParseResult as element(Module|ERROR);
(:~
(:~ codemirror
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.
@ -13,21 +8,14 @@ source?: string An optional source string indicating where the diagnostic is
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 lsp-diags:nostic(
range as lspt:Range,
severity as xs:integer, (: enum('error', 'hint', 'info', 'warning') :)
message as xs:string,
code? as xs:string,
source as xs:string:="xquery"
);
:)
import module namespace lspt = 'lsp-typedefs' at "lsp-typedefs.xqm";
import module namespace pos="lsp/position" at "position.xqm";
declare type lsp-diags:ParseResult as element(Module|ERROR);
declare variable $lsp-diags:severities:={
'error':1,
'hint':4,
'info':3,
'warning':2
};
declare function lsp-diags:publish(
$uri as xs:string,
@ -60,7 +48,7 @@ as map(*)*{
let $e:= number($xml/@e)-1
return (
(: mark error :)
lsp-diags:nostic(lspt:Range(pos:toPosition($text, $b),
lspt:diagnostic(lspt:Range(pos:toPosition($text, $b),
pos:toPosition($text, $e)),
1,
$dmesg,'XPST0003'),
@ -68,7 +56,7 @@ as map(*)*{
(:mark after error:)
if($e ge string-length($text))
then ()
else lsp-diags:nostic(lspt:Range(pos:toPosition($text, $e +1 ),
else lspt:diagnostic(lspt:Range(pos:toPosition($text, $e +1 ),
pos:toPosition($text, $last)),
2,
"Unparsed due to previous parser error.",

View file

@ -23,13 +23,8 @@ function lsp-text:hover($json as map(*))
as map(*)
{
let $r:= [
`markdown here, this is **bold**.
A [link](http://google.com)
The last line.`
,
`A hover at { pos:ln-col($json?params?position) } uri: {$json?params?textDocument?uri} `
`At { pos:ln-col($json?params?position) }, uri: {$json?params?textDocument?uri},
[path](https://quodatum.github.io/basex-xqparse/i-BaseX.xhtml#EQName)`
]
return rpc:result({"contents":$r},$json)
};

View file

@ -94,4 +94,19 @@ declare variable $lspt:SymbolKindMap :={
'TypeParameter': 26
};
declare variable $lspt:DiagnosticSeverityKinds:={
'error':1,
'warning':2,
'info':3,
'hint':4
};
declare record lspt:diagnostic(
range as lspt:Range,
severity as xs:integer, (: enum('error', 'hint', 'info', 'warning') :)
message as xs:string,
code? as xs:string,
source as xs:string:="xquery"
);
declare type lspt:TraceValue as enum( 'off' , 'messages' , 'verbose');

View file

@ -13,9 +13,7 @@ function lsp-ws:error($error) {
trace($error,"ERR ")
};
(:~
: Creates a WebSocket connection. Registers the user and notifies all clients.
:)
(:~ Creates a WebSocket connection. :)
declare
%ws:connect('/lsp')
function lsp-ws:connect() as empty-sequence() {

View file

@ -14,6 +14,7 @@ as lspt:num
let $off:=if($pos?line eq 0)
then 0
else $nl[$pos?line]
let $_:=substring($text,$off+1,1)=>string-to-codepoints()=>trace("LF? ")
return $off+$pos?character
};
@ -22,7 +23,7 @@ declare function pos:toPosition($text as xs:string,
$index as lspt:num
)
as lspt:Position {
let $nl:= if($index=>trace("IN ") gt string-length($text)=>trace("L "))
let $nl:= if($index gt string-length($text))
then error(xs:QName("pos:range"),`out of range: index={$index},length={string-length($text)}`)
else index-of(string-to-codepoints($text),10)
let $line:=pos:lineAt($nl,$index)

View file

@ -22,19 +22,16 @@ body {
grid-template-rows: min-content min-content 1fr min-content;
gap: 1px;
* {
padding: 0px 0px;
}
.nav-item{
padding: 0px 9px;
margin: 0px 3px;
.navbar * {
box-sizing: content-box;
}
/* Set editor dimensions */
#editor {
max-width: 100%;
overflow: hidden;
height:80cqh;
height:75cqh;
}
/* Stretch editor to fit inside its containing div */

File diff suppressed because one or more lines are too long