[fix] position
This commit is contained in:
parent
e3766ee6a4
commit
8f2368ce7b
9 changed files with 88 additions and 67 deletions
17
README.md
17
README.md
|
@ -2,7 +2,7 @@ Work in progress.
|
||||||
|
|
||||||
An attempt to write a [Language Server Protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol) server using XQuery 4.0 and the [BaseX websocket](https://docs.basex.org/main/WebSockets) feature.
|
An attempt to write a [Language Server Protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol) server using XQuery 4.0 and the [BaseX websocket](https://docs.basex.org/main/WebSockets) feature.
|
||||||
## LSP Server
|
## LSP Server
|
||||||
* `webapp/lsp` The LSP implementation in XQuery using WebSockets for transport and the [JSON-RPC](https://www.jsonrpc.org/specification) 2.0 API for message format.
|
* `webapp/lsp` The LSP implementation in XQuery using WebSockets for transport and the [JSON-RPC](https://www.jsonrpc.org/specification) 2.0 API for data format.
|
||||||
### Dev notes
|
### Dev notes
|
||||||
State is held in websocket attributes.
|
State is held in websocket attributes.
|
||||||
|
|
||||||
|
@ -13,12 +13,17 @@ State is held in websocket attributes.
|
||||||
|file-{uuid}|name of websocket attribute with textDocument|
|
|file-{uuid}|name of websocket attribute with textDocument|
|
||||||
|parse-{uuid}|name of websocket attribute with parse tree XML|
|
|parse-{uuid}|name of websocket attribute with parse tree XML|
|
||||||
## Sample html clients
|
## Sample html clients
|
||||||
### Using `CodeMirror6`
|
### Using `CodeMirror6` https://codemirror.net/
|
||||||
* `webapp/static/codemirror` A test html page using the [CodeMirror6 editor](https://codemirror.net/) that connects to the BaseX LSP instance
|
* `webapp/static/codemirror` A test html page using the [CodeMirror6 editor](https://codemirror.net/) that connects to the BaseX LSP instance
|
||||||
|
|
||||||
|
#### uses
|
||||||
|
* https://github.com/FurqanSoftware/codemirror-languageserver
|
||||||
|
* https://hjr265.me/blog/codemirror-lsp/
|
||||||
|
|
||||||
### Using `Ace Editor`
|
### Using `Ace Editor`
|
||||||
* `webapp/static/ace` A test html page using the [Ace editor](https://ace.c9.io/) that connects to the BaseX LSP instance
|
* `webapp/static/ace` A test html page using the [Ace editor](https://ace.c9.io/) that connects to the BaseX LSP instance
|
||||||
|
|
||||||
## Uses
|
#### Uses
|
||||||
* https://github.com/mkslanc/ace-linters https://mkslanc.github.io/ace-linters/
|
* https://github.com/mkslanc/ace-linters https://mkslanc.github.io/ace-linters/
|
||||||
|
|
||||||
I needed `set NODE_OPTIONS=--max_old_space_size=8192` for build to complete
|
I needed `set NODE_OPTIONS=--max_old_space_size=8192` for build to complete
|
||||||
|
@ -27,9 +32,9 @@ Or `node --max-old-space-size=8192 node_modules/webpack-dev-serve
|
||||||
r/bin/webpack-dev-server.js`
|
r/bin/webpack-dev-server.js`
|
||||||
|
|
||||||
|
|
||||||
## Notes
|
## Related links
|
||||||
java -cp org.eclipse.lemminx-uber.jar org.eclipse.lemminx.XMLServerSocketLauncher`
|
java -cp org.eclipse.lemminx-uber.jar org.eclipse.lemminx.XMLServerSocketLauncher`
|
||||||
Using https://github.com/mkslanc/ace-linters https://mkslanc.github.io/ace-linters/
|
|
||||||
|
|
||||||
Make a websocket server for lsp on port 3000
|
Make a websocket server for lsp on port 3000
|
||||||
https://mkslanc.github.io/ace-linters/websocket.html
|
https://mkslanc.github.io/ace-linters/websocket.html
|
||||||
|
@ -38,5 +43,5 @@ http://localhost:3000/exampleServer
|
||||||
|
|
||||||
https://github.com/mkslanc/ace-linters/blob/c1b317e01299016ac7da6588361228637f4eac25/packages/demo/websockets-lsp/server/server.ts
|
https://github.com/mkslanc/ace-linters/blob/c1b317e01299016ac7da6588361228637f4eac25/packages/demo/websockets-lsp/server/server.ts
|
||||||
|
|
||||||
https://www.jsonrpc.org/specification
|
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"cells":[{"kind":2,"language":"xquery","value":"(:<:)\r\n\r\nimport module namespace docs=\"lsp/docs\" at \"/srv/basex/webapp/lsp/docs.xqm\";"},{"kind":2,"language":"xquery","value":"ws:ids()"},{"kind":2,"language":"xquery","value":"let $sock:=foot(ws:ids())\r\nlet $f:=docs:list($sock)\r\nlet $t:=docs:get($sock,$f,\"textDocument\")\r\nreturn $t"}]}
|
{"cells":[{"kind":2,"language":"xquery","value":"(:<:)\r\n\r\nimport module namespace docs=\"lsp/docs\" at \"/srv/basex/webapp/lsp/docs.xqm\";"},{"kind":2,"language":"xquery","value":"ws:ids()"},{"kind":2,"language":"xquery","value":"let $sock:=foot(ws:ids())\r\nlet $f:=docs:list($sock)\r\nlet $t:=docs:get($sock,$f,\"textDocument\")\r\nreturn $t"},{"kind":2,"language":"xquery","value":"let $sock:=foot(ws:ids())\r\nlet $f:=docs:list($sock)\r\nlet $t:=docs:get($sock,$f,\"parse\")\r\nreturn $t/self::ERROR"},{"kind":2,"language":"xquery","value":"let $sock:=foot(ws:ids())\r\nlet $f:=docs:list($sock)\r\nlet $t:=docs:get($sock,$f,\"textDocument\")?text\r\nreturn string-to-codepoints($t)=>index-of(10)"}]}
|
|
@ -45,13 +45,17 @@ declare function docs:change(
|
||||||
{
|
{
|
||||||
let $uri:=$params?textDocument?uri
|
let $uri:=$params?textDocument?uri
|
||||||
let $key:=docs:key($socket,$uri,"textDocument")
|
let $key:=docs:key($socket,$uri,"textDocument")
|
||||||
|
|
||||||
let $file:=if(exists($key))
|
let $file:=if(exists($key))
|
||||||
then ws:get($socket,$key)
|
then ws:get($socket,$key)
|
||||||
else error("no file")
|
else error(xs:QName("docs:change"), "no file" || $uri)
|
||||||
|
|
||||||
let $ver:=$params?textDocument?version
|
let $ver:=$params?textDocument?version
|
||||||
let $text:=if($ver eq 1+ $file?version)
|
let $IGNORE_VER:=true()
|
||||||
|
let $text:=if($IGNORE_VER or $ver eq 1+ $file?version)
|
||||||
then pos:apply-changes($file?text,$params?contentChanges)
|
then pos:apply-changes($file?text,$params?contentChanges)
|
||||||
else error("bad ver")
|
else error(xs:QName("docs:change"),`badver got {$ver} expecting: {1+ $file?version}`)
|
||||||
|
|
||||||
let $file:=$file=>map:put("version",$ver)=>map:put("text",$text)
|
let $file:=$file=>map:put("version",$ver)=>map:put("text",$text)
|
||||||
|
|
||||||
return ( ws:set($socket,$key,$file)
|
return ( ws:set($socket,$key,$file)
|
||||||
|
@ -80,7 +84,7 @@ ws:get($socket,"files",{})=>map:keys()
|
||||||
};
|
};
|
||||||
|
|
||||||
(: document info :)
|
(: document info :)
|
||||||
declare type docs:property as enum(
|
declare type docs:Property as enum(
|
||||||
"textDocument",
|
"textDocument",
|
||||||
"parse"
|
"parse"
|
||||||
);
|
);
|
||||||
|
@ -89,7 +93,7 @@ declare type docs:property as enum(
|
||||||
declare function docs:key(
|
declare function docs:key(
|
||||||
$socket as xs:string,
|
$socket as xs:string,
|
||||||
$file as xs:string,
|
$file as xs:string,
|
||||||
$property as docs:property
|
$property as docs:Property
|
||||||
) as xs:string?
|
) as xs:string?
|
||||||
{
|
{
|
||||||
ws:get($socket,"files")($file)($property)
|
ws:get($socket,"files")($file)($property)
|
||||||
|
@ -99,7 +103,7 @@ declare function docs:key(
|
||||||
declare function docs:get(
|
declare function docs:get(
|
||||||
$socket as xs:string,
|
$socket as xs:string,
|
||||||
$file as xs:string,
|
$file as xs:string,
|
||||||
$property as docs:property
|
$property as docs:Property
|
||||||
) as item()?
|
) as item()?
|
||||||
{
|
{
|
||||||
ws:get($socket,
|
ws:get($socket,
|
||||||
|
@ -117,6 +121,6 @@ declare function docs:parse(
|
||||||
let $xml:= p:parse-Module($text)=>prof:time("⏱️ p:parse-Module " || $file)
|
let $xml:= p:parse-Module($text)=>prof:time("⏱️ p:parse-Module " || $file)
|
||||||
return (
|
return (
|
||||||
ws:set($socket,docs:key($socket,$file,"parse"),$xml),
|
ws:set($socket,docs:key($socket,$file,"parse"),$xml),
|
||||||
lsp-diags:publish($file,$xml)
|
lsp-diags:publish($file, $text, $xml)
|
||||||
)
|
)
|
||||||
};
|
};
|
|
@ -7,11 +7,11 @@
|
||||||
"documentSelector": [{ "language": "xquery" }]
|
"documentSelector": [{ "language": "xquery" }]
|
||||||
},
|
},
|
||||||
"hoverProvider": false,
|
"hoverProvider": false,
|
||||||
"documentSymbolProvider": true,
|
"documentSymbolProvider": false,
|
||||||
"documentRangeFormattingProvider": false,
|
"documentRangeFormattingProvider": false,
|
||||||
"colorProvider": {},
|
"colorProvider": {},
|
||||||
"foldingRangeProvider": true,
|
"foldingRangeProvider": false,
|
||||||
"selectionRangeProvider": true,
|
"selectionRangeProvider": false,
|
||||||
"documentLinkProvider": {},
|
"documentLinkProvider": {},
|
||||||
"serverInfo": {
|
"serverInfo": {
|
||||||
"name": "XQuery 4.0b Language Server",
|
"name": "XQuery 4.0b Language Server",
|
||||||
|
|
|
@ -6,7 +6,7 @@ module namespace rpc = 'rpc';
|
||||||
import module namespace lsp-text = 'lsp-text' at "lsp-text.xqm";
|
import module namespace lsp-text = 'lsp-text' at "lsp-text.xqm";
|
||||||
|
|
||||||
(: map methods to functions :)
|
(: map methods to functions :)
|
||||||
declare variable $rpc:methods:=map:merge((
|
declare variable $rpc:Methods:=map:merge((
|
||||||
map{
|
map{
|
||||||
"initialize" : rpc:method-initialize#1,
|
"initialize" : rpc:method-initialize#1,
|
||||||
"initialized" : rpc:method-initialized#1,
|
"initialized" : rpc:method-initialized#1,
|
||||||
|
@ -31,14 +31,17 @@ as map(*)?
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
(:~ send replay to $json :)
|
(:~ send reply to $json message
|
||||||
|
get functions for methods
|
||||||
|
evaluate function with message
|
||||||
|
send any responses
|
||||||
|
:)
|
||||||
declare
|
declare
|
||||||
function rpc:reply($json as map(*))
|
function rpc:reply($json as map(*))
|
||||||
as empty-sequence() {
|
as empty-sequence() {
|
||||||
let $method := $json?method
|
let $f :=$rpc:Methods?($json?method)
|
||||||
let $f :=$rpc:methods?($method)
|
|
||||||
let $response := $f!.($json)
|
let $response := $f!.($json)
|
||||||
return if(exists($response)) then ws:send($response=>trace("REPLY: "),ws:id())
|
return $response!ws:send(.=>trace("REPLY: "),ws:id())
|
||||||
};
|
};
|
||||||
|
|
||||||
(:~ canned initialize response :)
|
(:~ canned initialize response :)
|
||||||
|
|
|
@ -2,30 +2,17 @@ module namespace lint="lsp/lint";
|
||||||
(:
|
(:
|
||||||
Describes a problem or hint for a piece of code.
|
Describes a problem or hint for a piece of code.
|
||||||
|
|
||||||
from: number
|
from: number The start position of the relevant text.
|
||||||
|
to: number The end position. May be equal to from, though actually covering text is preferable.
|
||||||
|
severity: "error" | "hint" | "info" | "warning". The severity of the problem. This will influence how it is displayed.
|
||||||
|
|
||||||
The start position of the relevant text.
|
markClass?: string When given, add an extra CSS class to parts of the code that this diagnostic applies to.
|
||||||
to: number
|
|
||||||
|
|
||||||
The end position. May be equal to from, though actually covering text is preferable.
|
source?: string An optional source string indicating where the diagnostic is coming from. You can put the name of your linter here, if applicable.
|
||||||
severity: "error" | "hint" | "info" | "warning"
|
|
||||||
|
|
||||||
The severity of the problem. This will influence how it is displayed.
|
message: string The message associated with this diagnostic.
|
||||||
markClass?: string
|
|
||||||
|
|
||||||
When given, add an extra CSS class to parts of the code that this diagnostic applies to.
|
|
||||||
source?: string
|
|
||||||
|
|
||||||
An optional source string indicating where the diagnostic is coming from. You can put the name of your linter here, if applicable.
|
|
||||||
message: string
|
|
||||||
|
|
||||||
The message associated with this diagnostic.
|
|
||||||
renderMessage?: fn(view: EditorView) → Node
|
|
||||||
|
|
||||||
An optional custom rendering function that displays the message as a DOM node.
|
|
||||||
actions?: readonly Action[]
|
|
||||||
|
|
||||||
An optional array of actions that can be taken on this diagnostic.
|
|
||||||
|
|
||||||
|
renderMessage?: fn(view: EditorView) → Node An optional custom rendering function that displays the message as a DOM node.
|
||||||
|
|
||||||
|
actions?: readonly Action[] An optional array of actions that can be taken on this diagnostic.
|
||||||
:)
|
:)
|
|
@ -1,17 +1,46 @@
|
||||||
module namespace lsp-diags = 'lsp-diags';
|
module namespace lsp-diags = 'lsp-diags';
|
||||||
|
|
||||||
import module namespace docs="lsp/docs" at "docs.xqm";
|
|
||||||
import module namespace pos="lsp/position" at "position.xqm";
|
import module namespace pos="lsp/position" at "position.xqm";
|
||||||
|
|
||||||
|
declare type lsp-diags:ParseResult as element(Module|ERROR);
|
||||||
|
(:~
|
||||||
|
from: number The start position of the relevant text.
|
||||||
|
to: number The end position. May be equal to from, though actually covering text is preferable.
|
||||||
|
severity: "error" | "hint" | "info" | "warning" The severity of the problem. This will influence how it is displayed.
|
||||||
|
markClass?: string When given, add an extra CSS class to parts of the code that this diagnostic applies to.
|
||||||
|
source?: string An optional source string indicating where the diagnostic is coming from. You can put the name of your linter here, if applicable.
|
||||||
|
message: string The message associated with this diagnostic.
|
||||||
|
renderMessage?: fn(view: EditorView) → Node An optional custom rendering function that displays the message as a DOM node.
|
||||||
|
actions?: readonly Action[] An optional array of actions that can be taken on this diagnostic.
|
||||||
|
:)
|
||||||
|
declare record lsp-diags:nostic(
|
||||||
|
range as pos:Range,
|
||||||
|
severity as xs:integer, (: enum('error', 'hint', 'info', 'warning') :)
|
||||||
|
message as xs:string
|
||||||
|
);
|
||||||
|
|
||||||
declare function lsp-diags:publish(
|
declare function lsp-diags:publish(
|
||||||
$uri as xs:string,
|
$uri as xs:string,
|
||||||
$xml as element(Module|ERROR))
|
$text as xs:string,
|
||||||
|
$xml as lsp-diags:ParseResult)
|
||||||
as map(*){
|
as map(*){
|
||||||
let $diagnostics:=[]
|
let $diagnostics:=if($xml/self::ERROR)
|
||||||
=>array:append(pos:nostic(pos:Range(pos:Position(0,1),pos:Position(0,3)),1,"A test"))
|
then [lsp-diags:parse-error($text, $xml)]
|
||||||
|
else []
|
||||||
|
|
||||||
return {"jsonrpc": "2.0",
|
return {"jsonrpc": "2.0",
|
||||||
"method":"textDocument/publishDiagnostics",
|
"method":"textDocument/publishDiagnostics",
|
||||||
"params":{"uri": $uri, "diagnostics": $diagnostics}
|
"params":{"uri": $uri, "diagnostics": $diagnostics}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(:~
|
||||||
|
<ERROR b="10819" e="10820" o="234" s="43">syntax error, found '}' while expecting [S,'else'] at line 290, column 3: ...} }; ? return bookmark info for children of $outlineItem as s...</ERROR>
|
||||||
|
:)
|
||||||
|
declare function lsp-diags:parse-error($text as xs:string, $xml as element(ERROR))
|
||||||
|
as map(*)?{
|
||||||
|
|
||||||
|
lsp-diags:nostic(pos:Range(pos:toPosition($xml, $xml/@b),
|
||||||
|
pos:toPosition($xml, $xml/@e)),
|
||||||
|
1,$xml/string())
|
||||||
|
};
|
|
@ -40,14 +40,15 @@ as xs:string{
|
||||||
declare function pos:resolvePosition($text as xs:string, $pos as map(*))
|
declare function pos:resolvePosition($text as xs:string, $pos as map(*))
|
||||||
as xs:integer
|
as xs:integer
|
||||||
{
|
{
|
||||||
let $nl:=characters($text)=>index-of(char("\n"))
|
let $nl:= index-of(string-to-codepoints($text),10)
|
||||||
|
let $_:=trace(count($nl),"lines ")
|
||||||
let $s:= while-do(
|
let $s:= while-do(
|
||||||
map{"off":0,"line":0},
|
map{"off":0,"line":0},
|
||||||
fn($r, $i){$r?line < $pos?line},
|
fn($r, $i){$r?line < $pos?line},
|
||||||
fn($r,$i){ let $next:=$nl[$i]
|
fn($r,$i){ let $next:=$nl[$i]
|
||||||
return if(empty($next))
|
return if(empty($next))
|
||||||
then error(xs:QName("docs:range"),"bad line")
|
then error(xs:QName("docs:range"),"bad line")
|
||||||
else {"off": $next+1,"line":$r?line+1}=>trace("AA ") }
|
else {"off": $next+1,"line":$r?line+1} }
|
||||||
)
|
)
|
||||||
let $off:= $s?off+$pos?character
|
let $off:= $s?off+$pos?character
|
||||||
return if($off>string-length($text))
|
return if($off>string-length($text))
|
||||||
|
@ -59,9 +60,11 @@ as xs:integer
|
||||||
(:~ convert index into Position :)
|
(:~ convert index into Position :)
|
||||||
declare function pos:toPosition($text as xs:string, $index as xs:integer)
|
declare function pos:toPosition($text as xs:string, $index as xs:integer)
|
||||||
as pos:Position {
|
as pos:Position {
|
||||||
let $nl:=characters($text)=>index-of(char("\n"))
|
let $nl:= index-of(string-to-codepoints($text),10)
|
||||||
|
let $_:=trace(count($nl),"lines ")
|
||||||
|
|
||||||
let $predicate:=fn($r, $i){
|
let $predicate:=fn($r, $i){
|
||||||
let $next:=$nl[$i=>trace("I ")]
|
let $next:=$nl[$i]
|
||||||
return exists($next) and $next < $index
|
return exists($next) and $next < $index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,19 +76,4 @@ as pos:Position {
|
||||||
return pos:Position($s?line, $index - $s?off)
|
return pos:Position($s?line, $index - $s?off)
|
||||||
};
|
};
|
||||||
|
|
||||||
(:~
|
|
||||||
from: number The start position of the relevant text.
|
|
||||||
to: number The end position. May be equal to from, though actually covering text is preferable.
|
|
||||||
severity: "error" | "hint" | "info" | "warning" The severity of the problem. This will influence how it is displayed.
|
|
||||||
markClass?: string When given, add an extra CSS class to parts of the code that this diagnostic applies to.
|
|
||||||
source?: string An optional source string indicating where the diagnostic is coming from. You can put the name of your linter here, if applicable.
|
|
||||||
message: string The message associated with this diagnostic.
|
|
||||||
renderMessage?: fn(view: EditorView) → Node An optional custom rendering function that displays the message as a DOM node.
|
|
||||||
actions?: readonly Action[] An optional array of actions that can be taken on this diagnostic.
|
|
||||||
:)
|
|
||||||
declare record pos:nostic(
|
|
||||||
range as pos:Range,
|
|
||||||
severity as xs:integer, (: enum('error', 'hint', 'info', 'warning') :)
|
|
||||||
message as xs:string
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,13 @@ document.getElementById("connect").onclick = e => {
|
||||||
document.getElementById("search").onclick = e => {
|
document.getElementById("search").onclick = e => {
|
||||||
lsp.openSearchPanel(view);
|
lsp.openSearchPanel(view);
|
||||||
};
|
};
|
||||||
document.getElementById("lint").onclick = e => {
|
document.getElementById("lint").onclick = async e => {
|
||||||
console.log("word",view.state.wordAt(1));
|
console.log("word",view.state.wordAt(1));
|
||||||
|
const ser=document.getElementById("iServer").value;
|
||||||
|
//const transport = new WebTransport(ser);
|
||||||
|
|
||||||
|
// The connection can be used once ready fulfills
|
||||||
|
//await transport.ready;
|
||||||
lsp.openLintPanel(view);
|
lsp.openLintPanel(view);
|
||||||
};
|
};
|
||||||
document.getElementById("load").onchange = e => {
|
document.getElementById("load").onchange = e => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue