[add] initial change implementation

This commit is contained in:
Andy Bunce 2026-02-26 16:33:36 +00:00
parent eba2e564bb
commit c0814be67c
4 changed files with 667 additions and 17 deletions

View file

@ -4,12 +4,12 @@
@author andy bunce
:)
module namespace doci = 'urn: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+,
length as xs:integer,
separator? as xs:string,
starts? as xs:integer+
separator? as xs:string
);
declare record doci:line(
@ -41,37 +41,89 @@ declare record doci:Range(
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:=tokenize($text, '(\r\n?|\n\r?)')
let $starts:=hof:scan-left($lines, 0,
fn($res,$line){$res+string-length($ls)+string-length($line)}
)
let $lines:= doci:split-lines($text)
return doci:doci(
lines:= $lines,
length:= string-length($text),
separator:=$ls,
starts:= $starts
separator:=$ls
)
};
(: line separator, assume all same:)
(: 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:string{
$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 ()
default return $doci:default-separator
}
};
(: apply change:)
declare function doci:change($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
return switch(){
case $sline eq $eline and count($lines) eq 1
return let $line:=$doci?lines[$sline+1]
let $new:=substring($line,1,$range?start?character)
|| $lines ||
substring($line,1+$range?end?character)
let $ulines:=( $doci?lines[position() le $sline],
$new,
$doci?lines[position() gt 1+$sline]
)
return doci:doci($ulines, $doci?separator)
default return error(#doci:change,"oh")
}
};
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)
{
let $line:=do-until(
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")
@ -81,5 +133,12 @@ let $line:=do-until(
},
fn($r){$r?max eq $r?min}
)?max
return $line
};
return doci:line(
number:= $line,
text:= $doci?lines[$line],
from:= $doci?starts[$line],
to:= -1
)
};