[fix] highlighting
This commit is contained in:
parent
f8b00b2bac
commit
d5c5673c96
5 changed files with 276 additions and 29 deletions
|
@ -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 };
|
|
@ -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>
|
||||
|
|
|
@ -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
|
@ -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) });
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue