160 lines
4.6 KiB
Text
160 lines
4.6 KiB
Text
(:~ handle textDocument
|
|
|
|
@see https://codemirror.net/docs/ref/#state.Text
|
|
@author andy bunce
|
|
:)
|
|
module namespace doci = 'urn:quodatum:text:doci';
|
|
(: default line seperator if none found in text :)
|
|
declare variable $doci:default-separator:=file:line-separator();
|
|
|
|
declare record doci:doci(
|
|
lines as xs:string+,
|
|
separator? as xs:string
|
|
);
|
|
|
|
declare record doci:line(
|
|
from as xs:integer, (:number The position of the start of the line. :)
|
|
to as xs:integer, (:The position at the end of the line (before the line break,
|
|
or at the end of document for the last line).:)
|
|
number as xs:integer, (: This line's line number (1-based).:)
|
|
text as xs:string (: The line's content. :)
|
|
);
|
|
|
|
(:~ json numbers :)
|
|
declare type doci: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 doci:Position(
|
|
line as doci:num,
|
|
character as doci:num
|
|
);
|
|
|
|
(:~
|
|
@param line Line position in a document (zero-based).
|
|
@param character Character offset on a line in a document (zero-based).
|
|
:)
|
|
declare record doci:Range(
|
|
start as doci:Position,
|
|
end as doci:Position
|
|
);
|
|
|
|
(:
|
|
@see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didChange
|
|
:)
|
|
declare record doci:TextDocumentContentChangeEvent(
|
|
text as xs:string,
|
|
range? as doci:Range
|
|
);
|
|
|
|
(: create new doci from string :)
|
|
declare function doci:build($text as xs:string)
|
|
as doci:doci{
|
|
let $ls:=doci:separator($text )
|
|
let $lines:= doci:split-lines($text)
|
|
|
|
return doci:doci(
|
|
lines:= $lines,
|
|
separator:=$ls
|
|
)
|
|
};
|
|
|
|
(: return full text :)
|
|
declare function doci:text($doci as doci:doci)
|
|
as xs:string{
|
|
string-join($doci?lines,$doci?separator)
|
|
};
|
|
|
|
(: number of lines:)
|
|
declare function doci:lines($doci as doci:doci)
|
|
as xs:integer{
|
|
$doci?lines=>count()
|
|
};
|
|
|
|
(: detect line separator, assumes all the same:)
|
|
declare function doci:separator($text as xs:string)
|
|
as xs:string?{
|
|
switch () {
|
|
case contains($text," ") return " "
|
|
case contains($text," ") return " "
|
|
case contains($text," ") return " "
|
|
default return $doci:default-separator
|
|
}
|
|
};
|
|
|
|
(: apply change:)
|
|
declare function doci:update($doci as doci:doci,$change as doci:TextDocumentContentChangeEvent)
|
|
as doci:doci{
|
|
let $lines:= doci:split-lines($change?text)
|
|
return if(empty($change?range))
|
|
then doci:doci($lines,$doci?separator)
|
|
else
|
|
let $range:=$change?range
|
|
let $sline:= $range?start?line
|
|
let $eline:= $range?end?line
|
|
|
|
(: string from start line before insert :)
|
|
let $head:= substring( $doci?lines[$sline+1] ,1, $range?start?character )
|
|
=>trace("$head")
|
|
|
|
(: string from edit end to end of line :)
|
|
let $last := substring( $doci?lines[$eline+1] , $range?end?character +1)
|
|
=>trace("$last")
|
|
|
|
let $ulines := (
|
|
subsequence($doci?lines, 1, $sline) (: lines before edit :)
|
|
=>trace("before")
|
|
,
|
|
if($sline eq $eline)
|
|
then ($head || $lines[1] || $last)
|
|
|
|
else if(1=count($lines))
|
|
then $head || $lines[1] || $last
|
|
else (
|
|
$head || $lines[1],
|
|
subsequence( $lines,2, count( $lines - 2)),
|
|
$lines[last()] ||$last
|
|
),
|
|
|
|
subsequence($doci?lines, $eline+2) (: lines after edit :)
|
|
=>trace("after")
|
|
)
|
|
return doci:doci($ulines,$doci?separator)
|
|
};
|
|
|
|
declare function doci:split-lines($text as xs:string)
|
|
as xs:string+{
|
|
if($text eq "")
|
|
then $text
|
|
else tokenize($text, '(\r\n?|\n\r?)')
|
|
};
|
|
|
|
(: line from pos :)
|
|
declare function doci:lineAt($doci as doci:doci,$pos as xs:integer)
|
|
as doci:line {
|
|
let $starts:=hof:scan-left($doci?lines, 0,
|
|
fn($res,$line){$res+string-length($doci?seperator)+string-length($line)}
|
|
)
|
|
let $line:=if($pos gt $doci?length)
|
|
then error(#doci:range,"pos beyond range")
|
|
else do-until(
|
|
{"min":1,"max":count($doci?lines)},
|
|
fn($r){
|
|
let $mid:=round(($r?min+$r?max) div 2,0,"away-from-zero")
|
|
return if ($doci?starts[$mid] lt $pos)
|
|
then map:put($r,"min",$mid)
|
|
else map:put($r,"max",$mid -1)
|
|
},
|
|
fn($r){$r?max eq $r?min}
|
|
)?max
|
|
|
|
return doci:line(
|
|
number:= $line,
|
|
text:= $doci?lines[$line],
|
|
from:= $doci?starts[$line],
|
|
to:= -1
|
|
)
|
|
};
|
|
|