(:~ : jsonrpc : @author andy bunce :) module namespace rpc = 'rpc'; import module namespace lsp-text = 'lsp-text' at "lsp-text.xqm"; (:~ build a notification :) declare function rpc:build-notification ($method as xs:string, $params as map(*)) as map(*){ {"jsonrpc": "2.0", "method": $method, "params": $params } }; (:~ response to $json msg, if result empty just acknowledge :) declare function rpc:build-response($json as map(*),$result:=()) as map(*){ { "jsonrpc": "2.0", "id": $json?id, "result": $result} }; (:~ response when error :) declare function rpc:build-error($info as map(*),$json as map(*)) as map(*){ { "jsonrpc": "2.0", "id": $json?id, "error": $info } }; (: map methods to functions :) declare variable $rpc:Methods:=map:merge(( map{ "initialize" : rpc:method-initialize#1, "initialized" : rpc:method-initialized#1, "workspace/didChangeConfiguration" :rpc:method-unknown#1 }, $lsp-text:methods )); (:~ return map if $msg is jsonrpc else empty :) declare function rpc:parse($msg as xs:string) as map(*)? { try { let $json:=parse-json($msg) return if($json?jsonrpc eq "2.0" and exists($json?method)) then $json else () } catch *{ () } }; (:~ send reply to $json message get functions for methods evaluate function with message send any responses :) declare function rpc:reply($json as map(*)) as empty-sequence() { let $f :=( rpc:admin-log($json,"➡️"), $rpc:Methods?($json?method) ) let $response := $f!.($json) return $response!rpc:send(.) }; (:~ send with logging :) declare function rpc:send($msg as map(*)) as empty-sequence() { rpc:admin-log($msg,"⬅️"),ws:send($msg ,ws:id()) }; (:~ canned initialize response :) declare function rpc:method-initialize($json as map(*)) as map(*) { ws:set(ws:id(),"client", $json?params), rpc:build-response($json, json:doc("etc/capabilities.json",{"format":"w3"})) }; (:~ initialized response :) declare 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 :) declare function rpc:method-unknown($json as map(*)) as map(*)? { let $_:=trace($json?method,"unknown") return () }; (:~ rpc log message :) declare function rpc:log($msg as xs:string) as map(*) { rpc:build-notification( "window/logMessage", {"type":1, "message": $msg} ) }; (:~ write to admin:log :) declare function rpc:admin-log($data as item()*,$emoji as xs:string) as empty-sequence() { let $msg:= json:serialize($data,{ 'format': 'w3', 'indent': 'no' }) return admin:write-log($emoji || $msg,"LSP") }; (:~ 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() };