[add] initial handler stuff

This commit is contained in:
Andy Bunce 2025-09-07 22:04:53 +01:00
parent 7deb653208
commit 9533519b8a
10 changed files with 284 additions and 187 deletions

56
webapp/lsp/handlers.xqm Normal file
View file

@ -0,0 +1,56 @@
(: tools to analyse xml parse tree
@author Andy Bunce
:)
module namespace hnd="lsp/handlers";
import module namespace pos="lsp/position" at "position.xqm";
declare record hnd:hand(
result as item()*,
skipchildren as xs:boolean
);
declare function hnd:default-handler($el as element(*),$state)
as function(*){
fn(){hnd:hand($state,false())}
};
declare function hnd:get-handler($el as element(*),$state)
as function(*)
{ function-lookup(xs:QName(name($el)),2)
otherwise hnd:default-handler($el,$state)
};
declare function hnd:diags($parse as element(),$diags:=())
{
let $h:= hnd:get-handler($parse,$diags)($parse,$diags)
return if($h?skipchildren)
then $h?result
else fold-left($parse/*,$diags,
fn($r,$this){
hnd:diags($this,$r)
})
};
declare record hnd:symbol (
name as xs:string,
type as xs:string,
range-name? as pos:Range,
range-full? as pos:Range,
children? as array(hnd:symbol)
);
declare function hnd:symbols($parse as element(),$syms as hnd:symbol* :=() )
{
'todo'
};
declare function hnd:anotated-declaration($parse as element(),$syms)
as hnd:hand
{
let $sym:=hnd:symbol(
$parse/token,
"AA"
)
return hnd:hand(($syms,$sym),true())
};

View file

@ -8075,6 +8075,29 @@ var lsp = (function (exports) {
return pos;
}
}
function skipAtomsForSelection(atoms, sel) {
let ranges = null;
for (let i = 0; i < sel.ranges.length; i++) {
let range = sel.ranges[i], updated = null;
if (range.empty) {
let pos = skipAtomicRanges(atoms, range.from, 0);
if (pos != range.from)
updated = EditorSelection.cursor(pos, -1);
}
else {
let from = skipAtomicRanges(atoms, range.from, -1);
let to = skipAtomicRanges(atoms, range.to, 1);
if (from != range.from || to != range.to)
updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
}
if (updated) {
if (!ranges)
ranges = sel.ranges.slice();
ranges[i] = updated;
}
}
return ranges ? EditorSelection.create(ranges, sel.mainIndex) : sel;
}
function skipAtoms(view, oldPos, pos) {
let newPos = skipAtomicRanges(view.state.facet(atomicRanges).map(f => f(view)), pos.from, oldPos.head > pos.from ? -1 : 1);
return newPos == pos.from ? pos : EditorSelection.cursor(newPos, newPos < pos.from ? 1 : -1);
@ -8310,6 +8333,8 @@ var lsp = (function (exports) {
if (view.inputState.lastSelectionOrigin == "select")
scrollIntoView = true;
userEvent = view.inputState.lastSelectionOrigin;
if (userEvent == "select.pointer")
newSel = skipAtomsForSelection(view.state.facet(atomicRanges).map(f => f(view)), newSel);
}
view.dispatch({ selection: newSel, scrollIntoView, userEvent });
return true;
@ -8787,31 +8812,8 @@ var lsp = (function (exports) {
if (this.dragging === false)
this.select(this.lastEvent);
}
skipAtoms(sel) {
let ranges = null;
for (let i = 0; i < sel.ranges.length; i++) {
let range = sel.ranges[i], updated = null;
if (range.empty) {
let pos = skipAtomicRanges(this.atoms, range.from, 0);
if (pos != range.from)
updated = EditorSelection.cursor(pos, -1);
}
else {
let from = skipAtomicRanges(this.atoms, range.from, -1);
let to = skipAtomicRanges(this.atoms, range.to, 1);
if (from != range.from || to != range.to)
updated = EditorSelection.range(range.from == range.anchor ? from : to, range.from == range.head ? from : to);
}
if (updated) {
if (!ranges)
ranges = sel.ranges.slice();
ranges[i] = updated;
}
}
return ranges ? EditorSelection.create(ranges, sel.mainIndex) : sel;
}
select(event) {
let { view } = this, selection = this.skipAtoms(this.style.get(event, this.extend, this.multiple));
let { view } = this, selection = skipAtomsForSelection(this.atoms, this.style.get(event, this.extend, this.multiple));
if (this.mustSelect || !selection.eq(view.state.selection, this.dragging === false))
this.view.dispatch({
selection,
@ -8964,6 +8966,9 @@ var lsp = (function (exports) {
return mouseSel.dragging === false;
}
}
else {
view.inputState.setSelectionOrigin("select.pointer");
}
return false;
};
function rangeForClick(view, pos, bias, type) {
@ -12288,7 +12293,7 @@ var lsp = (function (exports) {
}
/**
Find the line block (see
[`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt) at the given
[`lineBlockAt`](https://codemirror.net/6/docs/ref/#view.EditorView.lineBlockAt)) at the given
height, again interpreted relative to the [top of the
document](https://codemirror.net/6/docs/ref/#view.EditorView.documentTop).
*/
@ -13075,7 +13080,7 @@ var lsp = (function (exports) {
// Ctrl-Alt may be used for AltGr on Windows
!(browser.windows && event.ctrlKey && event.altKey) &&
// Alt-combinations on macOS tend to be typed characters
!(browser.mac && event.altKey && !event.ctrlKey) &&
!(browser.mac && event.altKey && !(event.ctrlKey || event.metaKey)) &&
(baseName = base[event.keyCode]) && baseName != name) {
if (runFor(scopeObj[prefix + modifiers(baseName, event, true)])) {
handled = true;
@ -23971,16 +23976,20 @@ var lsp = (function (exports) {
*/
function insertCompletionText(state, text, from, to) {
let { main } = state.selection, fromOff = from - main.from, toOff = to - main.from;
return Object.assign(Object.assign({}, state.changeByRange(range => {
if (range != main && from != to &&
state.sliceDoc(range.from + fromOff, range.from + toOff) != state.sliceDoc(from, to))
return { range };
let lines = state.toText(text);
return {
changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
range: EditorSelection.cursor(range.from + fromOff + lines.length)
};
})), { scrollIntoView: true, userEvent: "input.complete" });
return {
...state.changeByRange(range => {
if (range != main && from != to &&
state.sliceDoc(range.from + fromOff, range.from + toOff) != state.sliceDoc(from, to))
return { range };
let lines = state.toText(text);
return {
changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
range: EditorSelection.cursor(range.from + fromOff + lines.length)
};
}),
scrollIntoView: true,
userEvent: "input.complete"
};
}
const SourceCache = /*@__PURE__*/new WeakMap();
function asSource(source) {
@ -24625,7 +24634,7 @@ var lsp = (function (exports) {
}, prev ? prev.timestamp : Date.now(), selected, false);
}
map(changes) {
return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected, this.disabled);
return new CompletionDialog(this.options, this.attrs, { ...this.tooltip, pos: changes.mapPos(this.tooltip.pos) }, this.timestamp, this.selected, this.disabled);
}
setDisabled() {
return new CompletionDialog(this.options, this.attrs, this.tooltip, this.timestamp, this.selected, true);
@ -24810,7 +24819,10 @@ var lsp = (function (exports) {
if (!(result instanceof ActiveResult))
return false;
if (typeof apply == "string")
view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
view.dispatch({
...insertCompletionText(view.state, apply, result.from, result.to),
annotations: pickedCompletion.of(option.completion)
});
else
apply(view, option.completion, result.from, result.to);
return true;
@ -25227,7 +25239,7 @@ var lsp = (function (exports) {
let fields = [];
let lines = [], positions = [], m;
for (let line of template.split(/\r\n?|\n/)) {
while (m = /[#$]\{(?:(\d+)(?::([^}]*))?|((?:\\[{}]|[^}])*))\}/.exec(line)) {
while (m = /[#$]\{(?:(\d+)(?::([^{}]*))?|((?:\\[{}]|[^{}])*))\}/.exec(line)) {
let seq = m[1] ? +m[1] : null, rawName = m[2] || m[3] || "", found = -1;
let name = rawName.replace(/\\[{}]/g, m => m[1]);
for (let i = 0; i < fields.length; i++) {
@ -25244,6 +25256,12 @@ var lsp = (function (exports) {
if (pos.field >= found)
pos.field++;
}
for (let pos of positions)
if (pos.line == lines.length && pos.from > m.index) {
let snip = m[2] ? 3 + (m[1] || "").length : 2;
pos.from -= snip;
pos.to -= snip;
}
positions.push(new FieldPos(found, lines.length, m.index, m.index + name.length));
line = line.slice(0, m.index) + rawName + line.slice(m.index + m[0].length);
}
@ -25273,7 +25291,7 @@ var lsp = (function (exports) {
constructor(ranges, active) {
this.ranges = ranges;
this.active = active;
this.deco = Decoration.set(ranges.map(r => (r.from == r.to ? fieldMarker : fieldRange).range(r.from, r.to)));
this.deco = Decoration.set(ranges.map(r => (r.from == r.to ? fieldMarker : fieldRange).range(r.from, r.to)), true);
}
map(changes) {
let ranges = [];
@ -25690,17 +25708,18 @@ var lsp = (function (exports) {
/**
Basic keybindings for autocompletion.
- Ctrl-Space (and Alt-\` on macOS): [`startCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.startCompletion)
- Ctrl-Space (and Alt-\` or Alt-i on macOS): [`startCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.startCompletion)
- Escape: [`closeCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.closeCompletion)
- ArrowDown: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(true)`
- ArrowUp: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(false)`
- PageDown: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(true, "page")`
- PageDown: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(true, "page")`
- PageUp: [`moveCompletionSelection`](https://codemirror.net/6/docs/ref/#autocomplete.moveCompletionSelection)`(false, "page")`
- Enter: [`acceptCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.acceptCompletion)
*/
const completionKeymap = [
{ key: "Ctrl-Space", run: startCompletion },
{ mac: "Alt-`", run: startCompletion },
{ mac: "Alt-i", run: startCompletion },
{ key: "Escape", run: closeCompletion },
{ key: "ArrowDown", run: /*@__PURE__*/moveCompletionSelection(true) },
{ key: "ArrowUp", run: /*@__PURE__*/moveCompletionSelection(false) },
@ -29545,6 +29564,27 @@ ${text}</tr>
function toSeverity(sev) {
return sev == 1 ? "error" : sev == 2 ? "warning" : sev == 3 ? "info" : "hint";
}
const autoSync = /*@__PURE__*/ViewPlugin.fromClass(class {
constructor() {
this.pending = -1;
}
update(update) {
if (update.docChanged) {
if (this.pending > -1)
clearTimeout(this.pending);
this.pending = setTimeout(() => {
this.pending = -1;
let plugin = LSPPlugin.get(update.view);
if (plugin)
plugin.client.sync();
}, 500);
}
}
destroy() {
if (this.pending > -1)
clearTimeout(this.pending);
}
});
function serverDiagnostics() {
return {
clientCapabilities: { textDocument: { publishDiagnostics: { versionSupport: true } } },
@ -29567,7 +29607,8 @@ ${text}</tr>
})));
return true;
}
}
},
editorExtension: autoSync
};
}

File diff suppressed because one or more lines are too long

View file

@ -83,21 +83,18 @@ function connect() {
$("popConnect").hidePopover();
connectStatus(true);
let extLsp = client.plugin(file, "xquery");
let up = lsp.debouncedChangeListener({
delay: 750,
onChange: (content, state) => {
console.log('Debounced change detected:');
client.sync();
}
})
view.dispatch({
effects: lsp.StateEffect.appendConfig.of(
[lsp.linter(null, { autoPanel: true }), ...extLsp, up,
lsp.keymap.of([...lsp.formatKeymap])])
[lsp.linter(null, { autoPanel: true }), ...extLsp,
lsp.keymap.of([...lsp.formatKeymap])])
})
})
.catch(r => { connectStatus(false); alert("connection failed: " + server) });
.catch(e => {
console.log(e);
connectStatus(false);
alert("connection failed: " + server)
});
};
@ -114,7 +111,6 @@ function connectStatus(bool) {
function incoming(msg) {
const rpc = JSON.parse(msg);
log(rpc);
};