[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
|
||||
* `webapp/static/ace` A html page using an Ace editor that connects to a BaseX LSP instance
|
||||
An attempt to write a [Language Server Protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol) server using the BaseX websocket feature.
|
||||
|
||||
* `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
|
||||
* https://github.com/mkslanc/ace-linters https://mkslanc.github.io/ace-linters/
|
||||
|
|
|
@ -10,6 +10,7 @@ services:
|
|||
volumes:
|
||||
- ./data:/srv/basex/data
|
||||
- ./webapp/static/ace:/srv/basex/webapp/static/ace
|
||||
- ./webapp/static/codemirror:/srv/basex/webapp/static/codemirror
|
||||
- ./webapp/lsp:/srv/basex/webapp/lsp
|
||||
|
||||
# - ./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
|
||||
:)
|
||||
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,
|
||||
"initialized" : lsprpc:method-unknown#1,
|
||||
"workspace/didChangeConfiguration" :lsprpc:method-unknown#1,
|
||||
"textDocument/didOpen": lsprpc:method-unknown#1,
|
||||
"textDocument/didClose" : lsprpc:method-unknown#1
|
||||
};
|
||||
"workspace/didChangeConfiguration" :lsprpc:method-unknown#1
|
||||
},
|
||||
$lsp-text:methods
|
||||
));
|
||||
|
||||
(:~ return map if $msg is jsonrpc else empty :)
|
||||
declare
|
||||
|
@ -41,11 +44,28 @@ as empty-sequence() {
|
|||
(:~ canned initialize response :)
|
||||
declare
|
||||
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":{}}}}]``
|
||||
=>parse-json()
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"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 :)
|
||||
|
|
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
|
||||
%ws:connect('/lsp')
|
||||
function lsp-ws:connect() as empty-sequence() {
|
||||
|
||||
ws:set(ws:id()=>trace("CONNECT: "), $chat-util:id, "session:get($chat-util:id)")
|
||||
(: ,chat-util:users() :)
|
||||
let $id:=ws:id()=>trace("CONNECT: ")
|
||||
return (
|
||||
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