[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.
|
||||
|
||||
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
|
||||
* `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
|
||||
|
@ -26,4 +26,3 @@ https://github.com/mkslanc/ace-linters/blob/c1b317e01299016ac7da6588361228637f4e
|
|||
|
||||
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,
|
||||
"documentLinkProvider": {},
|
||||
"serverInfo": {
|
||||
"name": "My Custom Language Server",
|
||||
"version": "1.0.0"
|
||||
"name": "BaseX Language Server",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,8 +6,19 @@
|
|||
module namespace docs="lsp/docs";
|
||||
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 :)
|
||||
declare type docs:property as enum("textDocument","parse");
|
||||
declare type docs:property as enum(
|
||||
"textDocument",
|
||||
"parse"
|
||||
);
|
||||
|
||||
(: get $property for $file from session $socket :)
|
||||
declare function docs:get(
|
||||
|
@ -43,3 +54,28 @@ return (
|
|||
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
|
||||
:)
|
||||
module namespace lsp-text = 'lsp-text';
|
||||
import module namespace p="xq4" at "xq4.xqm";
|
||||
|
||||
|
||||
declare variable $lsp-text:methods:=map{
|
||||
"textDocument/didOpen": lsp-text:didOpen#1,
|
||||
"textDocument/didChange": lsp-text:didChange#1,
|
||||
"textDocument/didClose" : lsp-text:method-unknown#1,
|
||||
"textDocument/didChange": lsp-text:method-unknown#1,
|
||||
|
||||
"textDocument/hover": lsp-text:hover#1,
|
||||
"textDocument/completion": lsp-text:completion#1
|
||||
};
|
||||
|
||||
(:~ hover
|
||||
|
||||
:)
|
||||
|
@ -53,18 +55,32 @@ as map(*)?
|
|||
(:~ didOpen method response :)
|
||||
declare
|
||||
function lsp-text:didOpen($json as map(*))
|
||||
as map(*)?
|
||||
as empty-sequence()
|
||||
{
|
||||
let $textDoc:=$json?params?textDocument
|
||||
|
||||
let $x:=job:eval(xs:anyURI("parse.xq"),
|
||||
{"textDocument":$textDoc,"webSocket":ws:id()},
|
||||
{ '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 :)
|
||||
declare
|
||||
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">
|
||||
<title>Codemirror6 example using BaseX LSP</title>
|
||||
<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">
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
|
||||
|
@ -15,28 +15,31 @@
|
|||
<body>
|
||||
<nav class="navbar bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand">BaseX LSP client</a>
|
||||
<a href="/dba/logs" target="dba">#</a>
|
||||
<a class="navbar-brand">BaseX LSP client</a>
|
||||
<a href="/dba/logs" target="dba">#</a>
|
||||
|
||||
<form class="d-flex">
|
||||
|
||||
<span id="state">🔴</span>
|
||||
<input id="iServer" type="text" value="ws://localhost:3000/ws/lsp" style="width:25em" />
|
||||
<button id="connect">connect</button>
|
||||
</form>
|
||||
</div>
|
||||
<form class="d-flex">
|
||||
<span id="state">🔴</span>
|
||||
<input id="iServer" type="text" value="ws://localhost:3000/ws/lsp" style="width:25em" />
|
||||
<button id="connect">connect</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid" >
|
||||
<div class="row " >
|
||||
<div class="container-fluid">
|
||||
<div class="row ">
|
||||
<div class="col-2">
|
||||
something
|
||||
</div>
|
||||
<div class="col flex-grow-1" >
|
||||
<div class="row" >
|
||||
<div >
|
||||
<div class="col flex-grow-1">
|
||||
<div class="row">
|
||||
<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" />
|
||||
<button id="search">🔍</button>
|
||||
<button id="lint">⚠️</button>
|
||||
|
@ -47,7 +50,7 @@
|
|||
|
||||
<!-- Editor goes in here -->
|
||||
|
||||
<div id="editor"></div>
|
||||
<div id="editor"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -90,7 +93,7 @@
|
|||
lsp.simpleWebSocketTransport(server)
|
||||
.then(transport => {
|
||||
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]);
|
||||
view.setState(state);
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue