@bpmn-io/feel-editor 1.5.0 → 1.6.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 +10 -1
- package/dist/index.es.js +545 -359
- package/dist/index.js +559 -373
- package/package.json +22 -22
package/dist/index.es.js
CHANGED
|
@@ -1,18 +1,116 @@
|
|
|
1
|
-
import { snippetCompletion, autocompletion
|
|
1
|
+
import { snippetCompletion, autocompletion, closeBrackets } from '@codemirror/autocomplete';
|
|
2
2
|
import { defaultKeymap } from '@codemirror/commands';
|
|
3
|
-
import {
|
|
3
|
+
import { syntaxHighlighting, HighlightStyle, syntaxTree, bracketMatching, indentOnInput } from '@codemirror/language';
|
|
4
4
|
import { linter as linter$1, setDiagnosticsEffect } from '@codemirror/lint';
|
|
5
5
|
import { Facet, Compartment, EditorState } from '@codemirror/state';
|
|
6
6
|
import { EditorView, tooltips, keymap, placeholder } from '@codemirror/view';
|
|
7
|
-
import { snippets, keywordCompletions, feelLanguage } from 'lang-feel';
|
|
8
|
-
import { domify } from 'min-dom';
|
|
9
7
|
import { cmFeelLinter } from '@bpmn-io/feel-lint';
|
|
10
|
-
import { tags
|
|
8
|
+
import { tags } from '@lezer/highlight';
|
|
9
|
+
import { snippetCompletion as snippetCompletion$1, snippets, keywordCompletions, feel } from 'lang-feel';
|
|
10
|
+
import { domify } from 'min-dom';
|
|
11
|
+
|
|
12
|
+
var linter = [ linter$1(cmFeelLinter()) ];
|
|
13
|
+
|
|
14
|
+
const baseTheme = EditorView.theme({
|
|
15
|
+
'& .cm-content': {
|
|
16
|
+
padding: '0px',
|
|
17
|
+
},
|
|
18
|
+
'& .cm-line': {
|
|
19
|
+
padding: '0px',
|
|
20
|
+
},
|
|
21
|
+
'&.cm-editor.cm-focused': {
|
|
22
|
+
outline: 'none',
|
|
23
|
+
},
|
|
24
|
+
'& .cm-completionInfo': {
|
|
25
|
+
whiteSpace: 'pre-wrap',
|
|
26
|
+
overflow: 'hidden',
|
|
27
|
+
textOverflow: 'ellipsis'
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// Don't wrap whitespace for custom HTML
|
|
31
|
+
'& .cm-completionInfo > *': {
|
|
32
|
+
whiteSpace: 'normal'
|
|
33
|
+
},
|
|
34
|
+
'& .cm-completionInfo ul': {
|
|
35
|
+
margin: 0,
|
|
36
|
+
paddingLeft: '15px'
|
|
37
|
+
},
|
|
38
|
+
'& .cm-completionInfo pre': {
|
|
39
|
+
marginBottom: 0,
|
|
40
|
+
whiteSpace: 'pre-wrap'
|
|
41
|
+
},
|
|
42
|
+
'& .cm-completionInfo p': {
|
|
43
|
+
marginTop: 0,
|
|
44
|
+
},
|
|
45
|
+
'& .cm-completionInfo p:not(:last-of-type)': {
|
|
46
|
+
marginBottom: 0,
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const highlightTheme = EditorView.baseTheme({
|
|
51
|
+
'& .variableName': {
|
|
52
|
+
color: '#10f'
|
|
53
|
+
},
|
|
54
|
+
'& .number': {
|
|
55
|
+
color: '#164'
|
|
56
|
+
},
|
|
57
|
+
'& .string': {
|
|
58
|
+
color: '#a11'
|
|
59
|
+
},
|
|
60
|
+
'& .bool': {
|
|
61
|
+
color: '#219'
|
|
62
|
+
},
|
|
63
|
+
'& .function': {
|
|
64
|
+
color: '#aa3731',
|
|
65
|
+
fontWeight: 'bold'
|
|
66
|
+
},
|
|
67
|
+
'& .control': {
|
|
68
|
+
color: '#708'
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const syntaxClasses = syntaxHighlighting(
|
|
73
|
+
HighlightStyle.define([
|
|
74
|
+
{ tag: tags.variableName, class: 'variableName' },
|
|
75
|
+
{ tag: tags.name, class: 'variableName' },
|
|
76
|
+
{ tag: tags.number, class: 'number' },
|
|
77
|
+
{ tag: tags.string, class: 'string' },
|
|
78
|
+
{ tag: tags.bool, class: 'bool' },
|
|
79
|
+
{ tag: tags.function(tags.variableName), class: 'function' },
|
|
80
|
+
{ tag: tags.function(tags.special(tags.variableName)), class: 'function' },
|
|
81
|
+
{ tag: tags.controlKeyword, class: 'control' },
|
|
82
|
+
{ tag: tags.operatorKeyword, class: 'control' }
|
|
83
|
+
])
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
var theme = [ baseTheme, highlightTheme, syntaxClasses ];
|
|
11
87
|
|
|
12
88
|
// helpers ///////////////////////////////
|
|
13
89
|
|
|
14
|
-
function
|
|
15
|
-
return node.from === node.to;
|
|
90
|
+
function _isEmpty(node) {
|
|
91
|
+
return node && node.from === node.to;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {any} node
|
|
96
|
+
* @param {number} pos
|
|
97
|
+
*
|
|
98
|
+
* @return {boolean}
|
|
99
|
+
*/
|
|
100
|
+
function isEmpty(node, pos) {
|
|
101
|
+
|
|
102
|
+
// For the special case of empty nodes, we need to check the current node
|
|
103
|
+
// as well. The previous node could be part of another token, e.g.
|
|
104
|
+
// when typing functions "abs(".
|
|
105
|
+
const nextNode = node.nextSibling;
|
|
106
|
+
|
|
107
|
+
return _isEmpty(node) || (
|
|
108
|
+
nextNode && nextNode.from === pos && _isEmpty(nextNode)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isVariableName(node) {
|
|
113
|
+
return node && node.parent && node.parent.name === 'VariableName';
|
|
16
114
|
}
|
|
17
115
|
|
|
18
116
|
function isPathExpression(node) {
|
|
@@ -20,14 +118,389 @@ function isPathExpression(node) {
|
|
|
20
118
|
return false;
|
|
21
119
|
}
|
|
22
120
|
|
|
23
|
-
if (node.name === 'PathExpression') {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
121
|
+
if (node.name === 'PathExpression') {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return isPathExpression(node.parent);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @typedef { import('../core').Variable } Variable
|
|
130
|
+
* @typedef { import('@codemirror/autocomplete').CompletionSource } CompletionSource
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param { {
|
|
135
|
+
* variables?: Variable[],
|
|
136
|
+
* } } options
|
|
137
|
+
*
|
|
138
|
+
* @return { CompletionSource }
|
|
139
|
+
*/
|
|
140
|
+
function pathExpressionCompletion({ variables }) {
|
|
141
|
+
|
|
142
|
+
return (context) => {
|
|
143
|
+
|
|
144
|
+
const nodeBefore = syntaxTree(context.state).resolve(context.pos, -1);
|
|
145
|
+
|
|
146
|
+
if (!isPathExpression(nodeBefore)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const expression = findPathExpression(nodeBefore);
|
|
151
|
+
|
|
152
|
+
// if the cursor is directly after the `.`, variable starts at the cursor position
|
|
153
|
+
const from = nodeBefore === expression ? context.pos : nodeBefore.from;
|
|
154
|
+
|
|
155
|
+
const path = getPath(expression, context);
|
|
156
|
+
|
|
157
|
+
let options = variables;
|
|
158
|
+
for (var i = 0; i < path.length - 1; i++) {
|
|
159
|
+
var childVar = options.find(val => val.name === path[i].name);
|
|
160
|
+
|
|
161
|
+
if (!childVar) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// only suggest if variable type matches
|
|
166
|
+
if (
|
|
167
|
+
childVar.isList !== 'optional' &&
|
|
168
|
+
!!childVar.isList !== path[i].isList
|
|
169
|
+
) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
options = childVar.entries;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!options) return;
|
|
177
|
+
|
|
178
|
+
options = options.map(v => ({
|
|
179
|
+
label: v.name,
|
|
180
|
+
type: 'variable',
|
|
181
|
+
info: v.info,
|
|
182
|
+
detail: v.detail
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
const result = {
|
|
186
|
+
from: from,
|
|
187
|
+
options: options
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return result;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
function findPathExpression(node) {
|
|
196
|
+
while (node) {
|
|
197
|
+
if (node.name === 'PathExpression') {
|
|
198
|
+
return node;
|
|
199
|
+
}
|
|
200
|
+
node = node.parent;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// parses the path expression into a list of variable names with type information
|
|
205
|
+
// e.g. foo[0].bar => [ { name: 'foo', isList: true }, { name: 'bar', isList: false } ]
|
|
206
|
+
function getPath(node, context) {
|
|
207
|
+
let path = [];
|
|
208
|
+
|
|
209
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
210
|
+
if (child.name === 'PathExpression') {
|
|
211
|
+
path.push(...getPath(child, context));
|
|
212
|
+
} else if (child.name === 'FilterExpression') {
|
|
213
|
+
path.push(...getFilter(child, context));
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
path.push({
|
|
217
|
+
name: getNodeContent(child, context),
|
|
218
|
+
isList: false
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return path;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function getFilter(node, context) {
|
|
226
|
+
const list = node.firstChild;
|
|
227
|
+
|
|
228
|
+
if (list.name === 'PathExpression') {
|
|
229
|
+
const path = getPath(list, context);
|
|
230
|
+
const last = path[path.length - 1];
|
|
231
|
+
last.isList = true;
|
|
232
|
+
|
|
233
|
+
return path;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return [ {
|
|
237
|
+
name: getNodeContent(list, context),
|
|
238
|
+
isList: true
|
|
239
|
+
} ];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function getNodeContent(node, context) {
|
|
243
|
+
return context.state.sliceDoc(node.from, node.to);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @typedef { import('../core').Variable } Variable
|
|
248
|
+
* @typedef { import('@codemirror/autocomplete').CompletionSource } CompletionSource
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param { {
|
|
253
|
+
* variables?: Variable[],
|
|
254
|
+
* builtins?: Variable[]
|
|
255
|
+
* } } options
|
|
256
|
+
*
|
|
257
|
+
* @return { CompletionSource }
|
|
258
|
+
*/
|
|
259
|
+
function variableCompletion({ variables = [], builtins = [] }) {
|
|
260
|
+
|
|
261
|
+
const options = getVariableSuggestions(variables, builtins);
|
|
262
|
+
|
|
263
|
+
if (!options.length) {
|
|
264
|
+
return (context) => null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return (context) => {
|
|
268
|
+
|
|
269
|
+
const {
|
|
270
|
+
pos,
|
|
271
|
+
state
|
|
272
|
+
} = context;
|
|
273
|
+
|
|
274
|
+
// in most cases, use what is typed before the cursor
|
|
275
|
+
const nodeBefore = syntaxTree(state).resolve(pos, -1);
|
|
276
|
+
|
|
277
|
+
if (isEmpty(nodeBefore, pos)) {
|
|
278
|
+
return context.explicit ? {
|
|
279
|
+
from: pos,
|
|
280
|
+
options
|
|
281
|
+
} : null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// only auto-complete variables
|
|
285
|
+
if (!isVariableName(nodeBefore) || isPathExpression(nodeBefore)) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
from: nodeBefore.from,
|
|
291
|
+
options
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @param { Variable[] } variables
|
|
298
|
+
* @param { Variable[] } builtins
|
|
299
|
+
*
|
|
300
|
+
* @returns {import('@codemirror/autocomplete').Completion[]}
|
|
301
|
+
*/
|
|
302
|
+
function getVariableSuggestions(variables, builtins) {
|
|
303
|
+
return [].concat(
|
|
304
|
+
variables.map(v => createVariableSuggestion(v)),
|
|
305
|
+
builtins.map(b => createVariableSuggestion(b))
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* @param {import('..').Variable} variable
|
|
311
|
+
* @param {number} boost
|
|
312
|
+
|
|
313
|
+
* @returns {import('@codemirror/autocomplete').Completion}
|
|
314
|
+
*/
|
|
315
|
+
function createVariableSuggestion(variable, boost) {
|
|
316
|
+
if (variable.type === 'function') {
|
|
317
|
+
return createFunctionVariable(variable, boost);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
label: variable.name,
|
|
322
|
+
type: 'variable',
|
|
323
|
+
info: variable.info,
|
|
324
|
+
detail: variable.detail,
|
|
325
|
+
boost
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @param {import('..').Variable} variable
|
|
331
|
+
* @param {number} boost
|
|
332
|
+
*
|
|
333
|
+
* @returns {import('@codemirror/autocomplete').Completion}
|
|
334
|
+
*/
|
|
335
|
+
function createFunctionVariable(variable, boost) {
|
|
336
|
+
const {
|
|
337
|
+
name,
|
|
338
|
+
info,
|
|
339
|
+
detail,
|
|
340
|
+
params = []
|
|
341
|
+
} = variable;
|
|
342
|
+
|
|
343
|
+
const paramsWithNames = params.map(({ name, type }, index) => ({
|
|
344
|
+
name: name || `param ${index + 1}`,
|
|
345
|
+
type
|
|
346
|
+
}));
|
|
347
|
+
|
|
348
|
+
const template = `${name}(${paramsWithNames.map(p => '${' + p.name + '}').join(', ')})`;
|
|
349
|
+
|
|
350
|
+
const paramsSignature = paramsWithNames.map(({ name, type }) => (
|
|
351
|
+
type ? `${name}: ${type}` : name
|
|
352
|
+
)).join(', ');
|
|
353
|
+
const label = `${name}(${paramsSignature})`;
|
|
354
|
+
|
|
355
|
+
return snippetCompletion(template, {
|
|
356
|
+
label,
|
|
357
|
+
type: 'function',
|
|
358
|
+
info,
|
|
359
|
+
detail,
|
|
360
|
+
boost
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @typedef { import('../core').Variable } Variable
|
|
366
|
+
* @typedef { import('@codemirror/autocomplete').CompletionSource } CompletionSource
|
|
367
|
+
*/
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* @param { {
|
|
371
|
+
* variables?: Variable[],
|
|
372
|
+
* builtins?: Variable[]
|
|
373
|
+
* } } options
|
|
374
|
+
*
|
|
375
|
+
* @return { CompletionSource[] }
|
|
376
|
+
*/
|
|
377
|
+
function completions({ variables = [], builtins = [] }) {
|
|
378
|
+
|
|
379
|
+
return [
|
|
380
|
+
pathExpressionCompletion({ variables }),
|
|
381
|
+
variableCompletion({ variables, builtins }),
|
|
382
|
+
snippetCompletion$1(snippets.map(snippet => ({ ...snippet, boost: -1 }))),
|
|
383
|
+
...keywordCompletions
|
|
384
|
+
];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* @typedef { 'expression' | 'unaryTests' } Dialect
|
|
389
|
+
*/
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* @param { {
|
|
393
|
+
* dialect?: Dialect,
|
|
394
|
+
* context?: Record<string, any>,
|
|
395
|
+
* completions?: import('@codemirror/autocomplete').CompletionSource[]
|
|
396
|
+
* } } options
|
|
397
|
+
*
|
|
398
|
+
* @return { import('@codemirror/language').LanguageSupport }
|
|
399
|
+
*/
|
|
400
|
+
function language(options) {
|
|
401
|
+
return feel(options);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* @param { import('../core').Variable[] } variables
|
|
406
|
+
*
|
|
407
|
+
* @return {Record<string, any>}
|
|
408
|
+
*/
|
|
409
|
+
function createContext(variables, builtins) {
|
|
410
|
+
return variables.slice().reverse().reduce((context, builtin) => {
|
|
411
|
+
context[builtin.name] = new Function();
|
|
412
|
+
|
|
413
|
+
return context;
|
|
414
|
+
}, {});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* @typedef { 'expression' | 'unaryTests' } Dialect
|
|
419
|
+
* @typedef { import('..').Variable } Variable
|
|
420
|
+
*/
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* @type {Facet<Variable[]>}
|
|
424
|
+
*/
|
|
425
|
+
const builtinsFacet = Facet.define();
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* @type {Facet<Variable[]>}
|
|
429
|
+
*/
|
|
430
|
+
const variablesFacet = Facet.define();
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* @type {Facet<dialect>}
|
|
434
|
+
*/
|
|
435
|
+
const dialectFacet = Facet.define();
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* @typedef {object} Variable
|
|
439
|
+
* @property {string} name name or key of the variable
|
|
440
|
+
* @property {string} [info] short information about the variable, e.g. type
|
|
441
|
+
* @property {string} [detail] longer description of the variable content
|
|
442
|
+
* @property {boolean} [isList] whether the variable is a list
|
|
443
|
+
* @property {Array<Variable>} [schema] array of child variables if the variable is a context or list
|
|
444
|
+
* @property {'function'|'variable'} [type] type of the variable
|
|
445
|
+
* @property {Array<{name: string, type: string}>} [params] function parameters
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* @typedef { {
|
|
450
|
+
* dialect?: import('../language').Dialect,
|
|
451
|
+
* variables?: Variable[],
|
|
452
|
+
* builtins?: Variable[]
|
|
453
|
+
* } } CoreConfig
|
|
454
|
+
*
|
|
455
|
+
* @typedef { import('@codemirror/autocomplete').CompletionSource } CompletionSource
|
|
456
|
+
* @typedef { import('@codemirror/state').Extension } Extension
|
|
457
|
+
*/
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* @param { CoreConfig & { completions?: CompletionSource[] } } config
|
|
461
|
+
*
|
|
462
|
+
* @return { Extension }
|
|
463
|
+
*/
|
|
464
|
+
function configure({
|
|
465
|
+
dialect = 'expression',
|
|
466
|
+
variables = [],
|
|
467
|
+
builtins = [],
|
|
468
|
+
completions: completions$1 = completions({ builtins, variables })
|
|
469
|
+
}) {
|
|
470
|
+
|
|
471
|
+
const context = createContext([ ...variables, ...builtins ]);
|
|
472
|
+
|
|
473
|
+
return [
|
|
474
|
+
dialectFacet.of(dialect),
|
|
475
|
+
builtinsFacet.of(builtins),
|
|
476
|
+
variablesFacet.of(variables),
|
|
477
|
+
language({
|
|
478
|
+
dialect,
|
|
479
|
+
context,
|
|
480
|
+
completions: completions$1
|
|
481
|
+
})
|
|
482
|
+
];
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* @param {import('@codemirror/state').EditorState } state
|
|
487
|
+
*
|
|
488
|
+
* @return { CoreConfig }
|
|
489
|
+
*/
|
|
490
|
+
function get(state) {
|
|
491
|
+
|
|
492
|
+
const builtins = state.facet(builtinsFacet)[0];
|
|
493
|
+
const variables = state.facet(variablesFacet)[0];
|
|
494
|
+
const dialect = state.facet(dialectFacet)[0];
|
|
26
495
|
|
|
27
|
-
return
|
|
496
|
+
return {
|
|
497
|
+
builtins,
|
|
498
|
+
variables,
|
|
499
|
+
dialect
|
|
500
|
+
};
|
|
28
501
|
}
|
|
29
502
|
|
|
30
|
-
var
|
|
503
|
+
var camundaTags = [
|
|
31
504
|
{
|
|
32
505
|
name: "not(negand)",
|
|
33
506
|
description: "<p>Returns the logical negation of the given value.</p>\n<p><strong>Function signature</strong></p>\n<pre><code class=\"language-feel\">not(negand: boolean): boolean\n</code></pre>\n<p><strong>Examples</strong></p>\n<pre><code class=\"language-feel\">not(true)\n// false\n\nnot(null)\n// null\n</code></pre>\n"
|
|
@@ -106,7 +579,7 @@ var tags = [
|
|
|
106
579
|
},
|
|
107
580
|
{
|
|
108
581
|
name: "date and time(from)",
|
|
109
|
-
description: "<p>Parses the given string into a date and time.</p>\n<p><strong>Function signature</strong></p>\n<pre><code class=\"language-feel\">date and time(from: string): date and time\n</code></pre>\n<p><strong>Examples</strong></p>\n<pre><code class=\"language-feel\">date and time("2018-04-
|
|
582
|
+
description: "<p>Parses the given string into a date and time.</p>\n<p><strong>Function signature</strong></p>\n<pre><code class=\"language-feel\">date and time(from: string): date and time\n</code></pre>\n<p><strong>Examples</strong></p>\n<pre><code class=\"language-feel\">date and time("2018-04-29T09:30:00")\n// date and time("2018-04-29T09:30:00")\n</code></pre>\n"
|
|
110
583
|
},
|
|
111
584
|
{
|
|
112
585
|
name: "date and time(date, time)",
|
|
@@ -510,363 +983,57 @@ var tags = [
|
|
|
510
983
|
}
|
|
511
984
|
];
|
|
512
985
|
|
|
513
|
-
const options = tags.map(tag => {
|
|
514
|
-
const match = tag.name.match(/^([\w\s]+)\((.*)\)$/);
|
|
515
|
-
const functionName = match[1];
|
|
516
|
-
const functionArguments = match[2];
|
|
517
|
-
|
|
518
|
-
const placeHolders = functionArguments
|
|
519
|
-
.split(', ')
|
|
520
|
-
.map((arg) => `\${${arg}}`)
|
|
521
|
-
.join(', ');
|
|
522
|
-
|
|
523
|
-
return snippetCompletion(
|
|
524
|
-
`${functionName}(${placeHolders})`,
|
|
525
|
-
{
|
|
526
|
-
label: tag.name,
|
|
527
|
-
type: 'function',
|
|
528
|
-
info: () => {
|
|
529
|
-
const html = domify(`<div class="description">${tag.description}<div>`);
|
|
530
|
-
return html;
|
|
531
|
-
},
|
|
532
|
-
boost: -1
|
|
533
|
-
}
|
|
534
|
-
);
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
var builtins = context => {
|
|
538
|
-
|
|
539
|
-
let nodeBefore = syntaxTree(context.state).resolve(context.pos, -1);
|
|
540
|
-
|
|
541
|
-
// For the special case of empty nodes, we need to check the current node
|
|
542
|
-
// as well. The previous node could be part of another token, e.g.
|
|
543
|
-
// when typing functions "abs(".
|
|
544
|
-
let nextNode = nodeBefore.nextSibling;
|
|
545
|
-
const isInEmptyNode =
|
|
546
|
-
isNodeEmpty(nodeBefore) ||
|
|
547
|
-
nextNode && nextNode.from === context.pos && isNodeEmpty(nextNode);
|
|
548
|
-
|
|
549
|
-
if (isInEmptyNode) {
|
|
550
|
-
return context.explicit ? {
|
|
551
|
-
from: context.pos,
|
|
552
|
-
options: options
|
|
553
|
-
} : null;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Don't auto-complete on path expressions/context keys/...
|
|
557
|
-
if ((nodeBefore.parent && nodeBefore.parent.name !== 'VariableName') || isPathExpression(nodeBefore)) {
|
|
558
|
-
return null;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
return {
|
|
562
|
-
from: nodeBefore.from,
|
|
563
|
-
options: options
|
|
564
|
-
};
|
|
565
|
-
};
|
|
566
|
-
|
|
567
986
|
/**
|
|
568
|
-
* @
|
|
987
|
+
* @param { import('..').Builtin[] } builtins
|
|
988
|
+
*
|
|
989
|
+
* @returns {import('..').Variable[] } variable
|
|
569
990
|
*/
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
var pathExpression = context => {
|
|
573
|
-
const variables = context.state.facet(variablesFacet)[0];
|
|
574
|
-
const nodeBefore = syntaxTree(context.state).resolve(context.pos, -1);
|
|
575
|
-
|
|
576
|
-
if (!isPathExpression(nodeBefore)) {
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
const expression = findPathExpression(nodeBefore);
|
|
581
|
-
|
|
582
|
-
// if the cursor is directly after the `.`, variable starts at the cursor position
|
|
583
|
-
const from = nodeBefore === expression ? context.pos : nodeBefore.from;
|
|
584
|
-
|
|
585
|
-
const path = getPath(expression, context);
|
|
586
|
-
|
|
587
|
-
let options = variables;
|
|
588
|
-
for (var i = 0; i < path.length - 1; i++) {
|
|
589
|
-
var childVar = options.find(val => val.name === path[i].name);
|
|
590
|
-
|
|
591
|
-
if (!childVar) {
|
|
592
|
-
return null;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// only suggest if variable type matches
|
|
596
|
-
if (
|
|
597
|
-
childVar.isList !== 'optional' &&
|
|
598
|
-
!!childVar.isList !== path[i].isList
|
|
599
|
-
) {
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
options = childVar.entries;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
if (!options) return;
|
|
607
|
-
|
|
608
|
-
options = options.map(v => ({
|
|
609
|
-
label: v.name,
|
|
610
|
-
type: 'variable',
|
|
611
|
-
info: v.info,
|
|
612
|
-
detail: v.detail
|
|
613
|
-
}));
|
|
614
|
-
|
|
615
|
-
const result = {
|
|
616
|
-
from: from,
|
|
617
|
-
options: options
|
|
618
|
-
};
|
|
619
|
-
|
|
620
|
-
return result;
|
|
621
|
-
};
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
function findPathExpression(node) {
|
|
625
|
-
while (node) {
|
|
626
|
-
if (node.name === 'PathExpression') {
|
|
627
|
-
return node;
|
|
628
|
-
}
|
|
629
|
-
node = node.parent;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// parses the path expression into a list of variable names with type information
|
|
634
|
-
// e.g. foo[0].bar => [ { name: 'foo', isList: true }, { name: 'bar', isList: false } ]
|
|
635
|
-
function getPath(node, context) {
|
|
636
|
-
let path = [];
|
|
637
|
-
|
|
638
|
-
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
639
|
-
if (child.name === 'PathExpression') {
|
|
640
|
-
path.push(...getPath(child, context));
|
|
641
|
-
} else if (child.name === 'FilterExpression') {
|
|
642
|
-
path.push(...getFilter(child, context));
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
645
|
-
path.push({
|
|
646
|
-
name: getNodeContent(child, context),
|
|
647
|
-
isList: false
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
return path;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function getFilter(node, context) {
|
|
655
|
-
const list = node.firstChild;
|
|
656
|
-
|
|
657
|
-
if (list.name === 'PathExpression') {
|
|
658
|
-
const path = getPath(list, context);
|
|
659
|
-
const last = path[path.length - 1];
|
|
660
|
-
last.isList = true;
|
|
661
|
-
|
|
662
|
-
return path;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
return [ {
|
|
666
|
-
name: getNodeContent(list, context),
|
|
667
|
-
isList: true
|
|
668
|
-
} ];
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function getNodeContent(node, context) {
|
|
672
|
-
return context.state.sliceDoc(node.from, node.to);
|
|
991
|
+
function parseBuiltins(builtins) {
|
|
992
|
+
return builtins.map(parseBuiltin);
|
|
673
993
|
}
|
|
674
994
|
|
|
675
995
|
/**
|
|
676
|
-
* @
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
const variables = context.state.facet(variablesFacet)[0];
|
|
681
|
-
|
|
682
|
-
const options = variables.map(v => createVariableSuggestion(v));
|
|
683
|
-
|
|
684
|
-
// In most cases, use what is typed before the cursor
|
|
685
|
-
let nodeBefore = syntaxTree(context.state).resolve(context.pos, -1);
|
|
686
|
-
|
|
687
|
-
// For the special case of empty nodes, we need to check the current node
|
|
688
|
-
// as well. The previous node could be part of another token, e.g.
|
|
689
|
-
// when typing functions "abs(".
|
|
690
|
-
let nextNode = nodeBefore.nextSibling;
|
|
691
|
-
const isInEmptyNode =
|
|
692
|
-
isNodeEmpty(nodeBefore) ||
|
|
693
|
-
nextNode && nextNode.from === context.pos && isNodeEmpty(nextNode);
|
|
694
|
-
|
|
695
|
-
if (isInEmptyNode) {
|
|
696
|
-
return context.explicit ? {
|
|
697
|
-
from: context.pos,
|
|
698
|
-
options: options
|
|
699
|
-
} : null;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
const result = {
|
|
703
|
-
from: nodeBefore.from,
|
|
704
|
-
options: options
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
// Only auto-complete variables
|
|
708
|
-
if ((nodeBefore.parent && nodeBefore.parent.name !== 'VariableName') || isPathExpression(nodeBefore)) {
|
|
709
|
-
return null;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
return result;
|
|
713
|
-
};
|
|
714
|
-
|
|
715
|
-
/**
|
|
716
|
-
* @param {import('..').Variable} variable
|
|
717
|
-
* @returns {import('@codemirror/autocomplete').Completion}
|
|
996
|
+
* @param { import('..').Builtin } builtin
|
|
997
|
+
*
|
|
998
|
+
* @returns { import('..').Variable } variable
|
|
718
999
|
*/
|
|
719
|
-
function
|
|
720
|
-
if (variable.type === 'function') {
|
|
721
|
-
return createFunctionVariable(variable);
|
|
722
|
-
}
|
|
1000
|
+
function parseBuiltin(builtin) {
|
|
723
1001
|
|
|
724
|
-
return {
|
|
725
|
-
label: variable.name,
|
|
726
|
-
type: 'variable',
|
|
727
|
-
info: variable.info,
|
|
728
|
-
detail: variable.detail
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
* @param {import('..').Variable} variable
|
|
734
|
-
* @returns {import('@codemirror/autocomplete').Completion}
|
|
735
|
-
*/
|
|
736
|
-
function createFunctionVariable(variable) {
|
|
737
1002
|
const {
|
|
738
1003
|
name,
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
params = []
|
|
742
|
-
} = variable;
|
|
743
|
-
|
|
744
|
-
const paramsWithNames = params.map(({ name, type }, index) => ({
|
|
745
|
-
name: name || `param ${index + 1}`,
|
|
746
|
-
type
|
|
747
|
-
}));
|
|
1004
|
+
description
|
|
1005
|
+
} = builtin;
|
|
748
1006
|
|
|
749
|
-
const
|
|
1007
|
+
const match = name.match(/^([\w\s]+)\((.*)\)$/);
|
|
1008
|
+
const functionName = match[1];
|
|
1009
|
+
const functionArguments = match[2];
|
|
750
1010
|
|
|
751
|
-
const
|
|
752
|
-
type ? `${name}: ${type}` : name
|
|
753
|
-
)).join(', ');
|
|
754
|
-
const label = `${name}(${paramsSignature})`;
|
|
1011
|
+
const params = functionArguments.split(', ').map(name => ({ name }));
|
|
755
1012
|
|
|
756
|
-
return
|
|
757
|
-
|
|
1013
|
+
return {
|
|
1014
|
+
name: functionName,
|
|
758
1015
|
type: 'function',
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
return [
|
|
766
|
-
autocompletion$1({
|
|
767
|
-
override: [
|
|
768
|
-
variables,
|
|
769
|
-
builtins,
|
|
770
|
-
completeFromList(snippets.map(s => ({ ...s, boost: -1 }))),
|
|
771
|
-
pathExpression,
|
|
772
|
-
...keywordCompletions
|
|
773
|
-
]
|
|
774
|
-
})
|
|
775
|
-
];
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
function language() {
|
|
779
|
-
return new LanguageSupport(feelLanguage, [ ]);
|
|
1016
|
+
params,
|
|
1017
|
+
info: () => {
|
|
1018
|
+
return domify(`<div class="description">${description}<div>`);
|
|
1019
|
+
},
|
|
1020
|
+
boost: 0
|
|
1021
|
+
};
|
|
780
1022
|
}
|
|
781
1023
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
const baseTheme = EditorView.theme({
|
|
785
|
-
'& .cm-content': {
|
|
786
|
-
padding: '0px',
|
|
787
|
-
},
|
|
788
|
-
'& .cm-line': {
|
|
789
|
-
padding: '0px',
|
|
790
|
-
},
|
|
791
|
-
'&.cm-editor.cm-focused': {
|
|
792
|
-
outline: 'none',
|
|
793
|
-
},
|
|
794
|
-
'& .cm-completionInfo': {
|
|
795
|
-
whiteSpace: 'pre-wrap',
|
|
796
|
-
overflow: 'hidden',
|
|
797
|
-
textOverflow: 'ellipsis'
|
|
798
|
-
},
|
|
799
|
-
|
|
800
|
-
// Don't wrap whitespace for custom HTML
|
|
801
|
-
'& .cm-completionInfo > *': {
|
|
802
|
-
whiteSpace: 'normal'
|
|
803
|
-
},
|
|
804
|
-
'& .cm-completionInfo ul': {
|
|
805
|
-
margin: 0,
|
|
806
|
-
paddingLeft: '15px'
|
|
807
|
-
},
|
|
808
|
-
'& .cm-completionInfo pre': {
|
|
809
|
-
marginBottom: 0,
|
|
810
|
-
whiteSpace: 'pre-wrap'
|
|
811
|
-
},
|
|
812
|
-
'& .cm-completionInfo p': {
|
|
813
|
-
marginTop: 0,
|
|
814
|
-
},
|
|
815
|
-
'& .cm-completionInfo p:not(:last-of-type)': {
|
|
816
|
-
marginBottom: 0,
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
const highlightTheme = EditorView.baseTheme({
|
|
821
|
-
'& .variableName': {
|
|
822
|
-
color: '#10f'
|
|
823
|
-
},
|
|
824
|
-
'& .number': {
|
|
825
|
-
color: '#164'
|
|
826
|
-
},
|
|
827
|
-
'& .string': {
|
|
828
|
-
color: '#a11'
|
|
829
|
-
},
|
|
830
|
-
'& .bool': {
|
|
831
|
-
color: '#219'
|
|
832
|
-
},
|
|
833
|
-
'& .function': {
|
|
834
|
-
color: '#aa3731',
|
|
835
|
-
fontWeight: 'bold'
|
|
836
|
-
},
|
|
837
|
-
'& .control': {
|
|
838
|
-
color: '#708'
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
const syntaxClasses = syntaxHighlighting(
|
|
843
|
-
HighlightStyle.define([
|
|
844
|
-
{ tag: tags$1.variableName, class: 'variableName' },
|
|
845
|
-
{ tag: tags$1.name, class: 'variableName' },
|
|
846
|
-
{ tag: tags$1.number, class: 'number' },
|
|
847
|
-
{ tag: tags$1.string, class: 'string' },
|
|
848
|
-
{ tag: tags$1.bool, class: 'bool' },
|
|
849
|
-
{ tag: tags$1.function(tags$1.variableName), class: 'function' },
|
|
850
|
-
{ tag: tags$1.function(tags$1.special(tags$1.variableName)), class: 'function' },
|
|
851
|
-
{ tag: tags$1.controlKeyword, class: 'control' },
|
|
852
|
-
{ tag: tags$1.operatorKeyword, class: 'control' }
|
|
853
|
-
])
|
|
854
|
-
);
|
|
1024
|
+
const camunda = parseBuiltins(camundaTags);
|
|
855
1025
|
|
|
856
|
-
|
|
1026
|
+
/**
|
|
1027
|
+
* @typedef { import('./core').Variable } Variable
|
|
1028
|
+
*/
|
|
857
1029
|
|
|
858
1030
|
/**
|
|
859
|
-
* @typedef {object}
|
|
860
|
-
* @property {string} name
|
|
861
|
-
* @property {string}
|
|
862
|
-
* @property {string} [detail] longer description of the variable content
|
|
863
|
-
* @property {boolean} [isList] whether the variable is a list
|
|
864
|
-
* @property {Array<Variable>} [schema] array of child variables if the variable is a context or list
|
|
865
|
-
* @property {'function'|'variable'} [type] type of the variable
|
|
866
|
-
* @property {Array<{name: string, type: string}>} [params] function parameters
|
|
1031
|
+
* @typedef {object} Builtin
|
|
1032
|
+
* @property {string} name
|
|
1033
|
+
* @property {string} description
|
|
867
1034
|
*/
|
|
868
1035
|
|
|
869
|
-
const
|
|
1036
|
+
const coreConf = new Compartment();
|
|
870
1037
|
const placeholderConf = new Compartment();
|
|
871
1038
|
|
|
872
1039
|
|
|
@@ -876,6 +1043,7 @@ const placeholderConf = new Compartment();
|
|
|
876
1043
|
* @param {Object} config
|
|
877
1044
|
* @param {DOMNode} config.container
|
|
878
1045
|
* @param {Extension[]} [config.extensions]
|
|
1046
|
+
* @param {Dialect} [config.dialect='expression']
|
|
879
1047
|
* @param {DOMNode|String} [config.tooltipContainer]
|
|
880
1048
|
* @param {Function} [config.onChange]
|
|
881
1049
|
* @param {Function} [config.onKeyDown]
|
|
@@ -883,11 +1051,13 @@ const placeholderConf = new Compartment();
|
|
|
883
1051
|
* @param {Boolean} [config.readOnly]
|
|
884
1052
|
* @param {String} [config.value]
|
|
885
1053
|
* @param {Variable[]} [config.variables]
|
|
1054
|
+
* @param {Variable[]} [config.builtins]
|
|
886
1055
|
*
|
|
887
1056
|
* @returns {Object} editor
|
|
888
1057
|
*/
|
|
889
1058
|
function FeelEditor({
|
|
890
1059
|
extensions: editorExtensions = [],
|
|
1060
|
+
dialect = 'expression',
|
|
891
1061
|
container,
|
|
892
1062
|
contentAttributes = {},
|
|
893
1063
|
tooltipContainer,
|
|
@@ -897,6 +1067,7 @@ function FeelEditor({
|
|
|
897
1067
|
placeholder: placeholder$1 = '',
|
|
898
1068
|
readOnly = false,
|
|
899
1069
|
value = '',
|
|
1070
|
+
builtins = camunda,
|
|
900
1071
|
variables = []
|
|
901
1072
|
}) {
|
|
902
1073
|
|
|
@@ -937,22 +1108,25 @@ function FeelEditor({
|
|
|
937
1108
|
}) : [];
|
|
938
1109
|
|
|
939
1110
|
const extensions = [
|
|
940
|
-
autocompletionConf.of(variablesFacet.of(variables)),
|
|
941
1111
|
autocompletion(),
|
|
1112
|
+
coreConf.of(configure({
|
|
1113
|
+
dialect,
|
|
1114
|
+
builtins,
|
|
1115
|
+
variables
|
|
1116
|
+
})),
|
|
942
1117
|
bracketMatching(),
|
|
943
|
-
|
|
1118
|
+
indentOnInput(),
|
|
944
1119
|
closeBrackets(),
|
|
945
1120
|
EditorView.contentAttributes.of(contentAttributes),
|
|
946
|
-
|
|
1121
|
+
changeHandler,
|
|
947
1122
|
keyHandler,
|
|
948
1123
|
keymap.of([
|
|
949
1124
|
...defaultKeymap,
|
|
950
1125
|
]),
|
|
951
|
-
language(),
|
|
952
1126
|
linter,
|
|
953
1127
|
lintHandler,
|
|
954
|
-
placeholderConf.of(placeholder(placeholder$1)),
|
|
955
1128
|
tooltipLayout,
|
|
1129
|
+
placeholderConf.of(placeholder(placeholder$1)),
|
|
956
1130
|
theme,
|
|
957
1131
|
...editorExtensions
|
|
958
1132
|
];
|
|
@@ -964,7 +1138,7 @@ function FeelEditor({
|
|
|
964
1138
|
this._cmEditor = new EditorView({
|
|
965
1139
|
state: EditorState.create({
|
|
966
1140
|
doc: value,
|
|
967
|
-
extensions
|
|
1141
|
+
extensions
|
|
968
1142
|
}),
|
|
969
1143
|
parent: container
|
|
970
1144
|
});
|
|
@@ -1017,19 +1191,31 @@ FeelEditor.prototype.getSelection = function() {
|
|
|
1017
1191
|
|
|
1018
1192
|
/**
|
|
1019
1193
|
* Set variables to be used for autocompletion.
|
|
1194
|
+
*
|
|
1020
1195
|
* @param {Variable[]} variables
|
|
1021
|
-
* @returns {void}
|
|
1022
1196
|
*/
|
|
1023
1197
|
FeelEditor.prototype.setVariables = function(variables) {
|
|
1198
|
+
|
|
1199
|
+
const {
|
|
1200
|
+
dialect,
|
|
1201
|
+
builtins
|
|
1202
|
+
} = get(this._cmEditor.state);
|
|
1203
|
+
|
|
1024
1204
|
this._cmEditor.dispatch({
|
|
1025
|
-
effects:
|
|
1205
|
+
effects: [
|
|
1206
|
+
coreConf.reconfigure(configure({
|
|
1207
|
+
dialect,
|
|
1208
|
+
builtins,
|
|
1209
|
+
variables
|
|
1210
|
+
}))
|
|
1211
|
+
]
|
|
1026
1212
|
});
|
|
1027
1213
|
};
|
|
1028
1214
|
|
|
1029
1215
|
/**
|
|
1030
1216
|
* Update placeholder text.
|
|
1217
|
+
*
|
|
1031
1218
|
* @param {string} placeholder
|
|
1032
|
-
* @returns {void}
|
|
1033
1219
|
*/
|
|
1034
1220
|
FeelEditor.prototype.setPlaceholder = function(placeholder$1) {
|
|
1035
1221
|
this._cmEditor.dispatch({
|