@valtzu/codemirror-lang-el 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -29
- package/dist/index.cjs +77 -22
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +77 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,37 +8,48 @@
|
|
|
8
8
|
```html
|
|
9
9
|
<div id="editor"></div>
|
|
10
10
|
<script type="importmap">
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
{
|
|
12
|
+
"imports": {
|
|
13
|
+
"codemirror": "https://esm.sh/*codemirror@6.0.1",
|
|
14
|
+
"@codemirror/state": "https://esm.sh/*@codemirror/state@6.4.1",
|
|
15
|
+
"@codemirror/search": "https://esm.sh/*@codemirror/search@6.5.6",
|
|
16
|
+
"@codemirror/autocomplete": "https://esm.sh/*@codemirror/autocomplete@6.9.0",
|
|
17
|
+
"@codemirror/view": "https://esm.sh/*@codemirror/view@6.17.1",
|
|
18
|
+
"@codemirror/commands": "https://esm.sh/*@codemirror/commands@6.2.5",
|
|
19
|
+
"@codemirror/language": "https://esm.sh/*@codemirror/language@6.9.0",
|
|
20
|
+
"@codemirror/lint": "https://esm.sh/*@codemirror/lint@6.4.1",
|
|
21
|
+
"@lezer/lr": "https://esm.sh/*@lezer/lr@1.3.9",
|
|
22
|
+
"@lezer/highlight": "https://esm.sh/*@lezer/highlight@1.1.6",
|
|
23
|
+
"@lezer/common": "https://esm.sh/*@lezer/common@1.2.1",
|
|
24
|
+
"style-mod": "https://esm.sh/*style-mod@4.1.2",
|
|
25
|
+
"w3c-keyname": "https://esm.sh/*w3c-keyname@2.2.8",
|
|
26
|
+
"crelt": "https://esm.sh/*crelt@1.0.6",
|
|
27
|
+
"@valtzu/codemirror-lang-el": "https://esm.sh/*@valtzu/codemirror-lang-el@0.2.3"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
19
30
|
</script>
|
|
20
31
|
<script type="module">
|
|
21
|
-
import {EditorView, basicSetup} from "codemirror";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
32
|
+
import { EditorView, basicSetup } from "codemirror";
|
|
33
|
+
import { acceptCompletion } from "@codemirror/autocomplete";
|
|
34
|
+
import { keymap } from "@codemirror/view";
|
|
35
|
+
import { expressionlanguage } from "@valtzu/codemirror-lang-el";
|
|
25
36
|
|
|
26
|
-
let editor = new EditorView({
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
37
|
+
let editor = new EditorView({
|
|
38
|
+
extensions: [
|
|
39
|
+
basicSetup,
|
|
40
|
+
keymap.of([{key: "Tab", run: acceptCompletion}]),
|
|
41
|
+
expressionlanguage({
|
|
42
|
+
identifiers: [
|
|
43
|
+
{ name: 'foo', info: 'Foo is a variable' },
|
|
44
|
+
{ name: 'bar' }
|
|
45
|
+
],
|
|
46
|
+
functions: [
|
|
47
|
+
{ name: 'smh' },
|
|
48
|
+
{ name: 'smash_my_head', args: ['object'], info: 'This is a function' },
|
|
49
|
+
],
|
|
50
|
+
})
|
|
51
|
+
],
|
|
52
|
+
parent: document.getElementById('editor'),
|
|
53
|
+
});
|
|
43
54
|
</script>
|
|
44
55
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -6,11 +6,12 @@ var lr = require('@lezer/lr');
|
|
|
6
6
|
var language = require('@codemirror/language');
|
|
7
7
|
var highlight = require('@lezer/highlight');
|
|
8
8
|
var lint = require('@codemirror/lint');
|
|
9
|
+
var view = require('@codemirror/view');
|
|
9
10
|
|
|
10
11
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
|
11
12
|
const parser = lr.LRParser.deserialize({
|
|
12
13
|
version: 14,
|
|
13
|
-
states: "$nQVQPOOO!QQQO'#ChO!VQPO'#CjO!^QPO'#CmOOQO'#Cs'#CsOOQO'#Cn'#CnQVQPOOO!eQQO,59SOOQO,59U,59UO!
|
|
14
|
+
states: "$nQVQPOOO!QQQO'#ChO!VQPO'#CjO!^QPO'#CmOOQO'#Cs'#CsOOQO'#Cn'#CnQVQPOOO!eQQO,59SOOQO,59U,59UO!jQQO,59UOOQO,59X,59XO!rQPO,59XOOQO-E6l-E6lOVQPO1G.nOVQPO'#CoO!yQQO1G.pOOQO1G.p1G.pOOQO1G.s1G.sO#RQQO7+$YOOQO,59Z,59ZOOQO-E6m-E6mOOQO7+$[7+$[OOQO<<Gt<<GtO#ZQQO<<GtO#`QQOAN=`OVQPOG22zO#eQPOLD(fOOQO!$'LQ!$'LQ",
|
|
14
15
|
stateData: "#r~OfOS~OQSORSOSSOTSOUSOVSOWSOXSOYSOZSO`ROiPOmQO~O]VO~OlWO~PVO_YO~PVOj]O~Ok^Ol`O~O_aO~PVOk^OleO~OhfOkgO~O]hO~OjiO~OhkO~OQUTXWRW~",
|
|
15
16
|
goto: "!ghPPPPPPPPPPPPiPiPPis}PPP!TaSOQRUZ]^iQUOQZRT[UZQ_XRd_WTORUZQXQQb]Qc^Rji",
|
|
16
17
|
nodeNames: "⚠ Program Number Identifier String Boolean Null Operator OperatorKeyword Punctuation NullSafe NullCoalescing Object ObjectKey Array ClosingBracket OpeningBracket Application",
|
|
@@ -21,33 +22,71 @@ const parser = lr.LRParser.deserialize({
|
|
|
21
22
|
],
|
|
22
23
|
skippedNodes: [0],
|
|
23
24
|
repeatNodeCount: 2,
|
|
24
|
-
tokenData: "
|
|
25
|
+
tokenData: "B{~R!QXY$XYZ$X]^$Xpq$Xqr$jrs%Puv$zvw&{wx'Txy(xyz(}z{)S{|$z|})[}!O$z!O!P)c!P!Q$z!Q![*t![!],T!^!_$r!_!`,[!`!a$r!a!b,b!c!},w!}#O-[#P#Q-a#Q#R$z#R#S,w#T#U-f#U#V,w#V#W/P#W#X,w#X#Y2s#Y#Z5g#Z#],w#]#^8R#^#a,w#a#b8l#b#c;[#c#d>u#d#g,w#g#h?`#h#iAe#i#o,w#o#pBi#p#qBn#q#rBv#r#s$z~$^Sf~XY$XYZ$X]^$Xpq$X~$oPV~!_!`$r~$wPV~!_!`$z~%POV~R%SXOr%Prs%os#O%P#O#P%v#P;'S%P;'S;=`&u<%l~%P~O%P~~%oR%vO]QSPR%yRO;'S%P;'S;=`&S;=`O%PR&VYOr%Prs%os#O%P#O#P%v#P;'S%P;'S;=`&u;=`<%l%P<%l~%P~O%P~~%oR&xP;=`<%l%P~'QPV~vw$zR'WXOw'Twx%ox#O'T#O#P's#P;'S'T;'S;=`(r<%l~'T~O'T~~%oR'vRO;'S'T;'S;=`(P;=`O'TR(SYOw'Twx%ox#O'T#O#P's#P;'S'T;'S;=`(r;=`<%l'T<%l~'T~O'T~~%oR(uP;=`<%l'T~(}O`~~)SO_~~)XPV~z{$zR)cOkQXP~)hQXP!O!P$z!Q![)n~)sSQ~!Q![)n!g!h*P#R#S*n#X#Y*P~*SR{|*]}!O*]!Q![*c~*`P!Q![*c~*hQQ~!Q![*c#R#S*]~*qP!Q![)n~*yTQ~!O!P+Y!Q![*t!g!h*P#R#S+}#X#Y*P~+]SO!O+i!P;'S+i;'S;=`+w<%lO+i~+nRQ~!Q![)n!g!h*P#X#Y*P~+zP;=`<%l+i~,QP!Q![*tR,[OjQXP~,_P!_!`$r~,gQXP!O!P,m!a!b,r~,rOY~~,wOZ~R-OS]QRP!Q![,w!c!},w#R#S,w#T#o,w~-aOm~~-fOl~R-mU]QRP!Q![,w!c!},w#R#S,w#T#b,w#b#c.P#c#o,wR.WU]QRP!Q![,w!c!},w#R#S,w#T#W,w#W#X.j#X#o,wR.sS]QWPRP!Q![,w!c!},w#R#S,w#T#o,wR/WU]QRP!Q![,w!c!},w#R#S,w#T#c,w#c#d/j#d#o,wR/qU]QRP!Q![,w!c!},w#R#S,w#T#b,w#b#c0T#c#o,wR0[U]QRP!Q![,w!c!},w#R#S,w#T#h,w#h#i0n#i#o,wR0uT]QRP!Q![,w!c!},w#R#S,w#T#U1U#U#o,wR1]U]QRP!Q![,w!c!},w#R#S,w#T#],w#]#^1o#^#o,wR1vU]QRP!Q![,w!c!},w#R#S,w#T#b,w#b#c2Y#c#o,wR2aU]QRP!Q![,w!c!},w#R#S,w#T#g,w#g#h.j#h#o,wR2zU]QRP!Q![,w!c!},w#R#S,w#T#b,w#b#c3^#c#o,wR3eU]QRP!Q![,w!c!},w#R#S,w#T#W,w#W#X3w#X#o,wR4OU]QRP!Q![,w!c!},w#R#S,w#T#g,w#g#h4b#h#o,wR4iT]QRPpq4x!Q![,w!c!},w#R#S,w#T#o,wP4{P#k#l5OP5RP#]#^5UP5XP#h#i5[P5_P#[#]5bP5gOWPR5nT]QRP!Q![,w!c!},w#R#S,w#T#U5}#U#o,wR6UU]QRP!Q![,w!c!},w#R#S,w#T#`,w#`#a6h#a#o,wR6oU]QRP!Q![,w!c!},w#R#S,w#T#g,w#g#h7R#h#o,wR7YU]QRP!Q![,w!c!},w#R#S,w#T#X,w#X#Y7l#Y#o,wR7uS]QTPRP!Q![,w!c!},w#R#S,w#T#o,wR8YU]QRP!Q![,w!c!},w#R#S,w#T#b,w#b#c.j#c#o,wR8sT]QRP!Q![,w!c!},w#R#S,w#T#U9S#U#o,wR9ZU]QRP!Q![,w!c!},w#R#S,w#T#h,w#h#i9m#i#o,wR9tU]QRP!Q![,w!c!},w#R#S,w#T#V,w#V#W:W#W#o,wR:_U]QRP!Q![,w!c!},w#R#S,w#T#[,w#[#]:q#]#o,wR:xU]QRP!Q![,w!c!},w#R#S,w#T#X,w#X#Y2Y#Y#o,wR;cW]QRP!Q![,w!c!},w#R#S,w#T#c,w#c#d;{#d#i,w#i#j=[#j#o,wR<SU]QRP!Q![,w!c!},w#R#S,w#T#h,w#h#i<f#i#o,wR<oT]QWPRPpq=O!Q![,w!c!},w#R#S,w#T#o,wP=RP#]#^=UP=XP#b#c5bR=cU]QRP!Q![,w!c!},w#R#S,w#T#`,w#`#a=u#a#o,wR=|U]QRP!Q![,w!c!},w#R#S,w#T#`,w#`#a>`#a#o,wR>iS]QUPRP!Q![,w!c!},w#R#S,w#T#o,wR>|U]QRP!Q![,w!c!},w#R#S,w#T#f,w#f#g.j#g#o,wR?gU]QRP!Q![,w!c!},w#R#S,w#T#h,w#h#i?y#i#o,wR@QT]QRP!Q![,w!c!},w#R#S,w#T#U@a#U#o,wR@hU]QRP!Q![,w!c!},w#R#S,w#T#f,w#f#g@z#g#o,wRARU]QRP!Q![,w!c!},w#R#S,w#T#h,w#h#i3w#i#o,wRAlU]QRP!Q![,w!c!},w#R#S,w#T#f,w#f#gBO#g#o,wRBVU]QRP!Q![,w!c!},w#R#S,w#T#i,w#i#j7R#j#o,w~BnOi~~BsPV~#p#q$z~B{Oh~",
|
|
25
26
|
tokenizers: [0, 1],
|
|
26
27
|
topRules: {"Program":[0,1]},
|
|
27
28
|
tokenPrec: 118
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
const identifier = /^[a-zA-Z_]+[a-zA-Z_0-9]*$/;
|
|
32
|
+
const isFunction = (identifier, config) => { var _a; return (_a = config.functions) === null || _a === void 0 ? void 0 : _a.find(fn => fn.name === identifier); };
|
|
33
|
+
const isVariable = (identifier, config) => { var _a; return (_a = config.identifiers) === null || _a === void 0 ? void 0 : _a.find(variable => variable.name === identifier); };
|
|
31
34
|
const expressionLanguageLinter = (config) => lint.linter(view => {
|
|
32
35
|
let diagnostics = [];
|
|
36
|
+
let previousNode = null;
|
|
33
37
|
language.syntaxTree(view.state).cursor().iterate(node => {
|
|
34
|
-
var _a
|
|
38
|
+
var _a;
|
|
35
39
|
if (node.name == "Identifier") {
|
|
40
|
+
if ((previousNode === null || previousNode === void 0 ? void 0 : previousNode.name) == "Identifier" && ((_a = node.node.parent) === null || _a === void 0 ? void 0 : _a.name) != 'Array') {
|
|
41
|
+
diagnostics.push({
|
|
42
|
+
from: node.from,
|
|
43
|
+
to: node.to,
|
|
44
|
+
severity: 'error',
|
|
45
|
+
message: `Unexpected identifier after another identifier`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
36
48
|
const identifier = view.state.sliceDoc(node.from, node.to);
|
|
37
|
-
|
|
38
|
-
const isVariable = (_b = config.identifiers) === null || _b === void 0 ? void 0 : _b.filter(variable => variable.name === identifier);
|
|
39
|
-
if (!isFunction && !isVariable) {
|
|
49
|
+
if (!isFunction(identifier, config) && !isVariable(identifier, config)) {
|
|
40
50
|
diagnostics.push({
|
|
41
51
|
from: node.from,
|
|
42
52
|
to: node.to,
|
|
43
|
-
severity: '
|
|
53
|
+
severity: 'error',
|
|
44
54
|
message: `Identifier "${identifier}" not found`,
|
|
45
55
|
});
|
|
46
56
|
}
|
|
47
57
|
}
|
|
58
|
+
previousNode = node.node;
|
|
48
59
|
});
|
|
49
60
|
return diagnostics;
|
|
50
61
|
});
|
|
62
|
+
const keywordTooltip = (config) => view.hoverTooltip((view, pos, side) => {
|
|
63
|
+
var _a, _b;
|
|
64
|
+
let { from, to, text } = view.state.doc.lineAt(pos);
|
|
65
|
+
let start = pos, end = pos;
|
|
66
|
+
while (start > from && /\w/.test(text[start - from - 1]))
|
|
67
|
+
start--;
|
|
68
|
+
while (end < to && /\w/.test(text[end - from]))
|
|
69
|
+
end++;
|
|
70
|
+
if (start == pos && side < 0 || end == pos && side > 0) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const keyword = text.slice(start - from, end - from);
|
|
74
|
+
const info = (_b = ((_a = isFunction(keyword, config)) !== null && _a !== void 0 ? _a : isVariable(keyword, config))) === null || _b === void 0 ? void 0 : _b.info;
|
|
75
|
+
if (!info) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
pos: start,
|
|
80
|
+
end,
|
|
81
|
+
above: true,
|
|
82
|
+
create(view) {
|
|
83
|
+
let dom = document.createElement("div");
|
|
84
|
+
dom.textContent = info;
|
|
85
|
+
dom.className = 'cm-diagnostic';
|
|
86
|
+
return { dom };
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
});
|
|
51
90
|
const ELLanguage = language.LRLanguage.define({
|
|
52
91
|
parser: parser.configure({
|
|
53
92
|
props: [
|
|
@@ -76,57 +115,73 @@ const ELLanguage = language.LRLanguage.define({
|
|
|
76
115
|
}),
|
|
77
116
|
languageData: {}
|
|
78
117
|
});
|
|
79
|
-
function completeOperatorKeyword(state, config, tree, from, to) {
|
|
118
|
+
function completeOperatorKeyword(state, config, tree, from, to, explicit) {
|
|
80
119
|
var _a, _b;
|
|
81
120
|
const text = state.sliceDoc(from, to);
|
|
82
121
|
return {
|
|
83
122
|
from,
|
|
84
123
|
to,
|
|
85
|
-
options: (_b = (_a = config.operatorKeywords) === null || _a === void 0 ? void 0 : _a.filter(
|
|
86
|
-
validFor: (text) => { var _a, _b; return (_b = (_a = config.operatorKeywords) === null || _a === void 0 ? void 0 : _a.some(
|
|
124
|
+
options: (_b = (_a = config.operatorKeywords) === null || _a === void 0 ? void 0 : _a.filter(({ name }) => explicit || name.startsWith(text)).map(({ name, info, detail }) => ({ label: name, apply: `${name} `, info, detail, type: "keyword" }))) !== null && _b !== void 0 ? _b : [],
|
|
125
|
+
validFor: (text) => { var _a, _b; return (_b = (_a = config.operatorKeywords) === null || _a === void 0 ? void 0 : _a.some(({ name }) => explicit || name.startsWith(text))) !== null && _b !== void 0 ? _b : false; },
|
|
87
126
|
};
|
|
88
127
|
}
|
|
89
|
-
function completeIdentifier(state, config, tree, from, to) {
|
|
90
|
-
var _a, _b, _c, _d, _e, _f;
|
|
128
|
+
function completeIdentifier(state, config, tree, from, to, explicit) {
|
|
129
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
91
130
|
const text = state.sliceDoc(from, to);
|
|
92
|
-
const identifiers = (_b = (_a = config.identifiers) === null || _a === void 0 ? void 0 : _a.filter(({ name }) => name.startsWith(text))) !== null && _b !== void 0 ? _b : [];
|
|
93
|
-
const functions = (_d = (_c = config.functions) === null || _c === void 0 ? void 0 : _c.filter(({ name }) => name.startsWith(text))) !== null && _d !== void 0 ? _d : [];
|
|
131
|
+
const identifiers = (_b = (_a = config.identifiers) === null || _a === void 0 ? void 0 : _a.filter(({ name }) => explicit || name.startsWith(text))) !== null && _b !== void 0 ? _b : [];
|
|
132
|
+
const functions = (_d = (_c = config.functions) === null || _c === void 0 ? void 0 : _c.filter(({ name }) => explicit || name.startsWith(text))) !== null && _d !== void 0 ? _d : [];
|
|
133
|
+
const prevName = (_e = tree.prevSibling) === null || _e === void 0 ? void 0 : _e.name;
|
|
134
|
+
const apply = (name) => !prevName || !['OpeningBracket', 'Operator', 'OperatorKeyword', 'Punctuation'].includes(prevName) ? `${name} ` : name;
|
|
94
135
|
return {
|
|
95
136
|
from,
|
|
96
137
|
to,
|
|
97
138
|
options: [
|
|
98
|
-
...((
|
|
99
|
-
...((
|
|
139
|
+
...((_f = identifiers.map(({ name, info, detail }) => ({ label: name, apply: apply(name), info, detail, type: 'variable' }))) !== null && _f !== void 0 ? _f : []),
|
|
140
|
+
...((_g = functions.map(({ name, args = [], info }) => ({ label: name, detail: `(${args.join(',')})`, apply: `${name}(${args.length == 0 ? ')' : ''}`, info, type: "function" }))) !== null && _g !== void 0 ? _g : []),
|
|
100
141
|
],
|
|
101
142
|
validFor: identifier,
|
|
102
143
|
};
|
|
103
144
|
}
|
|
104
145
|
function expressionLanguageCompletionFor(config, context) {
|
|
105
|
-
let { state, pos } = context;
|
|
146
|
+
let { state, pos, explicit } = context;
|
|
106
147
|
let tree = language.syntaxTree(state).resolveInner(pos, -1);
|
|
148
|
+
const isOperator = (node) => ['Operator', 'OperatorKeyword', 'Punctuation', 'NullSafe', 'NullCoalescing', 'OpeningBracket'].includes(node.name);
|
|
149
|
+
const isIdentifier = (node) => node.name === 'Identifier';
|
|
107
150
|
if (tree.name == 'String') {
|
|
108
151
|
return null;
|
|
109
152
|
}
|
|
110
|
-
if (tree.prevSibling && !
|
|
111
|
-
return completeOperatorKeyword(state, config, tree, tree.from, pos);
|
|
153
|
+
if (tree.prevSibling && !isOperator(tree.prevSibling) && (!explicit || !isOperator(tree.node))) {
|
|
154
|
+
return completeOperatorKeyword(state, config, tree, tree.from, pos, explicit);
|
|
112
155
|
}
|
|
113
|
-
if (tree.
|
|
114
|
-
return completeIdentifier(state, config, tree, tree.from, pos);
|
|
156
|
+
if ((!tree.prevSibling || !isIdentifier(tree.prevSibling) || isOperator(tree.node)) && (explicit || isIdentifier(tree.node))) {
|
|
157
|
+
return completeIdentifier(state, config, tree, isIdentifier(tree.node) ? tree.from : pos, pos, explicit);
|
|
115
158
|
}
|
|
116
159
|
return null;
|
|
117
160
|
}
|
|
118
161
|
function expressionLanguageCompletionSourceWith(config) {
|
|
119
162
|
var _a;
|
|
120
|
-
(_a = config.operatorKeywords) !== null && _a !== void 0 ? _a : (config.operatorKeywords = [
|
|
163
|
+
(_a = config.operatorKeywords) !== null && _a !== void 0 ? _a : (config.operatorKeywords = [
|
|
164
|
+
{ name: 'starts with' },
|
|
165
|
+
{ name: 'ends with' },
|
|
166
|
+
{ name: 'contains' },
|
|
167
|
+
{ name: 'matches' },
|
|
168
|
+
{ name: 'not in' },
|
|
169
|
+
{ name: 'in' },
|
|
170
|
+
{ name: 'not' },
|
|
171
|
+
{ name: 'or' },
|
|
172
|
+
{ name: 'and' },
|
|
173
|
+
]);
|
|
121
174
|
return (context) => expressionLanguageCompletionFor(config, context);
|
|
122
175
|
}
|
|
123
176
|
function expressionlanguage(config = {}, extensions = []) {
|
|
124
177
|
return new language.LanguageSupport(ELLanguage, [
|
|
125
178
|
ELLanguage.data.of({ autocomplete: expressionLanguageCompletionSourceWith(config) }),
|
|
126
179
|
expressionLanguageLinter(config),
|
|
180
|
+
keywordTooltip(config),
|
|
127
181
|
...extensions,
|
|
128
182
|
]);
|
|
129
183
|
}
|
|
130
184
|
|
|
131
185
|
exports.ELLanguage = ELLanguage;
|
|
132
186
|
exports.expressionlanguage = expressionlanguage;
|
|
187
|
+
exports.keywordTooltip = keywordTooltip;
|
package/dist/index.d.cts
CHANGED
|
@@ -10,8 +10,13 @@ interface ExpressionLanguageConfig {
|
|
|
10
10
|
args: string[];
|
|
11
11
|
info?: string;
|
|
12
12
|
}[];
|
|
13
|
-
operatorKeywords?: readonly
|
|
13
|
+
operatorKeywords?: readonly {
|
|
14
|
+
name: string;
|
|
15
|
+
detail?: string;
|
|
16
|
+
info?: string;
|
|
17
|
+
}[];
|
|
14
18
|
}
|
|
19
|
+
declare const keywordTooltip: (config: ExpressionLanguageConfig) => import("@codemirror/state").Extension;
|
|
15
20
|
declare const ELLanguage: LRLanguage;
|
|
16
21
|
declare function expressionlanguage(config?: ExpressionLanguageConfig, extensions?: Array<any>): LanguageSupport;
|
|
17
|
-
export { ExpressionLanguageConfig, ELLanguage, expressionlanguage };
|
|
22
|
+
export { ExpressionLanguageConfig, keywordTooltip, ELLanguage, expressionlanguage };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,8 +10,13 @@ interface ExpressionLanguageConfig {
|
|
|
10
10
|
args: string[];
|
|
11
11
|
info?: string;
|
|
12
12
|
}[];
|
|
13
|
-
operatorKeywords?: readonly
|
|
13
|
+
operatorKeywords?: readonly {
|
|
14
|
+
name: string;
|
|
15
|
+
detail?: string;
|
|
16
|
+
info?: string;
|
|
17
|
+
}[];
|
|
14
18
|
}
|
|
19
|
+
declare const keywordTooltip: (config: ExpressionLanguageConfig) => import("@codemirror/state").Extension;
|
|
15
20
|
declare const ELLanguage: LRLanguage;
|
|
16
21
|
declare function expressionlanguage(config?: ExpressionLanguageConfig, extensions?: Array<any>): LanguageSupport;
|
|
17
|
-
export { ExpressionLanguageConfig, ELLanguage, expressionlanguage };
|
|
22
|
+
export { ExpressionLanguageConfig, keywordTooltip, ELLanguage, expressionlanguage };
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,12 @@ import { LRParser } from '@lezer/lr';
|
|
|
2
2
|
import { LRLanguage, indentNodeProp, delimitedIndent, foldNodeProp, foldInside, LanguageSupport, syntaxTree } from '@codemirror/language';
|
|
3
3
|
import { styleTags, tags } from '@lezer/highlight';
|
|
4
4
|
import { linter } from '@codemirror/lint';
|
|
5
|
+
import { hoverTooltip } from '@codemirror/view';
|
|
5
6
|
|
|
6
7
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
|
7
8
|
const parser = LRParser.deserialize({
|
|
8
9
|
version: 14,
|
|
9
|
-
states: "$nQVQPOOO!QQQO'#ChO!VQPO'#CjO!^QPO'#CmOOQO'#Cs'#CsOOQO'#Cn'#CnQVQPOOO!eQQO,59SOOQO,59U,59UO!
|
|
10
|
+
states: "$nQVQPOOO!QQQO'#ChO!VQPO'#CjO!^QPO'#CmOOQO'#Cs'#CsOOQO'#Cn'#CnQVQPOOO!eQQO,59SOOQO,59U,59UO!jQQO,59UOOQO,59X,59XO!rQPO,59XOOQO-E6l-E6lOVQPO1G.nOVQPO'#CoO!yQQO1G.pOOQO1G.p1G.pOOQO1G.s1G.sO#RQQO7+$YOOQO,59Z,59ZOOQO-E6m-E6mOOQO7+$[7+$[OOQO<<Gt<<GtO#ZQQO<<GtO#`QQOAN=`OVQPOG22zO#eQPOLD(fOOQO!$'LQ!$'LQ",
|
|
10
11
|
stateData: "#r~OfOS~OQSORSOSSOTSOUSOVSOWSOXSOYSOZSO`ROiPOmQO~O]VO~OlWO~PVO_YO~PVOj]O~Ok^Ol`O~O_aO~PVOk^OleO~OhfOkgO~O]hO~OjiO~OhkO~OQUTXWRW~",
|
|
11
12
|
goto: "!ghPPPPPPPPPPPPiPiPPis}PPP!TaSOQRUZ]^iQUOQZRT[UZQ_XRd_WTORUZQXQQb]Qc^Rji",
|
|
12
13
|
nodeNames: "⚠ Program Number Identifier String Boolean Null Operator OperatorKeyword Punctuation NullSafe NullCoalescing Object ObjectKey Array ClosingBracket OpeningBracket Application",
|
|
@@ -17,33 +18,71 @@ const parser = LRParser.deserialize({
|
|
|
17
18
|
],
|
|
18
19
|
skippedNodes: [0],
|
|
19
20
|
repeatNodeCount: 2,
|
|
20
|
-
tokenData: "
|
|
21
|
+
tokenData: "B{~R!QXY$XYZ$X]^$Xpq$Xqr$jrs%Puv$zvw&{wx'Txy(xyz(}z{)S{|$z|})[}!O$z!O!P)c!P!Q$z!Q![*t![!],T!^!_$r!_!`,[!`!a$r!a!b,b!c!},w!}#O-[#P#Q-a#Q#R$z#R#S,w#T#U-f#U#V,w#V#W/P#W#X,w#X#Y2s#Y#Z5g#Z#],w#]#^8R#^#a,w#a#b8l#b#c;[#c#d>u#d#g,w#g#h?`#h#iAe#i#o,w#o#pBi#p#qBn#q#rBv#r#s$z~$^Sf~XY$XYZ$X]^$Xpq$X~$oPV~!_!`$r~$wPV~!_!`$z~%POV~R%SXOr%Prs%os#O%P#O#P%v#P;'S%P;'S;=`&u<%l~%P~O%P~~%oR%vO]QSPR%yRO;'S%P;'S;=`&S;=`O%PR&VYOr%Prs%os#O%P#O#P%v#P;'S%P;'S;=`&u;=`<%l%P<%l~%P~O%P~~%oR&xP;=`<%l%P~'QPV~vw$zR'WXOw'Twx%ox#O'T#O#P's#P;'S'T;'S;=`(r<%l~'T~O'T~~%oR'vRO;'S'T;'S;=`(P;=`O'TR(SYOw'Twx%ox#O'T#O#P's#P;'S'T;'S;=`(r;=`<%l'T<%l~'T~O'T~~%oR(uP;=`<%l'T~(}O`~~)SO_~~)XPV~z{$zR)cOkQXP~)hQXP!O!P$z!Q![)n~)sSQ~!Q![)n!g!h*P#R#S*n#X#Y*P~*SR{|*]}!O*]!Q![*c~*`P!Q![*c~*hQQ~!Q![*c#R#S*]~*qP!Q![)n~*yTQ~!O!P+Y!Q![*t!g!h*P#R#S+}#X#Y*P~+]SO!O+i!P;'S+i;'S;=`+w<%lO+i~+nRQ~!Q![)n!g!h*P#X#Y*P~+zP;=`<%l+i~,QP!Q![*tR,[OjQXP~,_P!_!`$r~,gQXP!O!P,m!a!b,r~,rOY~~,wOZ~R-OS]QRP!Q![,w!c!},w#R#S,w#T#o,w~-aOm~~-fOl~R-mU]QRP!Q![,w!c!},w#R#S,w#T#b,w#b#c.P#c#o,wR.WU]QRP!Q![,w!c!},w#R#S,w#T#W,w#W#X.j#X#o,wR.sS]QWPRP!Q![,w!c!},w#R#S,w#T#o,wR/WU]QRP!Q![,w!c!},w#R#S,w#T#c,w#c#d/j#d#o,wR/qU]QRP!Q![,w!c!},w#R#S,w#T#b,w#b#c0T#c#o,wR0[U]QRP!Q![,w!c!},w#R#S,w#T#h,w#h#i0n#i#o,wR0uT]QRP!Q![,w!c!},w#R#S,w#T#U1U#U#o,wR1]U]QRP!Q![,w!c!},w#R#S,w#T#],w#]#^1o#^#o,wR1vU]QRP!Q![,w!c!},w#R#S,w#T#b,w#b#c2Y#c#o,wR2aU]QRP!Q![,w!c!},w#R#S,w#T#g,w#g#h.j#h#o,wR2zU]QRP!Q![,w!c!},w#R#S,w#T#b,w#b#c3^#c#o,wR3eU]QRP!Q![,w!c!},w#R#S,w#T#W,w#W#X3w#X#o,wR4OU]QRP!Q![,w!c!},w#R#S,w#T#g,w#g#h4b#h#o,wR4iT]QRPpq4x!Q![,w!c!},w#R#S,w#T#o,wP4{P#k#l5OP5RP#]#^5UP5XP#h#i5[P5_P#[#]5bP5gOWPR5nT]QRP!Q![,w!c!},w#R#S,w#T#U5}#U#o,wR6UU]QRP!Q![,w!c!},w#R#S,w#T#`,w#`#a6h#a#o,wR6oU]QRP!Q![,w!c!},w#R#S,w#T#g,w#g#h7R#h#o,wR7YU]QRP!Q![,w!c!},w#R#S,w#T#X,w#X#Y7l#Y#o,wR7uS]QTPRP!Q![,w!c!},w#R#S,w#T#o,wR8YU]QRP!Q![,w!c!},w#R#S,w#T#b,w#b#c.j#c#o,wR8sT]QRP!Q![,w!c!},w#R#S,w#T#U9S#U#o,wR9ZU]QRP!Q![,w!c!},w#R#S,w#T#h,w#h#i9m#i#o,wR9tU]QRP!Q![,w!c!},w#R#S,w#T#V,w#V#W:W#W#o,wR:_U]QRP!Q![,w!c!},w#R#S,w#T#[,w#[#]:q#]#o,wR:xU]QRP!Q![,w!c!},w#R#S,w#T#X,w#X#Y2Y#Y#o,wR;cW]QRP!Q![,w!c!},w#R#S,w#T#c,w#c#d;{#d#i,w#i#j=[#j#o,wR<SU]QRP!Q![,w!c!},w#R#S,w#T#h,w#h#i<f#i#o,wR<oT]QWPRPpq=O!Q![,w!c!},w#R#S,w#T#o,wP=RP#]#^=UP=XP#b#c5bR=cU]QRP!Q![,w!c!},w#R#S,w#T#`,w#`#a=u#a#o,wR=|U]QRP!Q![,w!c!},w#R#S,w#T#`,w#`#a>`#a#o,wR>iS]QUPRP!Q![,w!c!},w#R#S,w#T#o,wR>|U]QRP!Q![,w!c!},w#R#S,w#T#f,w#f#g.j#g#o,wR?gU]QRP!Q![,w!c!},w#R#S,w#T#h,w#h#i?y#i#o,wR@QT]QRP!Q![,w!c!},w#R#S,w#T#U@a#U#o,wR@hU]QRP!Q![,w!c!},w#R#S,w#T#f,w#f#g@z#g#o,wRARU]QRP!Q![,w!c!},w#R#S,w#T#h,w#h#i3w#i#o,wRAlU]QRP!Q![,w!c!},w#R#S,w#T#f,w#f#gBO#g#o,wRBVU]QRP!Q![,w!c!},w#R#S,w#T#i,w#i#j7R#j#o,w~BnOi~~BsPV~#p#q$z~B{Oh~",
|
|
21
22
|
tokenizers: [0, 1],
|
|
22
23
|
topRules: {"Program":[0,1]},
|
|
23
24
|
tokenPrec: 118
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
const identifier = /^[a-zA-Z_]+[a-zA-Z_0-9]*$/;
|
|
28
|
+
const isFunction = (identifier, config) => { var _a; return (_a = config.functions) === null || _a === void 0 ? void 0 : _a.find(fn => fn.name === identifier); };
|
|
29
|
+
const isVariable = (identifier, config) => { var _a; return (_a = config.identifiers) === null || _a === void 0 ? void 0 : _a.find(variable => variable.name === identifier); };
|
|
27
30
|
const expressionLanguageLinter = (config) => linter(view => {
|
|
28
31
|
let diagnostics = [];
|
|
32
|
+
let previousNode = null;
|
|
29
33
|
syntaxTree(view.state).cursor().iterate(node => {
|
|
30
|
-
var _a
|
|
34
|
+
var _a;
|
|
31
35
|
if (node.name == "Identifier") {
|
|
36
|
+
if ((previousNode === null || previousNode === void 0 ? void 0 : previousNode.name) == "Identifier" && ((_a = node.node.parent) === null || _a === void 0 ? void 0 : _a.name) != 'Array') {
|
|
37
|
+
diagnostics.push({
|
|
38
|
+
from: node.from,
|
|
39
|
+
to: node.to,
|
|
40
|
+
severity: 'error',
|
|
41
|
+
message: `Unexpected identifier after another identifier`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
32
44
|
const identifier = view.state.sliceDoc(node.from, node.to);
|
|
33
|
-
|
|
34
|
-
const isVariable = (_b = config.identifiers) === null || _b === void 0 ? void 0 : _b.filter(variable => variable.name === identifier);
|
|
35
|
-
if (!isFunction && !isVariable) {
|
|
45
|
+
if (!isFunction(identifier, config) && !isVariable(identifier, config)) {
|
|
36
46
|
diagnostics.push({
|
|
37
47
|
from: node.from,
|
|
38
48
|
to: node.to,
|
|
39
|
-
severity: '
|
|
49
|
+
severity: 'error',
|
|
40
50
|
message: `Identifier "${identifier}" not found`,
|
|
41
51
|
});
|
|
42
52
|
}
|
|
43
53
|
}
|
|
54
|
+
previousNode = node.node;
|
|
44
55
|
});
|
|
45
56
|
return diagnostics;
|
|
46
57
|
});
|
|
58
|
+
const keywordTooltip = (config) => hoverTooltip((view, pos, side) => {
|
|
59
|
+
var _a, _b;
|
|
60
|
+
let { from, to, text } = view.state.doc.lineAt(pos);
|
|
61
|
+
let start = pos, end = pos;
|
|
62
|
+
while (start > from && /\w/.test(text[start - from - 1]))
|
|
63
|
+
start--;
|
|
64
|
+
while (end < to && /\w/.test(text[end - from]))
|
|
65
|
+
end++;
|
|
66
|
+
if (start == pos && side < 0 || end == pos && side > 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const keyword = text.slice(start - from, end - from);
|
|
70
|
+
const info = (_b = ((_a = isFunction(keyword, config)) !== null && _a !== void 0 ? _a : isVariable(keyword, config))) === null || _b === void 0 ? void 0 : _b.info;
|
|
71
|
+
if (!info) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
pos: start,
|
|
76
|
+
end,
|
|
77
|
+
above: true,
|
|
78
|
+
create(view) {
|
|
79
|
+
let dom = document.createElement("div");
|
|
80
|
+
dom.textContent = info;
|
|
81
|
+
dom.className = 'cm-diagnostic';
|
|
82
|
+
return { dom };
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
});
|
|
47
86
|
const ELLanguage = LRLanguage.define({
|
|
48
87
|
parser: parser.configure({
|
|
49
88
|
props: [
|
|
@@ -72,56 +111,71 @@ const ELLanguage = LRLanguage.define({
|
|
|
72
111
|
}),
|
|
73
112
|
languageData: {}
|
|
74
113
|
});
|
|
75
|
-
function completeOperatorKeyword(state, config, tree, from, to) {
|
|
114
|
+
function completeOperatorKeyword(state, config, tree, from, to, explicit) {
|
|
76
115
|
var _a, _b;
|
|
77
116
|
const text = state.sliceDoc(from, to);
|
|
78
117
|
return {
|
|
79
118
|
from,
|
|
80
119
|
to,
|
|
81
|
-
options: (_b = (_a = config.operatorKeywords) === null || _a === void 0 ? void 0 : _a.filter(
|
|
82
|
-
validFor: (text) => { var _a, _b; return (_b = (_a = config.operatorKeywords) === null || _a === void 0 ? void 0 : _a.some(
|
|
120
|
+
options: (_b = (_a = config.operatorKeywords) === null || _a === void 0 ? void 0 : _a.filter(({ name }) => explicit || name.startsWith(text)).map(({ name, info, detail }) => ({ label: name, apply: `${name} `, info, detail, type: "keyword" }))) !== null && _b !== void 0 ? _b : [],
|
|
121
|
+
validFor: (text) => { var _a, _b; return (_b = (_a = config.operatorKeywords) === null || _a === void 0 ? void 0 : _a.some(({ name }) => explicit || name.startsWith(text))) !== null && _b !== void 0 ? _b : false; },
|
|
83
122
|
};
|
|
84
123
|
}
|
|
85
|
-
function completeIdentifier(state, config, tree, from, to) {
|
|
86
|
-
var _a, _b, _c, _d, _e, _f;
|
|
124
|
+
function completeIdentifier(state, config, tree, from, to, explicit) {
|
|
125
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
87
126
|
const text = state.sliceDoc(from, to);
|
|
88
|
-
const identifiers = (_b = (_a = config.identifiers) === null || _a === void 0 ? void 0 : _a.filter(({ name }) => name.startsWith(text))) !== null && _b !== void 0 ? _b : [];
|
|
89
|
-
const functions = (_d = (_c = config.functions) === null || _c === void 0 ? void 0 : _c.filter(({ name }) => name.startsWith(text))) !== null && _d !== void 0 ? _d : [];
|
|
127
|
+
const identifiers = (_b = (_a = config.identifiers) === null || _a === void 0 ? void 0 : _a.filter(({ name }) => explicit || name.startsWith(text))) !== null && _b !== void 0 ? _b : [];
|
|
128
|
+
const functions = (_d = (_c = config.functions) === null || _c === void 0 ? void 0 : _c.filter(({ name }) => explicit || name.startsWith(text))) !== null && _d !== void 0 ? _d : [];
|
|
129
|
+
const prevName = (_e = tree.prevSibling) === null || _e === void 0 ? void 0 : _e.name;
|
|
130
|
+
const apply = (name) => !prevName || !['OpeningBracket', 'Operator', 'OperatorKeyword', 'Punctuation'].includes(prevName) ? `${name} ` : name;
|
|
90
131
|
return {
|
|
91
132
|
from,
|
|
92
133
|
to,
|
|
93
134
|
options: [
|
|
94
|
-
...((
|
|
95
|
-
...((
|
|
135
|
+
...((_f = identifiers.map(({ name, info, detail }) => ({ label: name, apply: apply(name), info, detail, type: 'variable' }))) !== null && _f !== void 0 ? _f : []),
|
|
136
|
+
...((_g = functions.map(({ name, args = [], info }) => ({ label: name, detail: `(${args.join(',')})`, apply: `${name}(${args.length == 0 ? ')' : ''}`, info, type: "function" }))) !== null && _g !== void 0 ? _g : []),
|
|
96
137
|
],
|
|
97
138
|
validFor: identifier,
|
|
98
139
|
};
|
|
99
140
|
}
|
|
100
141
|
function expressionLanguageCompletionFor(config, context) {
|
|
101
|
-
let { state, pos } = context;
|
|
142
|
+
let { state, pos, explicit } = context;
|
|
102
143
|
let tree = syntaxTree(state).resolveInner(pos, -1);
|
|
144
|
+
const isOperator = (node) => ['Operator', 'OperatorKeyword', 'Punctuation', 'NullSafe', 'NullCoalescing', 'OpeningBracket'].includes(node.name);
|
|
145
|
+
const isIdentifier = (node) => node.name === 'Identifier';
|
|
103
146
|
if (tree.name == 'String') {
|
|
104
147
|
return null;
|
|
105
148
|
}
|
|
106
|
-
if (tree.prevSibling && !
|
|
107
|
-
return completeOperatorKeyword(state, config, tree, tree.from, pos);
|
|
149
|
+
if (tree.prevSibling && !isOperator(tree.prevSibling) && (!explicit || !isOperator(tree.node))) {
|
|
150
|
+
return completeOperatorKeyword(state, config, tree, tree.from, pos, explicit);
|
|
108
151
|
}
|
|
109
|
-
if (tree.
|
|
110
|
-
return completeIdentifier(state, config, tree, tree.from, pos);
|
|
152
|
+
if ((!tree.prevSibling || !isIdentifier(tree.prevSibling) || isOperator(tree.node)) && (explicit || isIdentifier(tree.node))) {
|
|
153
|
+
return completeIdentifier(state, config, tree, isIdentifier(tree.node) ? tree.from : pos, pos, explicit);
|
|
111
154
|
}
|
|
112
155
|
return null;
|
|
113
156
|
}
|
|
114
157
|
function expressionLanguageCompletionSourceWith(config) {
|
|
115
158
|
var _a;
|
|
116
|
-
(_a = config.operatorKeywords) !== null && _a !== void 0 ? _a : (config.operatorKeywords = [
|
|
159
|
+
(_a = config.operatorKeywords) !== null && _a !== void 0 ? _a : (config.operatorKeywords = [
|
|
160
|
+
{ name: 'starts with' },
|
|
161
|
+
{ name: 'ends with' },
|
|
162
|
+
{ name: 'contains' },
|
|
163
|
+
{ name: 'matches' },
|
|
164
|
+
{ name: 'not in' },
|
|
165
|
+
{ name: 'in' },
|
|
166
|
+
{ name: 'not' },
|
|
167
|
+
{ name: 'or' },
|
|
168
|
+
{ name: 'and' },
|
|
169
|
+
]);
|
|
117
170
|
return (context) => expressionLanguageCompletionFor(config, context);
|
|
118
171
|
}
|
|
119
172
|
function expressionlanguage(config = {}, extensions = []) {
|
|
120
173
|
return new LanguageSupport(ELLanguage, [
|
|
121
174
|
ELLanguage.data.of({ autocomplete: expressionLanguageCompletionSourceWith(config) }),
|
|
122
175
|
expressionLanguageLinter(config),
|
|
176
|
+
keywordTooltip(config),
|
|
123
177
|
...extensions,
|
|
124
178
|
]);
|
|
125
179
|
}
|
|
126
180
|
|
|
127
|
-
export { ELLanguage, expressionlanguage };
|
|
181
|
+
export { ELLanguage, expressionlanguage, keywordTooltip };
|
package/package.json
CHANGED