diff --git a/test/ast.xq b/test/ast.xq index 575520b..2a27a27 100644 --- a/test/ast.xq +++ b/test/ast.xq @@ -1,10 +1,20 @@ -import module namespace syms="lsp/symbols" at "../webapp/lsp/ast/ast.xqm"; +import module namespace ast="lsp/ast" at "../webapp/lsp/ast/ast.xqm"; -declare $file:="C:\Users\mrwhe\git\quodatum\basex-lsp\test\sample.docs\pdfbox.xqm"; +(: declare variable $file:="sample.docs/pdfbox.xqm"; :) +declare variable $file:="sample.docs/simple.xq"; -declare variable $A:=doc("C:\Users\mrwhe\git\quodatum\basex-lsp\test\sample.docs\parse-pdfbox.xml"); -$A update{ - for $e in .//element() +declare variable $A:=doc("sample.docs/parse-pdfbox.xml"); + + +unparsed-text($file) + +=>ast:build() + +update{ + for $e in descendant-or-self::element() let $len:=string-length($e) - return insert node attribute len { $len } into $e -} + let $before:=$e/preceding-sibling::node()/string-length()=>sum() + return (insert node attribute len { $len } into $e, + insert node attribute before { $before } into $e + ) +} diff --git a/test/sample.docs/simple.xq b/test/sample.docs/simple.xq new file mode 100644 index 0000000..86206b4 --- /dev/null +++ b/test/sample.docs/simple.xq @@ -0,0 +1,6 @@ +(: simple parse test :) +declare function local:print($src as xs:string, $dest as xs:string) +{ + concat($src,"->", $dest) +}; +local:print("sss","bbb") \ No newline at end of file diff --git a/webapp/lsp/ast/ast.xqm b/webapp/lsp/ast/ast.xqm index b731136..58ec031 100644 --- a/webapp/lsp/ast/ast.xqm +++ b/webapp/lsp/ast/ast.xqm @@ -5,14 +5,23 @@ module namespace ast="lsp/ast"; (: import module namespace p="xq4" at "xq4.xqm"; :) import module namespace xq4="java:quodatum.parser.xq4"; -(:~ build :) -declare function ast:build($text as xs:string,$uri as xs:string:="") +(:~ build +$opts uri, + abstract:true(), + position: true() +:) +declare function ast:build($text as xs:string,$opts as map(*):={}) as element(*){ - let $xml:= xq4:parseModule($text)=>prof:time("⏱️ p:parse-Module " || $uri) - return ($xml, - message(ast:report($xml),"REP"), - message(ast:report(ast:flatten($xml)),"flat") - ) + let $xml:= xq4:parseModule($text)=>prof:time("⏱️ parseModule " || $opts?uri) + let $res:= if($opts?abstract) + then ast:flatten($xml)=>prof:time("⏱️ abstract ") + else $xml + +let $res:=if($opts?position) + then ast:annotate-with-positions($res)=>prof:time("⏱️ position ") + else $res + +return $res }; (:~ @@ -31,7 +40,82 @@ declare function ast:flatten($input as element()) as element() { } }; -(:-------------------------------------------:) +declare function ast:add-positions($nodes as node()*, $start-pos as xs:integer) as item()+ { + if (empty($nodes)) then ( + $start-pos, () + ) else ( + let $head := $nodes[1] + let $tail := $nodes[position() > 1] + return + typeswitch($head) + case element() return + let $children := $head/node() + let $child-result := ast:add-positions($children, $start-pos) + let $child-end-pos := $child-result[1] + let $processed-children := subsequence($child-result, 2) + let $element-text := string-join($processed-children ! string(.), "") + let $element-length := string-length($element-text) + let $element-end-pos := + if ($element-length > 0) then $start-pos + $element-length - 1 + else $start-pos + let $tail-result := + if (exists($tail)) then + ast:add-positions($tail, $element-end-pos + 1) + else ( + $element-end-pos + 1, () + ) + let $final-end-pos := $tail-result[1] + let $processed-tail := subsequence($tail-result, 2) + return ( + $final-end-pos, + element {node-name($head)} { + attribute start {$start-pos}, + attribute end {$element-end-pos}, + $processed-children + }, + $processed-tail + ) + case text() return + let $text-length := string-length($head) + let $text-end-pos := + if ($text-length > 0) then $start-pos + $text-length - 1 + else $start-pos + let $tail-result := + if (exists($tail)) then + ast:add-positions($tail, $text-end-pos + 1) + else ( + $text-end-pos + 1, () + ) + let $final-end-pos := $tail-result[1] + let $processed-tail := subsequence($tail-result, 2) + return ( + $final-end-pos, + $head, + $processed-tail + ) + default return + let $tail-result := + if (exists($tail)) then + ast:add-positions($tail, $start-pos) + else ( + $start-pos, () + ) + let $final-end-pos := $tail-result[1] + let $processed-tail := subsequence($tail-result, 2) + return ( + $final-end-pos, + $head, + $processed-tail + ) + ) +}; + +declare function ast:annotate-with-positions($xml as element()) as element() { + let $result := ast:add-positions($xml, 1) + return $result[2] +}; + +(:-------reporting------------------------------------:) declare function ast:report($el as element(*)) { { diff --git a/webapp/lsp/docs.xqm b/webapp/lsp/docs.xqm index 249f00d..e02b08b 100644 --- a/webapp/lsp/docs.xqm +++ b/webapp/lsp/docs.xqm @@ -118,7 +118,7 @@ declare function docs:parse( )as map(*)? { let $text:=docs:get($socket,$file,"textDocument")?text - let $xml:= ast:build($text, $file) + let $xml:= ast:build($text, {"uri": $file, "abstract":false(), "position":true()}) let $diags:=lsp-diags:list($file, $text, $xml)=>prof:time("⏱️ diags " || $file) return ( ws:set($socket,docs:key($socket,$file,"parse"),$xml), diff --git a/webapp/lsp/handlers.xqm b/webapp/lsp/handlers.xqm index d1c7076..74dcd8d 100644 --- a/webapp/lsp/handlers.xqm +++ b/webapp/lsp/handlers.xqm @@ -7,10 +7,14 @@ module namespace hnd="lsp/handlers"; (:~ structure returned by tree walkers :) declare record hnd:Result( result as item()*, - skipchildren? as xs:boolean:=false() + skipchildren? as xs:boolean:=false(), + extras? ); -declare type hnd:actionFn as fn($parse as element(),$state as hnd:Result ) as 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); diff --git a/webapp/lsp/lsp-ws.xqm b/webapp/lsp/lsp-ws.xqm index 378af2d..312e6bc 100644 --- a/webapp/lsp/lsp-ws.xqm +++ b/webapp/lsp/lsp-ws.xqm @@ -35,7 +35,7 @@ function lsp-ws:message( $message as xs:string ) as empty-sequence() { - let $json := rpc:parse($message) + let $json := rpc:parse($message=>trace("IN: ")) return if(exists($json)) then rpc:reply($json) else message($message,"bad RPC: ") diff --git a/webapp/lsp/position.xqm b/webapp/lsp/position.xqm index b278f0c..3fd4e94 100644 --- a/webapp/lsp/position.xqm +++ b/webapp/lsp/position.xqm @@ -87,8 +87,8 @@ as lspt:Range{ (:~ range for $text from indices:) declare function pos:range-from-ast( - $text as xs:string, -$ast as element(*)) + $ast as element(*), + $text as xs:string) as lspt:Range{ lspt:Range( pos:toPosition($text,number($ast/@start)), diff --git a/webapp/lsp/providers/documentSymbols.xqm b/webapp/lsp/providers/documentSymbols.xqm index 7f48006..1430bd4 100644 --- a/webapp/lsp/providers/documentSymbols.xqm +++ b/webapp/lsp/providers/documentSymbols.xqm @@ -51,8 +51,9 @@ declare function syms:FunctionDecl($parse as element(FunctionDecl),$state as hn as hnd:Result{ let $name:=syms:localName($parse/UnreservedFunctionEQName) let $prev:=$state?result[$name eq ?name] - let $range:=lspt:Range(lspt:Position(0,0),lspt:Position(0,3)) - let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Method'),$range,$range,"FUN") + let $range:=pos:range-from-ast($parse,$state?context?text)=>trace("POSSSS") + let $full-range:=$range + let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Method'),$range,$full-range,"FUN") return ($state?result,$sym)=>hnd:Result(true()) };