(:~ 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 ); (:~ 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 ") gt string-length($text)=>trace("L ")) 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) 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}` }; (:~ 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 }; (:~ full range for $text :) declare function pos:full-range($text as xs:string) as pos:Range{ pos:Range(pos:toPosition($text,0), pos:toPosition($text, string-length($text)-1)) };