[mod] local bootstrap
This commit is contained in:
parent
ce69c61b6c
commit
74f2c74fb4
14 changed files with 387 additions and 33 deletions
|
@ -1,6 +1,6 @@
|
||||||
Work in progress.
|
Work in progress.
|
||||||
|
|
||||||
An attempt to write a [Language Server Protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol) server using 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.
|
||||||
## Server
|
## 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 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 format.
|
||||||
## Sample clients
|
## Sample clients
|
||||||
|
@ -26,4 +26,3 @@ https://github.com/mkslanc/ace-linters/blob/c1b317e01299016ac7da6588361228637f4e
|
||||||
|
|
||||||
https://www.jsonrpc.org/specification
|
https://www.jsonrpc.org/specification
|
||||||
|
|
||||||
yyy **GGG**
|
|
15
sample.msg/didChange/client.json
Normal file
15
sample.msg/didChange/client.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "textDocument/didChange",
|
||||||
|
"params": {
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///some/file.xml",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"contentChanges": [
|
||||||
|
{
|
||||||
|
"text": "3+1f"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,8 +14,8 @@
|
||||||
"selectionRangeProvider": true,
|
"selectionRangeProvider": true,
|
||||||
"documentLinkProvider": {},
|
"documentLinkProvider": {},
|
||||||
"serverInfo": {
|
"serverInfo": {
|
||||||
"name": "My Custom Language Server",
|
"name": "BaseX Language Server",
|
||||||
"version": "1.0.0"
|
"version": "0.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,8 +6,19 @@
|
||||||
module namespace docs="lsp/docs";
|
module namespace docs="lsp/docs";
|
||||||
import module namespace p="xq4" at "xq4.xqm";
|
import module namespace p="xq4" at "xq4.xqm";
|
||||||
|
|
||||||
|
declare record docs:Position(
|
||||||
|
(: Line position in a document (zero-based). :)
|
||||||
|
line as xs:integer,
|
||||||
|
|
||||||
|
(: Character offset on a line in a document (zero-based). :)
|
||||||
|
character as xs:integer
|
||||||
|
);
|
||||||
|
|
||||||
(: document info :)
|
(: document info :)
|
||||||
declare type docs:property as enum("textDocument","parse");
|
declare type docs:property as enum(
|
||||||
|
"textDocument",
|
||||||
|
"parse"
|
||||||
|
);
|
||||||
|
|
||||||
(: get $property for $file from session $socket :)
|
(: get $property for $file from session $socket :)
|
||||||
declare function docs:get(
|
declare function docs:get(
|
||||||
|
@ -42,4 +53,29 @@ return (
|
||||||
ws:set($socket,$keys?textDocument,$textDocument),
|
ws:set($socket,$keys?textDocument,$textDocument),
|
||||||
ws:set($socket,$keys?parse,$xml)
|
ws:set($socket,$keys?parse,$xml)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
declare function docs:resolvePosition(text as xs:string, pos as docs:Position)
|
||||||
|
as xs:integer
|
||||||
|
{
|
||||||
|
let line = 0, off = 0
|
||||||
|
while (line < pos.line) {
|
||||||
|
let next = text.indexOf("\n", off)
|
||||||
|
if (!next) throw new RangeError("Position out of bounds")
|
||||||
|
off = next + 1
|
||||||
|
line++
|
||||||
|
}
|
||||||
|
off += pos.character
|
||||||
|
if (off > string-length($text)) throw new RangeError("Position out of bounds")
|
||||||
|
return off
|
||||||
|
}
|
||||||
|
|
||||||
|
declare function docs:toPosition($text as xs:string, $pos as xs:integer)
|
||||||
|
as docs:Position {
|
||||||
|
for (let off = 0, line = 0;;) {
|
||||||
|
let next = text.indexOf("\n", off)
|
||||||
|
if (next < 0 || next >= pos) return {line, character: pos - off}
|
||||||
|
off = next + 1
|
||||||
|
line++
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,15 +2,17 @@
|
||||||
: @author andy bunce
|
: @author andy bunce
|
||||||
:)
|
:)
|
||||||
module namespace lsp-text = 'lsp-text';
|
module namespace lsp-text = 'lsp-text';
|
||||||
import module namespace p="xq4" at "xq4.xqm";
|
|
||||||
|
|
||||||
declare variable $lsp-text:methods:=map{
|
declare variable $lsp-text:methods:=map{
|
||||||
"textDocument/didOpen": lsp-text:didOpen#1,
|
"textDocument/didOpen": lsp-text:didOpen#1,
|
||||||
|
"textDocument/didChange": lsp-text:didChange#1,
|
||||||
"textDocument/didClose" : lsp-text:method-unknown#1,
|
"textDocument/didClose" : lsp-text:method-unknown#1,
|
||||||
"textDocument/didChange": lsp-text:method-unknown#1,
|
|
||||||
"textDocument/hover": lsp-text:hover#1,
|
"textDocument/hover": lsp-text:hover#1,
|
||||||
"textDocument/completion": lsp-text:completion#1
|
"textDocument/completion": lsp-text:completion#1
|
||||||
};
|
};
|
||||||
|
|
||||||
(:~ hover
|
(:~ hover
|
||||||
|
|
||||||
:)
|
:)
|
||||||
|
@ -53,18 +55,32 @@ as map(*)?
|
||||||
(:~ didOpen method response :)
|
(:~ didOpen method response :)
|
||||||
declare
|
declare
|
||||||
function lsp-text:didOpen($json as map(*))
|
function lsp-text:didOpen($json as map(*))
|
||||||
as map(*)?
|
as empty-sequence()
|
||||||
{
|
{
|
||||||
let $textDoc:=$json?params?textDocument
|
let $textDoc:=$json?params?textDocument
|
||||||
|
|
||||||
let $x:=job:eval(xs:anyURI("parse.xq"),
|
let $x:=job:eval(xs:anyURI("parse.xq"),
|
||||||
{"textDocument":$textDoc,"webSocket":ws:id()},
|
{"textDocument":$textDoc,"webSocket":ws:id()},
|
||||||
{ 'cache': true() }
|
{ 'cache': true() }
|
||||||
)
|
)
|
||||||
|
|
||||||
return ws:set(ws:id(),$textDoc?uri,$textDoc)
|
return ()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(:~ didOpen method response :)
|
||||||
|
declare
|
||||||
|
function lsp-text:didChange($json as map(*))
|
||||||
|
as map(*)?
|
||||||
|
{
|
||||||
|
let $textDoc:=$json?params?textDocument
|
||||||
|
let $x:=job:eval(xs:anyURI("parse.xq"),
|
||||||
|
{"textDocument":$textDoc,"webSocket":ws:id()},
|
||||||
|
{ 'cache': true() }
|
||||||
|
)
|
||||||
|
|
||||||
|
return ()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
(:~ unknown method response :)
|
(:~ unknown method response :)
|
||||||
declare
|
declare
|
||||||
function lsp-text:method-unknown($json as map(*))
|
function lsp-text:method-unknown($json as map(*))
|
||||||
|
|
279
webapp/lsp/snippets.jsonc
Normal file
279
webapp/lsp/snippets.jsonc
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
{
|
||||||
|
"new library module": {
|
||||||
|
"isFileTemplate": true,
|
||||||
|
"prefix": "library module",
|
||||||
|
"body": [
|
||||||
|
"xquery version '3.1';",
|
||||||
|
"(:~",
|
||||||
|
"@author: ",
|
||||||
|
"@date: $CURRENT_YEAR/$CURRENT_MONTH/$CURRENT_DATE",
|
||||||
|
":)",
|
||||||
|
"module namespace ${1:prefix} = '${2:http://www.example.com/}';",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"description": "New library module template"
|
||||||
|
},
|
||||||
|
"new main module": {
|
||||||
|
"isFileTemplate": true,
|
||||||
|
"prefix": "main module",
|
||||||
|
"body": [
|
||||||
|
"xquery version '3.1';",
|
||||||
|
"(:~",
|
||||||
|
":)",
|
||||||
|
"${1:expr}",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"description": "New main module template"
|
||||||
|
},
|
||||||
|
"flowr": {
|
||||||
|
"prefix": [
|
||||||
|
"for",
|
||||||
|
"flowr"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"for \\$${1:var} at \\$${2:pos} in ${3:expr}",
|
||||||
|
"let \\$${4:var2} := ${5:expr}",
|
||||||
|
"where ${6:boolean}",
|
||||||
|
"order by ${7:expr}",
|
||||||
|
"return ${8:expr2}"
|
||||||
|
],
|
||||||
|
"description": "Full FLOWR expression"
|
||||||
|
},
|
||||||
|
"return": {
|
||||||
|
"prefix": "return",
|
||||||
|
"body": "return ${1:expr}"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"prefix": "import",
|
||||||
|
"body": "import module namespace ${1:ns} = '${2:http://www.example.com/}';",
|
||||||
|
"description": "Import module"
|
||||||
|
},
|
||||||
|
"if": {
|
||||||
|
"prefix": "if",
|
||||||
|
"body": [
|
||||||
|
"if (${1:boolean})",
|
||||||
|
"then ${2:expr1}",
|
||||||
|
"else ${3:expr2}"
|
||||||
|
],
|
||||||
|
"description": "If then else expression"
|
||||||
|
},
|
||||||
|
"module": {
|
||||||
|
"prefix": "module",
|
||||||
|
"body": "module namespace ${1:ns} = '${2:http://www.example.com}';"
|
||||||
|
},
|
||||||
|
"every": {
|
||||||
|
"prefix": "every",
|
||||||
|
"body": "every \\$${1:varname} in ${2:expr} satisfies ${3:expr}"
|
||||||
|
},
|
||||||
|
"some": {
|
||||||
|
"prefix": "some",
|
||||||
|
"body": "some \\$${1:varname} in ${2:expr} satisfies ${3:expr}"
|
||||||
|
},
|
||||||
|
"declare namespace": {
|
||||||
|
"prefix": [
|
||||||
|
"declare",
|
||||||
|
"namespace"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"declare ${1:prefix}='${2:namespace}';",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"description": "declare namespace"
|
||||||
|
},
|
||||||
|
"declare base-uri": {
|
||||||
|
"prefix": [
|
||||||
|
"declare",
|
||||||
|
"baseuri"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"declare base-uri '${1:uriliteral}';",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"description": "declare base-uri"
|
||||||
|
},
|
||||||
|
"declare option": {
|
||||||
|
"prefix": [
|
||||||
|
"declare",
|
||||||
|
"option"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"declare option ${1:eqname} '${2:string}';",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"description": "declare option"
|
||||||
|
},
|
||||||
|
"declare function": {
|
||||||
|
"prefix": [
|
||||||
|
"declare",
|
||||||
|
"function"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"(:~ ${1:name} :)",
|
||||||
|
"declare function ${2:ns}:${1:name}()",
|
||||||
|
"as ${3:type}{",
|
||||||
|
"${3:expr}",
|
||||||
|
"};",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"description": "declare function"
|
||||||
|
},
|
||||||
|
"declare variable": {
|
||||||
|
"prefix": [
|
||||||
|
"declare",
|
||||||
|
"variable"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"(:~ \\$${1:varname} :)",
|
||||||
|
"declare variable \\$${1:varname} := ${2:expr};",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"description": "declare variable"
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
"prefix": "switch",
|
||||||
|
"body": [
|
||||||
|
"switch(${1:foo})",
|
||||||
|
"case ${2:foo} return ${3:true}",
|
||||||
|
"default return ${4:false}"
|
||||||
|
],
|
||||||
|
"description": "switch statement"
|
||||||
|
},
|
||||||
|
"typeswitch": {
|
||||||
|
"prefix": "type",
|
||||||
|
"body": [
|
||||||
|
"typeswitch(${1:foo})",
|
||||||
|
"case ${2:foo} return ${3:true}",
|
||||||
|
"default return ${4:false}"
|
||||||
|
],
|
||||||
|
"description": "typeswitch statement"
|
||||||
|
},
|
||||||
|
"try": {
|
||||||
|
"prefix": "try",
|
||||||
|
"body": [
|
||||||
|
"try {",
|
||||||
|
" ${1:expr}",
|
||||||
|
"} catch ${2:*}",
|
||||||
|
" { ${3:expr}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "try catch"
|
||||||
|
},
|
||||||
|
"tumbling": {
|
||||||
|
"prefix": [
|
||||||
|
"for",
|
||||||
|
"tumbling",
|
||||||
|
"window"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"for tumbling window \\$${1:varname} in ${2:expr}",
|
||||||
|
"start at \\$${3:start} when ${4:expr}",
|
||||||
|
"end at \\$${5:end} when ${6:expr}",
|
||||||
|
"return ${7:expr}"
|
||||||
|
],
|
||||||
|
"description": "tumbling window"
|
||||||
|
},
|
||||||
|
"sliding": {
|
||||||
|
"prefix": [
|
||||||
|
"for",
|
||||||
|
"sliding",
|
||||||
|
"window"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"for sliding window \\$${1:varname} in ${2:expr}",
|
||||||
|
"start at \\$${3:start} when ${4:expr}",
|
||||||
|
"end at \\$${5:end} when ${6:expr}",
|
||||||
|
"return ${7:expr}"
|
||||||
|
],
|
||||||
|
"description": "sliding window"
|
||||||
|
},
|
||||||
|
"let": {
|
||||||
|
"prefix": "let",
|
||||||
|
"body": "let \\$${1:varname} := ${2:expr}"
|
||||||
|
},
|
||||||
|
"castable": {
|
||||||
|
"body": "castable as ${1:atomicType}"
|
||||||
|
},
|
||||||
|
"cast": {
|
||||||
|
"body": "cast as ${1:atomicType}"
|
||||||
|
},
|
||||||
|
// Updates ***************
|
||||||
|
"update insert": {
|
||||||
|
"prefix": [
|
||||||
|
"update",
|
||||||
|
"insert"
|
||||||
|
],
|
||||||
|
"body": "insert node ${1:expr} into ${2:xpath}"
|
||||||
|
},
|
||||||
|
"update delete": {
|
||||||
|
"prefix": ["delete","update"],
|
||||||
|
"body": "delete node ${1:xpath}"
|
||||||
|
},
|
||||||
|
"update replace node": {
|
||||||
|
"prefix":["update","replace"],
|
||||||
|
"body": "replace node ${1:xpath} with ${2:expr}"
|
||||||
|
},
|
||||||
|
"update replace value": {
|
||||||
|
"prefix": [ "update",
|
||||||
|
"replace",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"body": "replace value of node ${1:xpath} with ${2:expr}"
|
||||||
|
},
|
||||||
|
"update rename": {
|
||||||
|
"prefix": [
|
||||||
|
"update",
|
||||||
|
"rename"
|
||||||
|
],
|
||||||
|
"body": "rename node ${1:xpath} as ${2:eqname}"
|
||||||
|
},
|
||||||
|
"copy modify return": {
|
||||||
|
"prefix": [
|
||||||
|
"copy",
|
||||||
|
"modify",
|
||||||
|
"return"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"copy \\$${1:varname} := ${2:node}",
|
||||||
|
"modify ${3:updates}",
|
||||||
|
"return \\$${1:varname}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"transform with": {
|
||||||
|
"prefix": [
|
||||||
|
"transform",
|
||||||
|
"with",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"${1:node} transform with {",
|
||||||
|
" ${2:update}",
|
||||||
|
"}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"transform update": {
|
||||||
|
"prefix": [
|
||||||
|
"transform",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
"${1:node} update {",
|
||||||
|
"${2:update}",
|
||||||
|
"}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//snippet group
|
||||||
|
// group by $${1:varname} := ${2:expr}
|
||||||
|
//snippet order
|
||||||
|
// order by ${1:expr} ${2:descending}
|
||||||
|
//snippet stable
|
||||||
|
// stable order by ${1:expr}
|
||||||
|
//snippet count
|
||||||
|
// count $${1:varname}
|
||||||
|
//snippet ordered
|
||||||
|
// ordered { ${1:expr} }
|
||||||
|
//snippet unordered
|
||||||
|
// unordered { ${1:expr} }
|
||||||
|
//snippet treat
|
||||||
|
// treat as ${1:expr}
|
6
webapp/static/codemirror/bootstrap@5.3.7.css
vendored
Normal file
6
webapp/static/codemirror/bootstrap@5.3.7.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -6,7 +6,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Codemirror6 example using BaseX LSP</title>
|
<title>Codemirror6 example using BaseX LSP</title>
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet"
|
<link href="bootstrap@5.3.7.css" rel="stylesheet"
|
||||||
integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous">
|
integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="styles.css" />
|
<link rel="stylesheet" href="styles.css" />
|
||||||
|
|
||||||
|
@ -15,40 +15,43 @@
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar bg-body-tertiary">
|
<nav class="navbar bg-body-tertiary">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand">BaseX LSP client</a>
|
<a class="navbar-brand">BaseX LSP client</a>
|
||||||
<a href="/dba/logs" target="dba">#</a>
|
<a href="/dba/logs" target="dba">#</a>
|
||||||
|
|
||||||
<form class="d-flex">
|
<form class="d-flex">
|
||||||
|
<span id="state">🔴</span>
|
||||||
<span id="state">🔴</span>
|
<input id="iServer" type="text" value="ws://localhost:3000/ws/lsp" style="width:25em" />
|
||||||
<input id="iServer" type="text" value="ws://localhost:3000/ws/lsp" style="width:25em" />
|
<button id="connect">connect</button>
|
||||||
<button id="connect">connect</button>
|
</form>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container-fluid" >
|
<div class="container-fluid">
|
||||||
<div class="row " >
|
<div class="row ">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
something
|
something
|
||||||
</div>
|
</div>
|
||||||
<div class="col flex-grow-1" >
|
<div class="col flex-grow-1">
|
||||||
<div class="row" >
|
<div class="row">
|
||||||
<div >
|
<div>
|
||||||
|
<select id="language">
|
||||||
|
<option selected>Language</option>
|
||||||
|
<option value="plaintext">plaintext</option>
|
||||||
|
<option value="xquery">xquery</option>
|
||||||
|
<option value="xml">xml</option>
|
||||||
|
</select>
|
||||||
<label for="file">File:</label><input id="iFile" type="url" value="file:///some/file.xml" />
|
<label for="file">File:</label><input id="iFile" type="url" value="file:///some/file.xml" />
|
||||||
<button id="search">🔍</button>
|
<button id="search">🔍</button>
|
||||||
<button id="lint">⚠️</button>
|
<button id="lint">⚠️</button>
|
||||||
|
|
||||||
<label for="symbols">Symbols:</label><select id="symbols" disabled="disabled"></select>
|
<label for="symbols">Symbols:</label><select id="symbols" disabled="disabled"></select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Editor goes in here -->
|
<!-- Editor goes in here -->
|
||||||
|
|
||||||
<div id="editor"></div>
|
<div id="editor"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,7 +93,7 @@
|
||||||
lsp.simpleWebSocketTransport(server)
|
lsp.simpleWebSocketTransport(server)
|
||||||
.then(transport => {
|
.then(transport => {
|
||||||
let link = lsp.lsp(transport, file);
|
let link = lsp.lsp(transport, file);
|
||||||
const doc=view.state.doc.toString();
|
const doc = view.state.doc.toString();
|
||||||
const state = lsp.createEditorState(doc, [...lsp.baseExts, link]);
|
const state = lsp.createEditorState(doc, [...lsp.baseExts, link]);
|
||||||
view.setState(state);
|
view.setState(state);
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Reference in a new issue