From 24082ad97b7186467eef65eed82c27f41e3757e2 Mon Sep 17 00:00:00 2001 From: Andy Bunce Date: Sat, 11 Oct 2025 15:50:06 +0100 Subject: [PATCH] [add] fn completion --- bundles/grammar/xq4.java | 2 + docs/explore.xqbk | 2 +- docs/wordat.xqbk | 2 +- test/context.xq | 4 + webapp/lsp/context.xqm | 47 +++++++++++ webapp/lsp/etc/capabilities.json | 2 +- .../lsp/etc}/function-catalog.xml | 0 webapp/lsp/handlers.xqm | 8 +- webapp/lsp/lsp-text.xqm | 2 +- webapp/lsp/providers/completions.xqm | 14 +++- webapp/lsp/providers/documentSymbols.xqm | 15 +++- webapp/lsp/providers/hover.xqm | 2 +- webapp/static/clients/codemirror/grail.html | 77 ++++++++++--------- .../codemirror/{list.js => wc-qd-list.js} | 2 +- .../{popover.js => wc-qd-popover.js} | 0 15 files changed, 123 insertions(+), 56 deletions(-) create mode 100644 test/context.xq create mode 100644 webapp/lsp/context.xqm rename {bundles/grammar => webapp/lsp/etc}/function-catalog.xml (100%) rename webapp/static/clients/codemirror/{list.js => wc-qd-list.js} (98%) rename webapp/static/clients/codemirror/{popover.js => wc-qd-popover.js} (100%) diff --git a/bundles/grammar/xq4.java b/bundles/grammar/xq4.java index ea31ab1..e94d99a 100644 --- a/bundles/grammar/xq4.java +++ b/bundles/grammar/xq4.java @@ -308,6 +308,8 @@ public class xq4 { try { + Atts atts = new Atts(); + atts.add(Token.token("b"), Token.token(pe.getBegin() + 1)); builder.openElem(Token.token(name), atts, nsp); } catch (IOException e) diff --git a/docs/explore.xqbk b/docs/explore.xqbk index 12effb3..1bf6ce9 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\";\r\ndeclare variable $LAST:=foot(ws:ids());"},{"kind":2,"language":"xquery","value":"2+2"},{"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?text=>substring(1,100)"},{"kind":2,"language":"xquery","value":"let $sock:=foot(ws:ids())\r\nlet $f:=docs:list($sock)\r\nlet $t:=docs:get($sock,$f,\"parse\")\r\nreturn $t?text"},{"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"},{"kind":2,"language":"xquery","value":"(: client capabilities :)\r\nws:get($LAST,\"client\")"}]} \ 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\";\r\ndeclare variable $LAST:=foot(ws:ids());"},{"kind":2,"language":"xquery","value":"2+2"},{"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?text=>substring(1,100)"},{"kind":2,"language":"xquery","value":"let $sock:=foot(ws:ids())\r\nlet $f:=docs:list($sock)\r\nlet $t:=docs:get($sock,$f,\"parse\")\r\nreturn $t?text"},{"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"},{"kind":2,"language":"xquery","value":"(: client capabilities :)\r\nws:get($LAST,\"client\")=>serialize({ \"method\": \"json\",\"indent\": true()})"}]} \ No newline at end of file diff --git a/docs/wordat.xqbk b/docs/wordat.xqbk index fa90a97..50eff76 100644 --- a/docs/wordat.xqbk +++ b/docs/wordat.xqbk @@ -1 +1 @@ -{"cells":[{"kind":1,"language":"markdown","value":"XPath name charactors"},{"kind":2,"language":"xquery","value":"let $s:=`[A-Z]\r\n | '_'\r\n | [a-z]\r\n | [#xC0-#xD6]\r\n | [#xD8-#xF6]\r\n | [#xF8-#x2FF]\r\n | [#x370-#x37D]\r\n | [#x37F-#x1FFF]\r\n | [#x200C-#x200D]\r\n | [#x2070-#x218F]\r\n | [#x2C00-#x2FEF]\r\n | [#x3001-#xD7FF]\r\n | [#xF900-#xFDCF]\r\n | [#xFDF0-#xFFFD]\r\n `\r\nlet $sreg:= normalize-space($s)\r\n =>tokenize(\"\\|\")\r\n =!>normalize-space()\r\n =!>replace(\"(#x[0-9A-F]+)\",fn($s,$m){\r\n convert:integer-from-base(substring($m,3),16)\r\n =>codepoints-to-string()\r\n })\r\n =!>translate(\"'\",\"\")\r\n =>string-join(\"|\")\r\nreturn $sreg"}]} \ No newline at end of file +{"cells":[{"kind":1,"language":"markdown","value":"XPath name charactors"},{"kind":2,"language":"xquery","value":"let $s:=`[A-Z]\r\n | '_'\r\n | [a-z]\r\n | [#xC0-#xD6]\r\n | [#xD8-#xF6]\r\n | [#xF8-#x2FF]\r\n | [#x370-#x37D]\r\n | [#x37F-#x1FFF]\r\n | [#x200C-#x200D]\r\n | [#x2070-#x218F]\r\n | [#x2C00-#x2FEF]\r\n | [#x3001-#xD7FF]\r\n | [#xF900-#xFDCF]\r\n | [#xFDF0-#xFFFD]\r\n `\r\nlet $sreg:= normalize-space($s)\r\n =>tokenize(\"\\|\")\r\n =!>normalize-space()\r\n =!>replace(\"(#x[0-9A-F]+)\",fn($s,$m){\r\n convert:integer-from-base(substring($m,3),16)\r\n =>codepoints-to-string()\r\n })\r\n =!>translate(\"'\",\"\")\r\n =>string-join(\"|\")\r\nreturn $sreg"},{"kind":1,"language":"markdown","value":"# Functions"},{"kind":2,"language":"xquery","value":"declare namespace fos=\"http://www.w3.org/xpath-functions/spec/namespace\";\r\ndoc(\"C:/Users/mrwhe/git/quodatum/basex-lsp/bundles/grammar/function-catalog.xml\")\r\n//fos:function[@prefix=\"fn\"]/@name"}]} \ No newline at end of file diff --git a/test/context.xq b/test/context.xq new file mode 100644 index 0000000..3987f96 --- /dev/null +++ b/test/context.xq @@ -0,0 +1,4 @@ + +import module namespace ctx="lsp/context" at "../webapp/lsp/context.xqm"; + + ctx:functions("fn")!ctx:map(.)=>trace("AA") diff --git a/webapp/lsp/context.xqm b/webapp/lsp/context.xqm new file mode 100644 index 0000000..92b1bf0 --- /dev/null +++ b/webapp/lsp/context.xqm @@ -0,0 +1,47 @@ +(: context information xpath functions etc +@author Andy Bunce +:) +module namespace ctx="lsp/context"; +import module namespace lspt = 'lsp-typedefs' at 'lsp-typedefs.xqm'; +declare namespace fos="http://www.w3.org/xpath-functions/spec/namespace"; + +declare variable $ctx:catalog:=doc("etc/function-catalog.xml"); +declare variable $ctx:ns:=('fn', 'op','math','map','array'); +declare variable $ctx:doclink:="https://qt4cg.org/specifications/xpath-functions-40/Overview.html#func-"; + +declare function ctx:functions($ns as xs:string) +as element(fos:function)*{ +$ctx:catalog//fos:function[@prefix=$ns] +}; + +declare function ctx:map($fn as element(fos:function)) +{ + $fn!map{ + "label":string(@name), + "kind": 3, + "detail":ctx:summary(.), + "documentation":`spec` + !lspt:MarkupContent("markdown",.) +} + +}; + +declare function ctx:summary($fn as element(fos:function)) +{ +$fn/fos:summary/* +=>ctx:strip-ns() +=>serialize({ 'method': 'html' }) +}; + +declare function ctx:strip-ns($n as node()) as node() { + if($n instance of element()) then ( + element { node-name($n) } { + $n/@*, + $n/node()/ctx:strip-ns(.) + } + ) else if($n instance of document-node()) then ( + document { ctx:strip-ns($n/node()) } + ) else ( + $n + ) +}; \ No newline at end of file diff --git a/webapp/lsp/etc/capabilities.json b/webapp/lsp/etc/capabilities.json index d1952c4..ba6a2d3 100644 --- a/webapp/lsp/etc/capabilities.json +++ b/webapp/lsp/etc/capabilities.json @@ -13,7 +13,7 @@ } ] }, - "hoverProvider": false, + "hoverProvider": true, "documentSymbolProvider": true, "documentRangeFormattingProvider": false, "colorProvider": false, diff --git a/bundles/grammar/function-catalog.xml b/webapp/lsp/etc/function-catalog.xml similarity index 100% rename from bundles/grammar/function-catalog.xml rename to webapp/lsp/etc/function-catalog.xml diff --git a/webapp/lsp/handlers.xqm b/webapp/lsp/handlers.xqm index a45e99b..d1c7076 100644 --- a/webapp/lsp/handlers.xqm +++ b/webapp/lsp/handlers.xqm @@ -12,13 +12,13 @@ declare record hnd:Result( declare type hnd:actionFn as fn($parse as element(),$state as hnd:Result ) as hnd:Result; -declare type hnd:actionMap as map(xs:string,hnd:actionFn) -; +declare type hnd:actionMap as map(xs:string,hnd:actionFn); + declare function hnd:walk($parse as element(), $actions as hnd:actionMap, $state as hnd:Result ) -as hnd:Result{ - +as hnd:Result +{ let $action:=$actions(name($parse)) let $result:= if(exists($action)) then $action($parse,$state) diff --git a/webapp/lsp/lsp-text.xqm b/webapp/lsp/lsp-text.xqm index 9c44306..661a2d4 100644 --- a/webapp/lsp/lsp-text.xqm +++ b/webapp/lsp/lsp-text.xqm @@ -52,7 +52,7 @@ as map(*)? { let $doc:=$json?params?textDocument?uri let $context:=$json?params?context (:{"triggerCharacter":":","triggerKind":2.0e0}:) - let $result:=comp:dummy($context) + let $result:=comp:list($context) return rpc:result($json,array:build($result)) }; diff --git a/webapp/lsp/providers/completions.xqm b/webapp/lsp/providers/completions.xqm index 965d02e..9b14a99 100644 --- a/webapp/lsp/providers/completions.xqm +++ b/webapp/lsp/providers/completions.xqm @@ -1,22 +1,28 @@ module namespace comp = 'lsp-completions'; import module namespace lspt = 'lsp-typedefs' at "../lsp-typedefs.xqm"; +import module namespace ctx="lsp/context" at "../context.xqm"; + (: (:{"triggerCharacter":":","triggerKind":2.0e0}:):) declare function comp:list($context as map(*)) -as lspt:CompletionItem*{ +as lspt:CompletionItem* +{ message($context,"context: "), - (1 to 20)!lspt:CompletionItem("item"||.,.) + ctx:functions("fn")!ctx:map(.)=>trace("aaa") }; declare function comp:dummy($context as map(*)) -as lspt:CompletionItem*{ +as lspt:CompletionItem* +{ message($context,"context: "), map:for-each( $lspt:CompletionItemKindMap, fn($k,$v){ let $d:=lspt:MarkupContent( 'markdown', - '[path](https://quodatum.github.io/basex-xqparse/i-BaseX.xhtml#EQName)' + `More about + {$k} + ` ) return lspt:CompletionItem($k,$v,detail:="detail",documentation:=$d) }) diff --git a/webapp/lsp/providers/documentSymbols.xqm b/webapp/lsp/providers/documentSymbols.xqm index e178a95..5550161 100644 --- a/webapp/lsp/providers/documentSymbols.xqm +++ b/webapp/lsp/providers/documentSymbols.xqm @@ -19,6 +19,7 @@ as lspt:DocumentSymbol*{ return $result?result }; +(: for testing only:) declare function syms:dummy($parse as element(),$text as xs:string ) as lspt:DocumentSymbol*{ let $fr:=(pos:full-range($text),message("dummy")) @@ -38,7 +39,7 @@ as hnd:Result{ declare function syms:VarDecl($parse as element(VarDecl),$state as hnd:Result ) as hnd:Result{ - let $name:=$parse/VarNameAndType/EQName + let $name:=syms:localName($parse/VarNameAndType/EQName) let $length:=string($parse)=>string-length() let $range:=lspt:Range(lspt:Position(0,0),lspt:Position(0,3)) let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Variable'),$range,$range,"TODO") @@ -47,11 +48,17 @@ as hnd:Result{ declare function syms:FunctionDecl($parse as element(FunctionDecl),$state as hnd:Result ) as hnd:Result{ - let $name:=$parse/UnreservedFunctionEQName + let $name:=syms:localName($parse/UnreservedFunctionEQName) let $range:=lspt:Range(lspt:Position(0,0),lspt:Position(0,3)) let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Method'),$range,$range,"TODO") return ($state?result,$sym)=>hnd:Result(true()) }; - - +declare function syms:localName($name as xs:string ) +as xs:string{ + if(starts-with($name,"Q{")) + then substring-after($name,"}") + else if(contains($name,":")) + then substring-after($name,":") + else $name +}; diff --git a/webapp/lsp/providers/hover.xqm b/webapp/lsp/providers/hover.xqm index 5b239e9..1546ad9 100644 --- a/webapp/lsp/providers/hover.xqm +++ b/webapp/lsp/providers/hover.xqm @@ -6,7 +6,7 @@ declare function hov:list($uri, $pos as map(*)) as xs:string*{ let $word:="TODO" return `Hover { pos:ln-col($pos) }, uri: {$uri}, -[path](https://quodatum.github.io/basex-xqparse/i-BaseX.xhtml#EQName) + Path WordAt: {$word}` }; \ No newline at end of file diff --git a/webapp/static/clients/codemirror/grail.html b/webapp/static/clients/codemirror/grail.html index 4f5324c..823357a 100644 --- a/webapp/static/clients/codemirror/grail.html +++ b/webapp/static/clients/codemirror/grail.html @@ -31,32 +31,32 @@ - - - + + @@ -98,9 +98,7 @@ - + @@ -121,7 +119,7 @@
Workspace 0 - +