basex-lsp/webapp/lsp/position.xqm

97 lines
No EOL
2.6 KiB
Text

(:~
positions in text
:)
module namespace pos="lsp/position";
(:~ json numbers :)
declare type pos:num as (xs:integer|xs:double);
(:~
@param line Line position in a document (zero-based).
@param character Character offset on a line in a document (zero-based).
:)
declare record pos:Position(
line as pos:num,
character as pos:num
);
(:~
@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
);
(:~ update $text with changes $chs from didChange
:)
declare function pos:apply-changes($text as xs:string, $chs as array(*))
as xs:string{
array:fold-left($chs,$text,pos:apply-change#2)
};
(:~ text updated single change :)
declare function pos:apply-change($text as xs:string, $ch as map(*))
as xs:string{
if(exists($ch?range))
then substring($text,1, pos:resolvePosition($text, $ch?range?start))
|| $ch?text ||
substring($text,pos:resolvePosition($text, $ch?range?end))
else $ch?text
};
(:~ find index from Position :)
declare function pos:resolvePosition($text as xs:string, $pos as pos:Position)
as pos:num
{
let $nl:= index-of(string-to-codepoints($text),10)
let $off:=if($pos?line eq 0)
then 0
else $nl[$pos?line]
return $off+$pos?character
};
(:~ convert index into Position :)
declare function pos:toPosition($text as xs:string,
$index as pos:num
)
as pos:Position {
let $nl:= if($index=>trace("IN ") ge string-length($text)=>trace("L "))
then error(xs:QName("pos:range"),"out of range")
else index-of(string-to-codepoints($text),10)
let $line:=pos:lineAt($nl,$index)
let $off:=if($line eq 0)
then 0
else $nl[$line]
return pos:Position($line, $index - $off)
};
(:~ line number for $pos :)
declare function pos:lineAt($nl as xs:integer*,$pos as pos:num)
as xs:integer
{
if(empty($nl) or $pos le head($nl))
then 0
else if($pos gt foot($nl))
then count($nl)
else let $s:=do-until(
{"min":1,"max":count($nl)},
fn($r){
let $mid:=round(($r?min+$r?max) div 2,0,"away-from-zero")
return if ($nl[$mid] lt $pos)
then map:put($r,"min",$mid)
else map:put($r,"max",$mid -1)
},
fn($r){$r?max eq $r?min}
)
return $s?max
};
(:~ format position for text display :)
declare function pos:ln-col($pos as pos:Position,$offset as xs:integer:=1)
{
`Ln { $pos?line + $offset}, Col { $pos?character + $offset}`
};