@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.
Files changed (4) hide show
  1. package/README.md +10 -1
  2. package/dist/index.es.js +545 -359
  3. package/dist/index.js +559 -373
  4. package/package.json +22 -22
package/dist/index.es.js CHANGED
@@ -1,18 +1,116 @@
1
- import { snippetCompletion, autocompletion as autocompletion$1, completeFromList, closeBrackets } from '@codemirror/autocomplete';
1
+ import { snippetCompletion, autocompletion, closeBrackets } from '@codemirror/autocomplete';
2
2
  import { defaultKeymap } from '@codemirror/commands';
3
- import { syntaxTree, LanguageSupport, syntaxHighlighting, HighlightStyle, bracketMatching, indentOnInput } from '@codemirror/language';
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 as tags$1 } from '@lezer/highlight';
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 isNodeEmpty(node) {
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 isPathExpression(node.parent);
496
+ return {
497
+ builtins,
498
+ variables,
499
+ dialect
500
+ };
28
501
  }
29
502
 
30
- var tags = [
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(&quot;2018-04-29T009:30:00&quot;)\n// date and time(&quot;2018-04-29T009:30:00&quot;)\n</code></pre>\n"
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(&quot;2018-04-29T09:30:00&quot;)\n// date and time(&quot;2018-04-29T09:30:00&quot;)\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
- * @type {Facet<import('..').Variable[]>} Variable
987
+ * @param { import('..').Builtin[] } builtins
988
+ *
989
+ * @returns {import('..').Variable[] } variable
569
990
  */
570
- const variablesFacet = Facet.define();
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
- * @type {import('@codemirror/autocomplete').CompletionSource}
677
- */
678
- var variables = context => {
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 createVariableSuggestion(variable) {
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
- info,
740
- detail,
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 template = `${name}(${paramsWithNames.map(p => '${' + p.name + '}').join(', ')})`;
1007
+ const match = name.match(/^([\w\s]+)\((.*)\)$/);
1008
+ const functionName = match[1];
1009
+ const functionArguments = match[2];
750
1010
 
751
- const paramsSignature = paramsWithNames.map(({ name, type }) => (
752
- type ? `${name}: ${type}` : name
753
- )).join(', ');
754
- const label = `${name}(${paramsSignature})`;
1011
+ const params = functionArguments.split(', ').map(name => ({ name }));
755
1012
 
756
- return snippetCompletion(template, {
757
- label,
1013
+ return {
1014
+ name: functionName,
758
1015
  type: 'function',
759
- info,
760
- detail
761
- });
762
- }
763
-
764
- function autocompletion() {
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
- var linter = [ linter$1(cmFeelLinter()) ];
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
- var theme = [ baseTheme, highlightTheme, syntaxClasses ];
1026
+ /**
1027
+ * @typedef { import('./core').Variable } Variable
1028
+ */
857
1029
 
858
1030
  /**
859
- * @typedef {object} Variable
860
- * @property {string} name name or key of the variable
861
- * @property {string} [info] short information about the variable, e.g. type
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 autocompletionConf = new Compartment();
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
- changeHandler,
1118
+ indentOnInput(),
944
1119
  closeBrackets(),
945
1120
  EditorView.contentAttributes.of(contentAttributes),
946
- indentOnInput(),
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: 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: autocompletionConf.reconfigure(variablesFacet.of(variables))
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({