[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.
|
||||
## 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
|
||||
State is held in websocket attributes.
|
||||
|
||||
|
@ -13,12 +13,17 @@ State is held in websocket attributes.
|
|||
|file-{uuid}|name of websocket attribute with textDocument|
|
||||
|parse-{uuid}|name of websocket attribute with parse tree XML|
|
||||
## 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
|
||||
|
||||
#### uses
|
||||
* https://github.com/FurqanSoftware/codemirror-languageserver
|
||||
* https://hjr265.me/blog/codemirror-lsp/
|
||||
|
||||
### 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
|
||||
|
||||
## Uses
|
||||
#### Uses
|
||||
* 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
|
||||
|
@ -27,9 +32,9 @@ Or `node --max-old-space-size=8192 node_modules/webpack-dev-serve
|
|||
r/bin/webpack-dev-server.js`
|
||||
|
||||
|
||||
## Notes
|
||||
## Related links
|
||||
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
|
||||
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://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 $key:=docs:key($socket,$uri,"textDocument")
|
||||
|
||||
let $file:=if(exists($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 $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)
|
||||
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)
|
||||
|
||||
return ( ws:set($socket,$key,$file)
|
||||
|
@ -80,7 +84,7 @@ ws:get($socket,"files",{})=>map:keys()
|
|||
};
|
||||
|
||||
(: document info :)
|
||||
declare type docs:property as enum(
|
||||
declare type docs:Property as enum(
|
||||
"textDocument",
|
||||
"parse"
|
||||
);
|
||||
|
@ -89,7 +93,7 @@ declare type docs:property as enum(
|
|||
declare function docs:key(
|
||||
$socket as xs:string,
|
||||
$file as xs:string,
|
||||
$property as docs:property
|
||||
$property as docs:Property
|
||||
) as xs:string?
|
||||
{
|
||||
ws:get($socket,"files")($file)($property)
|
||||
|
@ -99,7 +103,7 @@ declare function docs:key(
|
|||
declare function docs:get(
|
||||
$socket as xs:string,
|
||||
$file as xs:string,
|
||||
$property as docs:property
|
||||
$property as docs:Property
|
||||
) as item()?
|
||||
{
|
||||
ws:get($socket,
|
||||
|
@ -117,6 +121,6 @@ declare function docs:parse(
|
|||
let $xml:= p:parse-Module($text)=>prof:time("⏱️ p:parse-Module " || $file)
|
||||
return (
|
||||
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" }]
|
||||
},
|
||||
"hoverProvider": false,
|
||||
"documentSymbolProvider": true,
|
||||
"documentSymbolProvider": false,
|
||||
"documentRangeFormattingProvider": false,
|
||||
"colorProvider": {},
|
||||
"foldingRangeProvider": true,
|
||||
"selectionRangeProvider": true,
|
||||
"foldingRangeProvider": false,
|
||||
"selectionRangeProvider": false,
|
||||
"documentLinkProvider": {},
|
||||
"serverInfo": {
|
||||
"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";
|
||||
|
||||
(: map methods to functions :)
|
||||
declare variable $rpc:methods:=map:merge((
|
||||
declare variable $rpc:Methods:=map:merge((
|
||||
map{
|
||||
"initialize" : rpc:method-initialize#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
|
||||
function rpc:reply($json as map(*))
|
||||
as empty-sequence() {
|
||||
let $method := $json?method
|
||||
let $f :=$rpc:methods?($method)
|
||||
let $f :=$rpc:Methods?($json?method)
|
||||
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 :)
|
||||
|
|
|
@ -2,30 +2,17 @@ module namespace lint="lsp/lint";
|
|||
(:
|
||||
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.
|
||||
to: number
|
||||
markClass?: string When given, add an extra CSS class to parts of the code that this diagnostic applies to.
|
||||
|
||||
The end position. May be equal to from, though actually covering text is preferable.
|
||||
severity: "error" | "hint" | "info" | "warning"
|
||||
source?: string An optional source string indicating where the diagnostic is coming from. You can put the name of your linter here, if applicable.
|
||||
|
||||
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.
|
||||
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.
|
||||
:)
|
|
@ -1,17 +1,46 @@
|
|||
module namespace lsp-diags = 'lsp-diags';
|
||||
|
||||
import module namespace docs="lsp/docs" at "docs.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(
|
||||
$uri as xs:string,
|
||||
$xml as element(Module|ERROR))
|
||||
$text as xs:string,
|
||||
$xml as lsp-diags:ParseResult)
|
||||
as map(*){
|
||||
let $diagnostics:=[]
|
||||
=>array:append(pos:nostic(pos:Range(pos:Position(0,1),pos:Position(0,3)),1,"A test"))
|
||||
let $diagnostics:=if($xml/self::ERROR)
|
||||
then [lsp-diags:parse-error($text, $xml)]
|
||||
else []
|
||||
|
||||
return {"jsonrpc": "2.0",
|
||||
"method":"textDocument/publishDiagnostics",
|
||||
"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(*))
|
||||
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(
|
||||
map{"off":0,"line":0},
|
||||
fn($r, $i){$r?line < $pos?line},
|
||||
fn($r,$i){ let $next:=$nl[$i]
|
||||
return if(empty($next))
|
||||
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
|
||||
return if($off>string-length($text))
|
||||
|
@ -59,9 +60,11 @@ as xs:integer
|
|||
(:~ convert index into Position :)
|
||||
declare function pos:toPosition($text as xs:string, $index as xs:integer)
|
||||
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 $next:=$nl[$i=>trace("I ")]
|
||||
let $next:=$nl[$i]
|
||||
return exists($next) and $next < $index
|
||||
}
|
||||
|
||||
|
@ -73,19 +76,4 @@ as pos:Position {
|
|||
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 => {
|
||||
lsp.openSearchPanel(view);
|
||||
};
|
||||
document.getElementById("lint").onclick = e => {
|
||||
document.getElementById("lint").onclick = async e => {
|
||||
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);
|
||||
};
|
||||
document.getElementById("load").onchange = e => {
|
||||
|
|
Loading…
Add table
Reference in a new issue