[mod] additions
This commit is contained in:
parent
c59edb71a2
commit
2f54b3370e
19 changed files with 40083 additions and 16 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
data/
|
data/
|
||||||
|
node_modules/
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
An attempt to write a language protocol server using BaseX features...
|
Work in progress.
|
||||||
|
|
||||||
* `webapp/lsp` A BaseX LSP application with a WebSocket Jsonrpc API
|
An attempt to write a [Language Server Protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol) server using the BaseX websocket feature.
|
||||||
* `webapp/static/ace` A html page using an Ace editor that connects to a BaseX LSP instance
|
|
||||||
|
* `webapp/lsp` The implementation using the BaseX WebSocket feature and the Jsonrpc API
|
||||||
|
* `webapp/static/codemirror` A test html page using the Codemirror6 editor that connects to the BaseX LSP instance
|
||||||
|
* `webapp/static/ace` A test html page using the Ace editor 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/
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/srv/basex/data
|
- ./data:/srv/basex/data
|
||||||
- ./webapp/static/ace:/srv/basex/webapp/static/ace
|
- ./webapp/static/ace:/srv/basex/webapp/static/ace
|
||||||
|
- ./webapp/static/codemirror:/srv/basex/webapp/static/codemirror
|
||||||
- ./webapp/lsp:/srv/basex/webapp/lsp
|
- ./webapp/lsp:/srv/basex/webapp/lsp
|
||||||
|
|
||||||
# - ./jars:/srv/basex/lib/custom
|
# - ./jars:/srv/basex/lib/custom
|
||||||
|
|
|
||||||
1299
grammar/XQuery-40.ebnf
Normal file
1299
grammar/XQuery-40.ebnf
Normal file
File diff suppressed because it is too large
Load diff
3
grammar/test.xq
Normal file
3
grammar/test.xq
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import module namespace p="xq4" at "C:\Users\mrwhe\git\quodatum\basex-lsp\webapp\lsp\xq4.xqm";
|
||||||
|
let $t:=fetch:text("https://raw.githubusercontent.com/dnovatchev/Articles/refs/heads/main/Generators/Code/generator.xq")=>lazy:cache()
|
||||||
|
return p:parse-Module($t)
|
||||||
1244
package-lock.json
generated
Normal file
1244
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
22
package.json
Normal file
22
package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/commands": "^6.1.2",
|
||||||
|
"@codemirror/lang-javascript": "^6.1.1",
|
||||||
|
"@codemirror/lang-xml": "^6.1.0",
|
||||||
|
"@codemirror/lsp-client": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.2.3",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.0",
|
||||||
|
"@codemirror/view": "^6.6.0",
|
||||||
|
"minify": "^9.1.0",
|
||||||
|
"rollup": "4",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
|
"@rollup/plugin-typescript": "^12.1.4",
|
||||||
|
"rollup-plugin-tla": "^0.0.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup src/index.js -m true -f iife -o dist/cm6.bundle.js -p node-resolve,tla --output.name cm6",
|
||||||
|
"build-cm": "rollup src/lsp.js -m true -f iife -o webapp/static/codemirror/lsp.bundle.js -p node-resolve,tla --output.name lsp",
|
||||||
|
"min-cm": "cd webapp/static/codemirror && npx minify lsp.bundle.js > lsp.bundle.min.js ",
|
||||||
|
"min": "cd dist && npx minify cm6.bundle.js > cm6.bundle.min.js && npx minify lsp.bundle.js > lsp.bundle.min.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
rollup.config.js
Normal file
14
rollup.config.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
import tla from 'rollup-plugin-tla';
|
||||||
|
import nodeResolve from 'rollup-node-resolve';
|
||||||
|
export default defineConfig({
|
||||||
|
input: 'src/editor.js',
|
||||||
|
output: {
|
||||||
|
format: 'iife',
|
||||||
|
dir: './dist',
|
||||||
|
file:"acm6.bundle.js",
|
||||||
|
name: `__Expose`,
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
plugins: [ nodeResolve(),tla()],
|
||||||
|
});
|
||||||
79
src/lsp.js
Normal file
79
src/lsp.js
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { EditorState } from '@codemirror/state';
|
||||||
|
import { openSearchPanel, highlightSelectionMatches,searchKeymap } from '@codemirror/search';
|
||||||
|
import { indentWithTab, history, defaultKeymap, historyKeymap } from '@codemirror/commands';
|
||||||
|
import { foldGutter, indentOnInput, indentUnit, bracketMatching, foldKeymap, syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language';
|
||||||
|
import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
|
||||||
|
import { lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, keymap, EditorView } from '@codemirror/view';
|
||||||
|
|
||||||
|
// Theme
|
||||||
|
import { oneDark } from "@codemirror/theme-one-dark";
|
||||||
|
import { LSPClient, languageServerSupport } from "@codemirror/lsp-client";
|
||||||
|
// Language
|
||||||
|
import { xml } from "@codemirror/lang-xml";
|
||||||
|
|
||||||
|
// no error checking
|
||||||
|
function simpleWebSocketTransport(uri) {
|
||||||
|
let handlers = [];
|
||||||
|
let sock = new WebSocket(uri);
|
||||||
|
sock.onmessage = e => { for (let h of handlers) h(e.data.toString()); };
|
||||||
|
return new Promise(resolve => {
|
||||||
|
sock.onopen = () => resolve({
|
||||||
|
send: (message) => sock.send(message),
|
||||||
|
subscribe: (handler) => handlers.push(handler),
|
||||||
|
unsubscribe: (handler) => handlers = handlers.filter(h => h != handler)
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function lsp(transport,fileId){
|
||||||
|
let client = new LSPClient().connect(transport);
|
||||||
|
return languageServerSupport(client, fileId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseExts = [
|
||||||
|
lineNumbers(),
|
||||||
|
highlightActiveLineGutter(),
|
||||||
|
highlightSpecialChars(),
|
||||||
|
history(),
|
||||||
|
foldGutter(),
|
||||||
|
drawSelection(),
|
||||||
|
indentUnit.of(" "),
|
||||||
|
EditorState.allowMultipleSelections.of(true),
|
||||||
|
indentOnInput(),
|
||||||
|
bracketMatching(),
|
||||||
|
closeBrackets(),
|
||||||
|
autocompletion(),
|
||||||
|
rectangularSelection(),
|
||||||
|
crosshairCursor(),
|
||||||
|
highlightActiveLine(),
|
||||||
|
highlightSelectionMatches(),
|
||||||
|
keymap.of([
|
||||||
|
indentWithTab,
|
||||||
|
...closeBracketsKeymap,
|
||||||
|
...defaultKeymap,
|
||||||
|
...historyKeymap,
|
||||||
|
...foldKeymap,
|
||||||
|
...completionKeymap,
|
||||||
|
...searchKeymap,
|
||||||
|
]),
|
||||||
|
xml(),
|
||||||
|
syntaxHighlighting(defaultHighlightStyle, { fallback: true })
|
||||||
|
];
|
||||||
|
|
||||||
|
function createEditorState(initialContents, extensions,options = {}) {
|
||||||
|
|
||||||
|
if (options.oneDark)
|
||||||
|
extensions.push(oneDark);
|
||||||
|
|
||||||
|
return EditorState.create({
|
||||||
|
doc: initialContents,
|
||||||
|
extensions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEditorView(state, parent) {
|
||||||
|
return new EditorView({ state, parent });
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createEditorState, createEditorView, openSearchPanel, languageServerSupport, baseExts ,lsp, simpleWebSocketTransport};
|
||||||
|
|
@ -3,14 +3,17 @@
|
||||||
: @author andy bunce
|
: @author andy bunce
|
||||||
:)
|
:)
|
||||||
module namespace lsprpc = 'lsprpc';
|
module namespace lsprpc = 'lsprpc';
|
||||||
|
import module namespace lsp-text = 'lsp-text' at "lsp-text.xqm";
|
||||||
|
|
||||||
declare variable $lsprpc:methods:=map{
|
(: map methods to functions :)
|
||||||
|
declare variable $lsprpc:methods:=map:merge((
|
||||||
|
map{
|
||||||
"initialize" : lsprpc:method-initialize#1,
|
"initialize" : lsprpc:method-initialize#1,
|
||||||
"initialized" : lsprpc:method-unknown#1,
|
"initialized" : lsprpc:method-unknown#1,
|
||||||
"workspace/didChangeConfiguration" :lsprpc:method-unknown#1,
|
"workspace/didChangeConfiguration" :lsprpc:method-unknown#1
|
||||||
"textDocument/didOpen": lsprpc:method-unknown#1,
|
},
|
||||||
"textDocument/didClose" : lsprpc:method-unknown#1
|
$lsp-text:methods
|
||||||
};
|
));
|
||||||
|
|
||||||
(:~ return map if $msg is jsonrpc else empty :)
|
(:~ return map if $msg is jsonrpc else empty :)
|
||||||
declare
|
declare
|
||||||
|
|
@ -41,11 +44,28 @@ as empty-sequence() {
|
||||||
(:~ canned initialize response :)
|
(:~ canned initialize response :)
|
||||||
declare
|
declare
|
||||||
function lsprpc:method-initialize($json as map(*))
|
function lsprpc:method-initialize($json as map(*))
|
||||||
as map(*)?
|
as map(*)
|
||||||
{
|
{
|
||||||
``[{"jsonrpc":"2.0","id":0,
|
{
|
||||||
"result":{"capabilities":{"textDocumentSync":2,"completionProvider":{"resolveProvider":false,"triggerCharacters":["\"",":"]},"hoverProvider":true,"documentSymbolProvider":true,"documentRangeFormattingProvider":false,"colorProvider":{},"foldingRangeProvider":true,"selectionRangeProvider":true,"documentLinkProvider":{}}}}]``
|
"jsonrpc": "2.0",
|
||||||
=>parse-json()
|
"id": $json?id,
|
||||||
|
"result": {
|
||||||
|
"capabilities": {
|
||||||
|
"textDocumentSync": 2,
|
||||||
|
"completionProvider": {
|
||||||
|
"resolveProvider": false(),
|
||||||
|
"triggerCharacters": [ """", ":" ]
|
||||||
|
},
|
||||||
|
"hoverProvider": true(),
|
||||||
|
"documentSymbolProvider": true(),
|
||||||
|
"documentRangeFormattingProvider": false(),
|
||||||
|
"colorProvider": {},
|
||||||
|
"foldingRangeProvider": true(),
|
||||||
|
"selectionRangeProvider": true(),
|
||||||
|
"documentLinkProvider": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(:~ unknown method response :)
|
(:~ unknown method response :)
|
||||||
|
|
|
||||||
71
webapp/lsp/lsp-text.xqm
Normal file
71
webapp/lsp/lsp-text.xqm
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
(:~ handle text messages
|
||||||
|
: @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/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
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"method": "textDocument/hover",
|
||||||
|
"params": {
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///session1.json"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"line": 2,
|
||||||
|
"character": 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:)
|
||||||
|
declare
|
||||||
|
function lsp-text:hover($json as map(*))
|
||||||
|
as map(*)?
|
||||||
|
{
|
||||||
|
let $doc:=$json?params?textDocument?uri
|
||||||
|
return map{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": $json?id,
|
||||||
|
"value":"uri: " || $doc
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
declare
|
||||||
|
function lsp-text:completion($json as map(*))
|
||||||
|
as map(*)?
|
||||||
|
{
|
||||||
|
let $doc:=$json?params?textDocument?uri
|
||||||
|
return map{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": $json?id,
|
||||||
|
"result":()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(:~ didOpen method response :)
|
||||||
|
declare
|
||||||
|
function lsp-text:didOpen($json as map(*))
|
||||||
|
as map(*)?
|
||||||
|
{
|
||||||
|
let $textDoc:=$json?params?textDocument
|
||||||
|
let $text:=$textDoc?text=>trace("TXT")
|
||||||
|
return ()
|
||||||
|
};
|
||||||
|
|
||||||
|
(:~ unknown method response :)
|
||||||
|
declare
|
||||||
|
function lsp-text:method-unknown($json as map(*))
|
||||||
|
as map(*)?
|
||||||
|
{
|
||||||
|
let $_:=trace($json?method,"unknown")
|
||||||
|
return ()
|
||||||
|
};
|
||||||
|
|
@ -19,9 +19,13 @@ function lsp-ws:error($error) {
|
||||||
declare
|
declare
|
||||||
%ws:connect('/lsp')
|
%ws:connect('/lsp')
|
||||||
function lsp-ws:connect() as empty-sequence() {
|
function lsp-ws:connect() as empty-sequence() {
|
||||||
|
let $id:=ws:id()=>trace("CONNECT: ")
|
||||||
ws:set(ws:id()=>trace("CONNECT: "), $chat-util:id, "session:get($chat-util:id)")
|
return (
|
||||||
(: ,chat-util:users() :)
|
ws:set($id, "id", $id),
|
||||||
|
store:clear(),
|
||||||
|
store:put("id",$id),
|
||||||
|
store:write("lsp-store")
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
(:~
|
(:~
|
||||||
|
|
|
||||||
6570
webapp/lsp/xq4.xqm
Normal file
6570
webapp/lsp/xq4.xqm
Normal file
File diff suppressed because it is too large
Load diff
BIN
webapp/static/codemirror/favicon.png
Normal file
BIN
webapp/static/codemirror/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
80
webapp/static/codemirror/index.html
Normal file
80
webapp/static/codemirror/index.html
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<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 rel="stylesheet" href="styles.css" />
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>BaseX LSP client <button id="search">🔍</button>
|
||||||
|
<label for="symbols">Symbols:</label><select id="symbols" disabled="disabled"></select>
|
||||||
|
<div style="float:right">
|
||||||
|
<input id="iServer" type="text" value="ws://localhost:3000/ws/lsp" /><button id="connect">connect</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="item">stuff</div>
|
||||||
|
<!-- Editor goes in here -->
|
||||||
|
<div id="editor" class="item"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CodeMirror 6 -->
|
||||||
|
<script src="./lsp.bundle.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const server = "ws://localhost:3000/ws/lsp";
|
||||||
|
let doc = `/**
|
||||||
|
*
|
||||||
|
* @param {string[]} items
|
||||||
|
* @param nada
|
||||||
|
*/
|
||||||
|
function foo(items, nada) {
|
||||||
|
for (var i=0; i<items.length; i++) {
|
||||||
|
alert(items[i] + 'juhu');
|
||||||
|
} // Real Tab.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
}`;
|
||||||
|
// Load saved content from localStorage when the page loads
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const savedText = localStorage.getItem('code');
|
||||||
|
if (savedText) {
|
||||||
|
doc = savedText;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const view = lsp.createEditorView(undefined, document.getElementById("editor"));
|
||||||
|
view.setState(lsp.createEditorState(doc, lsp.baseExts));
|
||||||
|
|
||||||
|
// Save content to localStorage when the page is about to unload
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
const doc = view.state.doc.toString();
|
||||||
|
localStorage.setItem('code', doc);
|
||||||
|
});
|
||||||
|
document.getElementById("connect").onclick = e => {
|
||||||
|
const v = document.getElementById("iServer").value;
|
||||||
|
alert(v)
|
||||||
|
};
|
||||||
|
document.getElementById("search").onclick = e => {
|
||||||
|
lsp.openSearchPanel(view);
|
||||||
|
};
|
||||||
|
|
||||||
|
lsp.simpleWebSocketTransport(server)
|
||||||
|
.then(transport => {
|
||||||
|
let link = lsp.lsp(transport, "file:///some/file.xml");
|
||||||
|
const state = lsp.createEditorState(doc, [...lsp.baseExts, link]);
|
||||||
|
view.setState(state);
|
||||||
|
})
|
||||||
|
.catch(r => alert("fail"));
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
30627
webapp/static/codemirror/lsp.bundle.js
Normal file
30627
webapp/static/codemirror/lsp.bundle.js
Normal file
File diff suppressed because it is too large
Load diff
1
webapp/static/codemirror/lsp.bundle.js.map
Normal file
1
webapp/static/codemirror/lsp.bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
1
webapp/static/codemirror/lsp.bundle.min.js
vendored
Normal file
1
webapp/static/codemirror/lsp.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
27
webapp/static/codemirror/styles.css
Normal file
27
webapp/static/codemirror/styles.css
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* Set editor dimensions */
|
||||||
|
#editor {
|
||||||
|
height: 400px;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stretch editor to fit inside its containing div */
|
||||||
|
.cm-editor {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
/* header */
|
||||||
|
header {
|
||||||
|
background-color: burlywood;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item + .item {
|
||||||
|
margin-left: 2%;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue