[fix] highlighting

This commit is contained in:
Andy Bunce 2025-08-27 09:50:01 +01:00
parent f8b00b2bac
commit d5c5673c96
5 changed files with 276 additions and 29 deletions

View file

@ -1,11 +1,15 @@
import { EditorState } from '@codemirror/state';
import { lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor, highlightActiveLine, keymap, EditorView } from '@codemirror/view';
import { EditorState, StateEffect } from '@codemirror/state';
import { lineNumbers, highlightActiveLineGutter, highlightSpecialChars,
drawSelection, rectangularSelection, crosshairCursor, highlightActiveLine,
keymap, dropCursor,EditorView } from '@codemirror/view';
import { openSearchPanel, highlightSelectionMatches, searchKeymap } from '@codemirror/search';
import { openLintPanel, lintGutter, lintKeymap, linter, setDiagnostics, } from "@codemirror/lint"
import { indentWithTab, history, defaultKeymap, historyKeymap } from '@codemirror/commands';
import { foldGutter, indentOnInput, indentUnit, bracketMatching, foldKeymap, StreamLanguage } from '@codemirror/language';
import { foldGutter, indentOnInput, indentUnit, bracketMatching, foldKeymap,
syntaxHighlighting, defaultHighlightStyle , StreamLanguage } from '@codemirror/language';
import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
@ -21,7 +25,7 @@ function simpleWebSocketTransport(uri) {
let handlers = [];
return new Promise(function (resolve, reject) {
let sock = new WebSocket(uri);
sock.onmessage = e => { for (let h of handlers) h(e.data.toString()); };
sock.onerror = e => reject(e);
sock.onopen = () => resolve({
@ -42,10 +46,10 @@ const baseExts = [
foldGutter(),
lintGutter(),
drawSelection(),
indentUnit.of(" "),
dropCursor(),
EditorState.allowMultipleSelections.of(true),
EditorView.lineWrapping,
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
bracketMatching(),
closeBrackets(),
autocompletion(),
@ -54,13 +58,12 @@ const baseExts = [
highlightActiveLine(),
highlightSelectionMatches(),
keymap.of([
indentWithTab,
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...completionKeymap,
...searchKeymap,
...lintKeymap
]),
StreamLanguage.define(xQuery)
@ -102,4 +105,4 @@ function listCommands(view) {
return commands;
};
export { EditorView,EditorState, openSearchPanel, openLintPanel, languageServerSupport, baseExts, simpleWebSocketTransport, linter, LSPPlugin, setDiagnostics, LSPClient, debouncedChangeListener, listCommands };
export { EditorView, EditorState, StateEffect, openSearchPanel, openLintPanel, languageServerSupport, baseExts, simpleWebSocketTransport, linter, LSPPlugin, setDiagnostics, LSPClient, debouncedChangeListener, listCommands };

View file

@ -118,7 +118,9 @@
<button id="connect">connect</button>
</div>
</dialog>
<popup-info id="popHelp">hhhh</popup-info>
<dialog id="popSettings" popover>
<header>Editor configuration
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
@ -131,6 +133,7 @@
<button id="connect">ok</button>
</div>
</dialog>
<!-- CodeMirror 6 -->
<script src="./lsp.bundle.js"></script>
<script src="./script.js"></script>

View file

@ -13469,6 +13469,100 @@ var lsp = (function (exports) {
}
}));
const setDropCursorPos = /*@__PURE__*/StateEffect.define({
map(pos, mapping) { return pos == null ? null : mapping.mapPos(pos); }
});
const dropCursorPos = /*@__PURE__*/StateField.define({
create() { return null; },
update(pos, tr) {
if (pos != null)
pos = tr.changes.mapPos(pos);
return tr.effects.reduce((pos, e) => e.is(setDropCursorPos) ? e.value : pos, pos);
}
});
const drawDropCursor = /*@__PURE__*/ViewPlugin.fromClass(class {
constructor(view) {
this.view = view;
this.cursor = null;
this.measureReq = { read: this.readPos.bind(this), write: this.drawCursor.bind(this) };
}
update(update) {
var _a;
let cursorPos = update.state.field(dropCursorPos);
if (cursorPos == null) {
if (this.cursor != null) {
(_a = this.cursor) === null || _a === void 0 ? void 0 : _a.remove();
this.cursor = null;
}
}
else {
if (!this.cursor) {
this.cursor = this.view.scrollDOM.appendChild(document.createElement("div"));
this.cursor.className = "cm-dropCursor";
}
if (update.startState.field(dropCursorPos) != cursorPos || update.docChanged || update.geometryChanged)
this.view.requestMeasure(this.measureReq);
}
}
readPos() {
let { view } = this;
let pos = view.state.field(dropCursorPos);
let rect = pos != null && view.coordsAtPos(pos);
if (!rect)
return null;
let outer = view.scrollDOM.getBoundingClientRect();
return {
left: rect.left - outer.left + view.scrollDOM.scrollLeft * view.scaleX,
top: rect.top - outer.top + view.scrollDOM.scrollTop * view.scaleY,
height: rect.bottom - rect.top
};
}
drawCursor(pos) {
if (this.cursor) {
let { scaleX, scaleY } = this.view;
if (pos) {
this.cursor.style.left = pos.left / scaleX + "px";
this.cursor.style.top = pos.top / scaleY + "px";
this.cursor.style.height = pos.height / scaleY + "px";
}
else {
this.cursor.style.left = "-100000px";
}
}
}
destroy() {
if (this.cursor)
this.cursor.remove();
}
setDropPos(pos) {
if (this.view.state.field(dropCursorPos) != pos)
this.view.dispatch({ effects: setDropCursorPos.of(pos) });
}
}, {
eventObservers: {
dragover(event) {
this.setDropPos(this.view.posAtCoords({ x: event.clientX, y: event.clientY }));
},
dragleave(event) {
if (event.target == this.view.contentDOM || !this.view.contentDOM.contains(event.relatedTarget))
this.setDropPos(null);
},
dragend() {
this.setDropPos(null);
},
drop() {
this.setDropPos(null);
}
}
});
/**
Draws a cursor at the current drop position when something is
dragged over the editor.
*/
function dropCursor() {
return [dropCursorPos, drawDropCursor];
}
function iterMatches(doc, re, from, to, f) {
re.lastIndex = 0;
for (let cursor = doc.iterRange(from, to), pos = from, m; !cursor.next().done; pos += cursor.value.length) {
@ -19409,7 +19503,7 @@ var lsp = (function (exports) {
for (let tag of style.tag)
map[tag.id] = style.class;
}
let { scope, all = null } = {};
let { scope, all = null } = options || {};
return {
style: (tags) => {
let cls = all;
@ -21389,6 +21483,56 @@ var lsp = (function (exports) {
cursor: "pointer"
}
});
/**
A highlight style associates CSS styles with higlighting
[tags](https://lezer.codemirror.net/docs/ref#highlight.Tag).
*/
class HighlightStyle {
constructor(
/**
The tag styles used to create this highlight style.
*/
specs, options) {
this.specs = specs;
let modSpec;
function def(spec) {
let cls = StyleModule.newName();
(modSpec || (modSpec = Object.create(null)))["." + cls] = spec;
return cls;
}
const all = typeof options.all == "string" ? options.all : options.all ? def(options.all) : undefined;
const scopeOpt = options.scope;
this.scope = scopeOpt instanceof Language ? (type) => type.prop(languageDataProp) == scopeOpt.data
: scopeOpt ? (type) => type == scopeOpt : undefined;
this.style = tagHighlighter(specs.map(style => ({
tag: style.tag,
class: style.class || def(Object.assign({}, style, { tag: null }))
})), {
all,
}).style;
this.module = modSpec ? new StyleModule(modSpec) : null;
this.themeType = options.themeType;
}
/**
Create a highlighter style that associates the given styles to
the given tags. The specs must be objects that hold a style tag
or array of tags in their `tag` property, and either a single
`class` property providing a static CSS class (for highlighter
that rely on external styling), or a
[`style-mod`](https://github.com/marijnh/style-mod#documentation)-style
set of CSS properties (which define the styling for those tags).
The CSS rules created for a highlighter will be emitted in the
order of the spec's properties. That means that for elements that
have multiple tags associated with them, styles defined further
down in the list will have a higher CSS precedence than styles
defined earlier.
*/
static define(specs, options) {
return new HighlightStyle(specs, options || {});
}
}
const highlighterFacet = /*@__PURE__*/Facet.define();
const fallbackHighlighter = /*@__PURE__*/Facet.define({
combine(values) { return values.length ? [values[0]] : null; }
@ -21398,6 +21542,30 @@ var lsp = (function (exports) {
return main.length ? main : state.facet(fallbackHighlighter);
}
/**
Wrap a highlighter in an editor extension that uses it to apply
syntax highlighting to the editor content.
When multiple (non-fallback) styles are provided, the styling
applied is the union of the classes they emit.
*/
function syntaxHighlighting(highlighter, options) {
let ext = [treeHighlighter], themeType;
if (highlighter instanceof HighlightStyle) {
if (highlighter.module)
ext.push(EditorView.styleModule.of(highlighter.module));
themeType = highlighter.themeType;
}
if (options === null || options === void 0 ? void 0 : options.fallback)
ext.push(fallbackHighlighter.of(highlighter));
else if (themeType)
ext.push(highlighterFacet.computeN([EditorView.darkTheme], state => {
return state.facet(EditorView.darkTheme) == (themeType == "dark") ? [highlighter] : [];
}));
else
ext.push(highlighterFacet.of(highlighter));
return ext;
}
/**
Returns the CSS classes (if any) that the highlighters active in
the state would assign to the given style
[tags](https://lezer.codemirror.net/docs/ref#highlight.Tag) and
@ -21417,6 +21585,86 @@ var lsp = (function (exports) {
}
return result;
}
class TreeHighlighter {
constructor(view) {
this.markCache = Object.create(null);
this.tree = syntaxTree(view.state);
this.decorations = this.buildDeco(view, getHighlighters(view.state));
this.decoratedTo = view.viewport.to;
}
update(update) {
let tree = syntaxTree(update.state), highlighters = getHighlighters(update.state);
let styleChange = highlighters != getHighlighters(update.startState);
let { viewport } = update.view, decoratedToMapped = update.changes.mapPos(this.decoratedTo, 1);
if (tree.length < viewport.to && !styleChange && tree.type == this.tree.type && decoratedToMapped >= viewport.to) {
this.decorations = this.decorations.map(update.changes);
this.decoratedTo = decoratedToMapped;
}
else if (tree != this.tree || update.viewportChanged || styleChange) {
this.tree = tree;
this.decorations = this.buildDeco(update.view, highlighters);
this.decoratedTo = viewport.to;
}
}
buildDeco(view, highlighters) {
if (!highlighters || !this.tree.length)
return Decoration.none;
let builder = new RangeSetBuilder();
for (let { from, to } of view.visibleRanges) {
highlightTree(this.tree, highlighters, (from, to, style) => {
builder.add(from, to, this.markCache[style] || (this.markCache[style] = Decoration.mark({ class: style })));
}, from, to);
}
return builder.finish();
}
}
const treeHighlighter = /*@__PURE__*/Prec.high(/*@__PURE__*/ViewPlugin.fromClass(TreeHighlighter, {
decorations: v => v.decorations
}));
/**
A default highlight style (works well with light themes).
*/
const defaultHighlightStyle = /*@__PURE__*/HighlightStyle.define([
{ tag: tags.meta,
color: "#404740" },
{ tag: tags.link,
textDecoration: "underline" },
{ tag: tags.heading,
textDecoration: "underline",
fontWeight: "bold" },
{ tag: tags.emphasis,
fontStyle: "italic" },
{ tag: tags.strong,
fontWeight: "bold" },
{ tag: tags.strikethrough,
textDecoration: "line-through" },
{ tag: tags.keyword,
color: "#708" },
{ tag: [tags.atom, tags.bool, tags.url, tags.contentSeparator, tags.labelName],
color: "#219" },
{ tag: [tags.literal, tags.inserted],
color: "#164" },
{ tag: [tags.string, tags.deleted],
color: "#a11" },
{ tag: [tags.regexp, tags.escape, /*@__PURE__*/tags.special(tags.string)],
color: "#e40" },
{ tag: /*@__PURE__*/tags.definition(tags.variableName),
color: "#00f" },
{ tag: /*@__PURE__*/tags.local(tags.variableName),
color: "#30a" },
{ tag: [tags.typeName, tags.namespace],
color: "#085" },
{ tag: tags.className,
color: "#167" },
{ tag: [/*@__PURE__*/tags.special(tags.variableName), tags.macroName],
color: "#256" },
{ tag: /*@__PURE__*/tags.definition(tags.propertyName),
color: "#00c" },
{ tag: tags.comment,
color: "#940" },
{ tag: tags.invalid,
color: "#f00" }
]);
const baseTheme$2 = /*@__PURE__*/EditorView.baseTheme({
"&.cm-focused .cm-matchingBracket": { backgroundColor: "#328c8252" },
@ -23567,13 +23815,6 @@ var lsp = (function (exports) {
{ key: "Alt-A", run: toggleBlockComment },
{ key: "Ctrl-m", mac: "Shift-Alt-m", run: toggleTabFocusMode },
].concat(standardKeymap);
/**
A binding that binds Tab to [`indentMore`](https://codemirror.net/6/docs/ref/#commands.indentMore) and
Shift-Tab to [`indentLess`](https://codemirror.net/6/docs/ref/#commands.indentLess).
Please see the [Tab example](../../examples/tab/) before using
this.
*/
const indentWithTab = { key: "Tab", run: indentMore, shift: indentLess };
/**
An instance of this is passed to completion source functions.
@ -29738,7 +29979,7 @@ ${text}</tr>
let handlers = [];
return new Promise(function (resolve, reject) {
let sock = new WebSocket(uri);
sock.onmessage = e => { for (let h of handlers) h(e.data.toString()); };
sock.onerror = e => reject(e);
sock.onopen = () => resolve({
@ -29758,10 +29999,10 @@ ${text}</tr>
foldGutter(),
lintGutter(),
drawSelection(),
indentUnit.of(" "),
dropCursor(),
EditorState.allowMultipleSelections.of(true),
EditorView.lineWrapping,
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
bracketMatching(),
closeBrackets(),
autocompletion(),
@ -29770,13 +30011,12 @@ ${text}</tr>
highlightActiveLine(),
highlightSelectionMatches(),
keymap.of([
indentWithTab,
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...completionKeymap,
...searchKeymap,
...lintKeymap
]),
StreamLanguage.define(xQuery)
@ -29821,6 +30061,7 @@ ${text}</tr>
exports.EditorView = EditorView;
exports.LSPClient = LSPClient;
exports.LSPPlugin = LSPPlugin;
exports.StateEffect = StateEffect;
exports.baseExts = baseExts;
exports.debouncedChangeListener = debouncedChangeListener;
exports.languageServerSupport = languageServerSupport;

File diff suppressed because one or more lines are too long

View file

@ -2,11 +2,11 @@ const view = new lsp.EditorView({
extensions: lsp.baseExts,
parent:document.getElementById("editor")
});
let doc = `3+1`;
let doc = "xquery version '3.1';\n(:~ comment:)\nmodule namespace pdfbox='ns';\n";
var client;
var extLint;
function $(id) { return document.getElementById(id) };
function $(id){ return document.getElementById(id) };
// Load saved content from localStorage when the page loads
window.addEventListener('load', () => {
@ -14,6 +14,7 @@ window.addEventListener('load', () => {
if (savedText) {
doc = savedText;
}
view.setState(lsp.EditorState.create({doc:doc, extensions: lsp.baseExts}));
connect();
});
@ -70,7 +71,7 @@ function connect() {
let extLsp = lsp.languageServerSupport(client, file, "xquery");
extLint = lsp.linter(null,{ autoPanel: true });
const doc = view.state.doc.toString();
const exts=[...lsp.baseExts, extLsp, extLint,
const exts=[ extLsp, extLint,
lsp.debouncedChangeListener({
delay: 750,
onChange: (content, state) => {
@ -78,9 +79,8 @@ function connect() {
client.sync();
}})
];
const state =lsp.EditorState.create({doc: doc,extensions:exts})
view.setState(state);
view.dispatch({ effects: lsp.StateEffect.appendConfig.of(exts) })
})
.catch(r => { connectStatus(false); alert("connection failed: " + server) });