@bpmn-io/feel-editor 0.5.0 → 0.7.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 +22 -3
- package/dist/index.es.js +172 -65
- package/dist/index.js +169 -62
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -28,8 +28,9 @@ const editor = new FeelEditor({
|
|
|
28
28
|
|
|
29
29
|
### Variables
|
|
30
30
|
|
|
31
|
-
You can provide a variables array that will be used for auto completion.
|
|
32
|
-
|
|
31
|
+
You can provide a variables array that will be used for auto completion. Nested
|
|
32
|
+
structures are supported.
|
|
33
|
+
The Variables need to be in the following format:
|
|
33
34
|
|
|
34
35
|
```JavaScript
|
|
35
36
|
const editor = new FeelEditor({
|
|
@@ -38,12 +39,30 @@ const editor = new FeelEditor({
|
|
|
38
39
|
{
|
|
39
40
|
name: 'variablename to match',
|
|
40
41
|
detail: 'optional inline info',
|
|
41
|
-
info: 'optional pop-out info'
|
|
42
|
+
info: 'optional pop-out info',
|
|
43
|
+
entries: [
|
|
44
|
+
{
|
|
45
|
+
name: 'nested variable',
|
|
46
|
+
...
|
|
47
|
+
}
|
|
48
|
+
]
|
|
42
49
|
}
|
|
43
50
|
]
|
|
44
51
|
});
|
|
45
52
|
```
|
|
46
53
|
|
|
54
|
+
The variables can be updated on the instance via `FeelEditor#setVariables()`:
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
editor.setVariables([
|
|
58
|
+
{
|
|
59
|
+
name: 'newName',
|
|
60
|
+
detail: 'new variable inline info',
|
|
61
|
+
info: 'new pop-out info'
|
|
62
|
+
}
|
|
63
|
+
]);
|
|
64
|
+
```
|
|
65
|
+
|
|
47
66
|
## Hacking the Project
|
|
48
67
|
|
|
49
68
|
To get the development setup make sure to have [NodeJS](https://nodejs.org/en/download/) installed.
|
package/dist/index.es.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { snippetCompletion, autocompletion as autocompletion$1, completeFromList, closeBrackets } from '@codemirror/autocomplete';
|
|
2
2
|
import { defaultKeymap } from '@codemirror/commands';
|
|
3
|
-
import { syntaxTree, LanguageSupport, syntaxHighlighting, HighlightStyle,
|
|
3
|
+
import { syntaxTree, LanguageSupport, syntaxHighlighting, HighlightStyle, bracketMatching, indentOnInput } from '@codemirror/language';
|
|
4
4
|
import { linter as linter$1, setDiagnosticsEffect } from '@codemirror/lint';
|
|
5
|
-
import { EditorState } from '@codemirror/state';
|
|
6
|
-
import { EditorView, keymap } from '@codemirror/view';
|
|
5
|
+
import { Facet, Compartment, EditorState } from '@codemirror/state';
|
|
6
|
+
import { EditorView, tooltips, keymap } from '@codemirror/view';
|
|
7
7
|
import { snippets, feelLanguage } from 'lang-feel';
|
|
8
8
|
import { domify } from 'min-dom';
|
|
9
|
+
import { cmFeelLinter } from '@bpmn-io/feel-lint';
|
|
9
10
|
import { tags as tags$1 } from '@lezer/highlight';
|
|
10
11
|
|
|
11
12
|
// helpers ///////////////////////////////
|
|
@@ -371,9 +372,10 @@ const options = tags.map(tag => snippetCompletion(
|
|
|
371
372
|
label: tag.name,
|
|
372
373
|
type: 'function',
|
|
373
374
|
info: () => {
|
|
374
|
-
const html = domify(tag.description);
|
|
375
|
+
const html = domify(`<div class="description">${tag.description}<div>`);
|
|
375
376
|
return html;
|
|
376
|
-
}
|
|
377
|
+
},
|
|
378
|
+
boost: -1
|
|
377
379
|
}
|
|
378
380
|
));
|
|
379
381
|
|
|
@@ -407,7 +409,121 @@ var builtins = context => {
|
|
|
407
409
|
};
|
|
408
410
|
};
|
|
409
411
|
|
|
410
|
-
|
|
412
|
+
/**
|
|
413
|
+
* @type {Facet<import('..').Variable[]>} Variable
|
|
414
|
+
*/
|
|
415
|
+
const variablesFacet = Facet.define();
|
|
416
|
+
|
|
417
|
+
var pathExpression = context => {
|
|
418
|
+
const variables = context.state.facet(variablesFacet)[0];
|
|
419
|
+
const nodeBefore = syntaxTree(context.state).resolve(context.pos, -1);
|
|
420
|
+
|
|
421
|
+
if (!isPathExpression(nodeBefore)) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const expression = findPathExpression(nodeBefore);
|
|
426
|
+
|
|
427
|
+
// if the cursor is directly after the `.`, variable starts at the cursor position
|
|
428
|
+
const from = nodeBefore === expression ? context.pos : nodeBefore.from;
|
|
429
|
+
|
|
430
|
+
const path = getPath(expression, context);
|
|
431
|
+
|
|
432
|
+
let options = variables;
|
|
433
|
+
for (var i = 0; i < path.length - 1; i++) {
|
|
434
|
+
var childVar = options.find(val => val.name === path[i].name);
|
|
435
|
+
|
|
436
|
+
if (!childVar) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// only suggest if variable type matches
|
|
441
|
+
if (
|
|
442
|
+
childVar.isList !== 'optional' &&
|
|
443
|
+
!!childVar.isList !== path[i].isList
|
|
444
|
+
) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
options = childVar.entries;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!options) return;
|
|
452
|
+
|
|
453
|
+
options = options.map(v => ({
|
|
454
|
+
label: v.name,
|
|
455
|
+
type: 'variable',
|
|
456
|
+
info: v.info,
|
|
457
|
+
detail: v.detail
|
|
458
|
+
}));
|
|
459
|
+
|
|
460
|
+
const result = {
|
|
461
|
+
from: from,
|
|
462
|
+
options: options
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
return result;
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
function findPathExpression(node) {
|
|
470
|
+
while (node) {
|
|
471
|
+
if (node.name === 'PathExpression') {
|
|
472
|
+
return node;
|
|
473
|
+
}
|
|
474
|
+
node = node.parent;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// parses the path expression into a list of variable names with type information
|
|
479
|
+
// e.g. foo[0].bar => [ { name: 'foo', isList: true }, { name: 'bar', isList: false } ]
|
|
480
|
+
function getPath(node, context) {
|
|
481
|
+
let path = [];
|
|
482
|
+
|
|
483
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
484
|
+
if (child.name === 'PathExpression') {
|
|
485
|
+
path.push(...getPath(child, context));
|
|
486
|
+
} else if (child.name === 'FilterExpression') {
|
|
487
|
+
path.push(...getFilter(child, context));
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
path.push({
|
|
491
|
+
name: getNodeContent(child, context),
|
|
492
|
+
isList: false
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return path;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function getFilter(node, context) {
|
|
500
|
+
const list = node.firstChild;
|
|
501
|
+
|
|
502
|
+
if (list.name === 'PathExpression') {
|
|
503
|
+
const path = getPath(list, context);
|
|
504
|
+
const last = path[path.length - 1];
|
|
505
|
+
last.isList = true;
|
|
506
|
+
|
|
507
|
+
return path;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return [ {
|
|
511
|
+
name: getNodeContent(list, context),
|
|
512
|
+
isList: true
|
|
513
|
+
} ];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function getNodeContent(node, context) {
|
|
517
|
+
return context.state.sliceDoc(node.from, node.to);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* @type {import('@codemirror/autocomplete').CompletionSource}
|
|
522
|
+
*/
|
|
523
|
+
var variables = context => {
|
|
524
|
+
|
|
525
|
+
const variables = context.state.facet(variablesFacet)[0];
|
|
526
|
+
|
|
411
527
|
const options = variables.map(v => ({
|
|
412
528
|
label: v.name,
|
|
413
529
|
type: 'variable',
|
|
@@ -446,13 +562,14 @@ var variables = variables => context => {
|
|
|
446
562
|
return result;
|
|
447
563
|
};
|
|
448
564
|
|
|
449
|
-
function autocompletion(
|
|
565
|
+
function autocompletion() {
|
|
450
566
|
return [
|
|
451
567
|
autocompletion$1({
|
|
452
568
|
override: [
|
|
453
|
-
variables
|
|
569
|
+
variables,
|
|
454
570
|
builtins,
|
|
455
|
-
completeFromList(snippets)
|
|
571
|
+
completeFromList(snippets.map(s => ({ ...s, boost: -1 }))),
|
|
572
|
+
pathExpression
|
|
456
573
|
]
|
|
457
574
|
})
|
|
458
575
|
];
|
|
@@ -462,53 +579,7 @@ function language() {
|
|
|
462
579
|
return new LanguageSupport(feelLanguage, [ ]);
|
|
463
580
|
}
|
|
464
581
|
|
|
465
|
-
|
|
466
|
-
const messages = [];
|
|
467
|
-
|
|
468
|
-
// don't lint if the Editor is empty
|
|
469
|
-
if (editorView.state.doc.length === 0) {
|
|
470
|
-
return messages;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const tree = syntaxTree(editorView.state);
|
|
474
|
-
|
|
475
|
-
tree.iterate({
|
|
476
|
-
enter: node => {
|
|
477
|
-
if (node.type.isError) {
|
|
478
|
-
|
|
479
|
-
const error = node.toString();
|
|
480
|
-
|
|
481
|
-
/* The error has the pattern [⚠ || ⚠(NodeType)]. The regex extracts the node type from inside the brackets */
|
|
482
|
-
const match = /\((.*?)\)/.exec(error);
|
|
483
|
-
const nodeType = match && match[1];
|
|
484
|
-
|
|
485
|
-
let message;
|
|
486
|
-
|
|
487
|
-
if (nodeType) {
|
|
488
|
-
message = 'unexpected ' + nodeType;
|
|
489
|
-
} else {
|
|
490
|
-
message = 'expression expected';
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
messages.push(
|
|
494
|
-
{
|
|
495
|
-
from: node.from,
|
|
496
|
-
to: node.to,
|
|
497
|
-
severity: 'error',
|
|
498
|
-
message: message,
|
|
499
|
-
source: 'syntaxError'
|
|
500
|
-
}
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
return messages;
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
var syntaxLinter = linter$1(FeelLinter);
|
|
510
|
-
|
|
511
|
-
var linter = [ syntaxLinter ];
|
|
582
|
+
var linter = [ linter$1(cmFeelLinter()) ];
|
|
512
583
|
|
|
513
584
|
const baseTheme = EditorView.theme({
|
|
514
585
|
'& .cm-content': {
|
|
@@ -574,22 +645,35 @@ const syntaxClasses = syntaxHighlighting(
|
|
|
574
645
|
|
|
575
646
|
var theme = [ baseTheme, highlightTheme, syntaxClasses ];
|
|
576
647
|
|
|
648
|
+
/**
|
|
649
|
+
* @typedef {object} Variable
|
|
650
|
+
* @property {string} name name or key of the variable
|
|
651
|
+
* @property {string} [info] short information about the variable, e.g. type
|
|
652
|
+
* @property {string} [detail] longer description of the variable content
|
|
653
|
+
* @property {boolean} [isList] whether the variable is a list
|
|
654
|
+
* @property {array<Variable>} [schema] array of child variables if the variable is a context or list
|
|
655
|
+
*/
|
|
656
|
+
|
|
657
|
+
const autocompletionConf = new Compartment();
|
|
658
|
+
|
|
577
659
|
/**
|
|
578
660
|
* Creates a FEEL editor in the supplied container
|
|
579
661
|
*
|
|
580
662
|
* @param {Object} config
|
|
581
663
|
* @param {DOMNode} config.container
|
|
664
|
+
* @param {DOMNode|String} [config.tooltipContainer]
|
|
582
665
|
* @param {Function} [config.onChange]
|
|
583
666
|
* @param {Function} [config.onKeyDown]
|
|
584
667
|
* @param {Function} [config.onLint]
|
|
585
668
|
* @param {Boolean} [config.readOnly]
|
|
586
669
|
* @param {String} [config.value]
|
|
587
|
-
* @param {
|
|
670
|
+
* @param {Variable[]} [config.variables]
|
|
588
671
|
*
|
|
589
672
|
* @returns {Object} editor
|
|
590
673
|
*/
|
|
591
674
|
function FeelEditor({
|
|
592
675
|
container,
|
|
676
|
+
tooltipContainer,
|
|
593
677
|
onChange = () => {},
|
|
594
678
|
onKeyDown = () => {},
|
|
595
679
|
onLint = () => {},
|
|
@@ -624,20 +708,32 @@ function FeelEditor({
|
|
|
624
708
|
}
|
|
625
709
|
);
|
|
626
710
|
|
|
711
|
+
if (typeof tooltipContainer === 'string') {
|
|
712
|
+
tooltipContainer = document.querySelector(tooltipContainer);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const tooltipLayout = tooltipContainer ? tooltips({
|
|
716
|
+
tooltipSpace: function() {
|
|
717
|
+
return tooltipContainer.getBoundingClientRect();
|
|
718
|
+
}
|
|
719
|
+
}) : [];
|
|
720
|
+
|
|
627
721
|
const extensions = [
|
|
722
|
+
autocompletionConf.of(variablesFacet.of(variables)),
|
|
723
|
+
autocompletion(),
|
|
724
|
+
bracketMatching(),
|
|
725
|
+
changeHandler,
|
|
726
|
+
closeBrackets(),
|
|
727
|
+
indentOnInput(),
|
|
728
|
+
keyHandler,
|
|
628
729
|
keymap.of([
|
|
629
730
|
...defaultKeymap,
|
|
630
731
|
]),
|
|
631
|
-
changeHandler,
|
|
632
|
-
keyHandler,
|
|
633
732
|
language(),
|
|
634
|
-
autocompletion(variables),
|
|
635
|
-
theme,
|
|
636
733
|
linter,
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
lintHandler
|
|
734
|
+
lintHandler,
|
|
735
|
+
tooltipLayout,
|
|
736
|
+
theme
|
|
641
737
|
];
|
|
642
738
|
|
|
643
739
|
if (readOnly) {
|
|
@@ -698,4 +794,15 @@ FeelEditor.prototype.getSelection = function() {
|
|
|
698
794
|
return this._cmEditor.state.selection;
|
|
699
795
|
};
|
|
700
796
|
|
|
797
|
+
/**
|
|
798
|
+
* Set variables to be used for autocompletion.
|
|
799
|
+
* @param {Variable[]} variables
|
|
800
|
+
* @returns {void}
|
|
801
|
+
*/
|
|
802
|
+
FeelEditor.prototype.setVariables = function(variables) {
|
|
803
|
+
this._cmEditor.dispatch({
|
|
804
|
+
effects: autocompletionConf.reconfigure(variablesFacet.of(variables))
|
|
805
|
+
});
|
|
806
|
+
};
|
|
807
|
+
|
|
701
808
|
export { FeelEditor as default };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ var state = require('@codemirror/state');
|
|
|
8
8
|
var view = require('@codemirror/view');
|
|
9
9
|
var langFeel = require('lang-feel');
|
|
10
10
|
var minDom = require('min-dom');
|
|
11
|
+
var feelLint = require('@bpmn-io/feel-lint');
|
|
11
12
|
var highlight = require('@lezer/highlight');
|
|
12
13
|
|
|
13
14
|
// helpers ///////////////////////////////
|
|
@@ -373,9 +374,10 @@ const options = tags.map(tag => autocomplete.snippetCompletion(
|
|
|
373
374
|
label: tag.name,
|
|
374
375
|
type: 'function',
|
|
375
376
|
info: () => {
|
|
376
|
-
const html = minDom.domify(tag.description);
|
|
377
|
+
const html = minDom.domify(`<div class="description">${tag.description}<div>`);
|
|
377
378
|
return html;
|
|
378
|
-
}
|
|
379
|
+
},
|
|
380
|
+
boost: -1
|
|
379
381
|
}
|
|
380
382
|
));
|
|
381
383
|
|
|
@@ -409,7 +411,121 @@ var builtins = context => {
|
|
|
409
411
|
};
|
|
410
412
|
};
|
|
411
413
|
|
|
412
|
-
|
|
414
|
+
/**
|
|
415
|
+
* @type {Facet<import('..').Variable[]>} Variable
|
|
416
|
+
*/
|
|
417
|
+
const variablesFacet = state.Facet.define();
|
|
418
|
+
|
|
419
|
+
var pathExpression = context => {
|
|
420
|
+
const variables = context.state.facet(variablesFacet)[0];
|
|
421
|
+
const nodeBefore = language$1.syntaxTree(context.state).resolve(context.pos, -1);
|
|
422
|
+
|
|
423
|
+
if (!isPathExpression(nodeBefore)) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const expression = findPathExpression(nodeBefore);
|
|
428
|
+
|
|
429
|
+
// if the cursor is directly after the `.`, variable starts at the cursor position
|
|
430
|
+
const from = nodeBefore === expression ? context.pos : nodeBefore.from;
|
|
431
|
+
|
|
432
|
+
const path = getPath(expression, context);
|
|
433
|
+
|
|
434
|
+
let options = variables;
|
|
435
|
+
for (var i = 0; i < path.length - 1; i++) {
|
|
436
|
+
var childVar = options.find(val => val.name === path[i].name);
|
|
437
|
+
|
|
438
|
+
if (!childVar) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// only suggest if variable type matches
|
|
443
|
+
if (
|
|
444
|
+
childVar.isList !== 'optional' &&
|
|
445
|
+
!!childVar.isList !== path[i].isList
|
|
446
|
+
) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
options = childVar.entries;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!options) return;
|
|
454
|
+
|
|
455
|
+
options = options.map(v => ({
|
|
456
|
+
label: v.name,
|
|
457
|
+
type: 'variable',
|
|
458
|
+
info: v.info,
|
|
459
|
+
detail: v.detail
|
|
460
|
+
}));
|
|
461
|
+
|
|
462
|
+
const result = {
|
|
463
|
+
from: from,
|
|
464
|
+
options: options
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
return result;
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
function findPathExpression(node) {
|
|
472
|
+
while (node) {
|
|
473
|
+
if (node.name === 'PathExpression') {
|
|
474
|
+
return node;
|
|
475
|
+
}
|
|
476
|
+
node = node.parent;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// parses the path expression into a list of variable names with type information
|
|
481
|
+
// e.g. foo[0].bar => [ { name: 'foo', isList: true }, { name: 'bar', isList: false } ]
|
|
482
|
+
function getPath(node, context) {
|
|
483
|
+
let path = [];
|
|
484
|
+
|
|
485
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
486
|
+
if (child.name === 'PathExpression') {
|
|
487
|
+
path.push(...getPath(child, context));
|
|
488
|
+
} else if (child.name === 'FilterExpression') {
|
|
489
|
+
path.push(...getFilter(child, context));
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
path.push({
|
|
493
|
+
name: getNodeContent(child, context),
|
|
494
|
+
isList: false
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return path;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function getFilter(node, context) {
|
|
502
|
+
const list = node.firstChild;
|
|
503
|
+
|
|
504
|
+
if (list.name === 'PathExpression') {
|
|
505
|
+
const path = getPath(list, context);
|
|
506
|
+
const last = path[path.length - 1];
|
|
507
|
+
last.isList = true;
|
|
508
|
+
|
|
509
|
+
return path;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return [ {
|
|
513
|
+
name: getNodeContent(list, context),
|
|
514
|
+
isList: true
|
|
515
|
+
} ];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function getNodeContent(node, context) {
|
|
519
|
+
return context.state.sliceDoc(node.from, node.to);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* @type {import('@codemirror/autocomplete').CompletionSource}
|
|
524
|
+
*/
|
|
525
|
+
var variables = context => {
|
|
526
|
+
|
|
527
|
+
const variables = context.state.facet(variablesFacet)[0];
|
|
528
|
+
|
|
413
529
|
const options = variables.map(v => ({
|
|
414
530
|
label: v.name,
|
|
415
531
|
type: 'variable',
|
|
@@ -448,13 +564,14 @@ var variables = variables => context => {
|
|
|
448
564
|
return result;
|
|
449
565
|
};
|
|
450
566
|
|
|
451
|
-
function autocompletion(
|
|
567
|
+
function autocompletion() {
|
|
452
568
|
return [
|
|
453
569
|
autocomplete.autocompletion({
|
|
454
570
|
override: [
|
|
455
|
-
variables
|
|
571
|
+
variables,
|
|
456
572
|
builtins,
|
|
457
|
-
autocomplete.completeFromList(langFeel.snippets)
|
|
573
|
+
autocomplete.completeFromList(langFeel.snippets.map(s => ({ ...s, boost: -1 }))),
|
|
574
|
+
pathExpression
|
|
458
575
|
]
|
|
459
576
|
})
|
|
460
577
|
];
|
|
@@ -464,53 +581,7 @@ function language() {
|
|
|
464
581
|
return new language$1.LanguageSupport(langFeel.feelLanguage, [ ]);
|
|
465
582
|
}
|
|
466
583
|
|
|
467
|
-
|
|
468
|
-
const messages = [];
|
|
469
|
-
|
|
470
|
-
// don't lint if the Editor is empty
|
|
471
|
-
if (editorView.state.doc.length === 0) {
|
|
472
|
-
return messages;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const tree = language$1.syntaxTree(editorView.state);
|
|
476
|
-
|
|
477
|
-
tree.iterate({
|
|
478
|
-
enter: node => {
|
|
479
|
-
if (node.type.isError) {
|
|
480
|
-
|
|
481
|
-
const error = node.toString();
|
|
482
|
-
|
|
483
|
-
/* The error has the pattern [⚠ || ⚠(NodeType)]. The regex extracts the node type from inside the brackets */
|
|
484
|
-
const match = /\((.*?)\)/.exec(error);
|
|
485
|
-
const nodeType = match && match[1];
|
|
486
|
-
|
|
487
|
-
let message;
|
|
488
|
-
|
|
489
|
-
if (nodeType) {
|
|
490
|
-
message = 'unexpected ' + nodeType;
|
|
491
|
-
} else {
|
|
492
|
-
message = 'expression expected';
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
messages.push(
|
|
496
|
-
{
|
|
497
|
-
from: node.from,
|
|
498
|
-
to: node.to,
|
|
499
|
-
severity: 'error',
|
|
500
|
-
message: message,
|
|
501
|
-
source: 'syntaxError'
|
|
502
|
-
}
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
return messages;
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
var syntaxLinter = lint.linter(FeelLinter);
|
|
512
|
-
|
|
513
|
-
var linter = [ syntaxLinter ];
|
|
584
|
+
var linter = [ lint.linter(feelLint.cmFeelLinter()) ];
|
|
514
585
|
|
|
515
586
|
const baseTheme = view.EditorView.theme({
|
|
516
587
|
'& .cm-content': {
|
|
@@ -576,22 +647,35 @@ const syntaxClasses = language$1.syntaxHighlighting(
|
|
|
576
647
|
|
|
577
648
|
var theme = [ baseTheme, highlightTheme, syntaxClasses ];
|
|
578
649
|
|
|
650
|
+
/**
|
|
651
|
+
* @typedef {object} Variable
|
|
652
|
+
* @property {string} name name or key of the variable
|
|
653
|
+
* @property {string} [info] short information about the variable, e.g. type
|
|
654
|
+
* @property {string} [detail] longer description of the variable content
|
|
655
|
+
* @property {boolean} [isList] whether the variable is a list
|
|
656
|
+
* @property {array<Variable>} [schema] array of child variables if the variable is a context or list
|
|
657
|
+
*/
|
|
658
|
+
|
|
659
|
+
const autocompletionConf = new state.Compartment();
|
|
660
|
+
|
|
579
661
|
/**
|
|
580
662
|
* Creates a FEEL editor in the supplied container
|
|
581
663
|
*
|
|
582
664
|
* @param {Object} config
|
|
583
665
|
* @param {DOMNode} config.container
|
|
666
|
+
* @param {DOMNode|String} [config.tooltipContainer]
|
|
584
667
|
* @param {Function} [config.onChange]
|
|
585
668
|
* @param {Function} [config.onKeyDown]
|
|
586
669
|
* @param {Function} [config.onLint]
|
|
587
670
|
* @param {Boolean} [config.readOnly]
|
|
588
671
|
* @param {String} [config.value]
|
|
589
|
-
* @param {
|
|
672
|
+
* @param {Variable[]} [config.variables]
|
|
590
673
|
*
|
|
591
674
|
* @returns {Object} editor
|
|
592
675
|
*/
|
|
593
676
|
function FeelEditor({
|
|
594
677
|
container,
|
|
678
|
+
tooltipContainer,
|
|
595
679
|
onChange = () => {},
|
|
596
680
|
onKeyDown = () => {},
|
|
597
681
|
onLint = () => {},
|
|
@@ -626,20 +710,32 @@ function FeelEditor({
|
|
|
626
710
|
}
|
|
627
711
|
);
|
|
628
712
|
|
|
713
|
+
if (typeof tooltipContainer === 'string') {
|
|
714
|
+
tooltipContainer = document.querySelector(tooltipContainer);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const tooltipLayout = tooltipContainer ? view.tooltips({
|
|
718
|
+
tooltipSpace: function() {
|
|
719
|
+
return tooltipContainer.getBoundingClientRect();
|
|
720
|
+
}
|
|
721
|
+
}) : [];
|
|
722
|
+
|
|
629
723
|
const extensions = [
|
|
724
|
+
autocompletionConf.of(variablesFacet.of(variables)),
|
|
725
|
+
autocompletion(),
|
|
726
|
+
language$1.bracketMatching(),
|
|
727
|
+
changeHandler,
|
|
728
|
+
autocomplete.closeBrackets(),
|
|
729
|
+
language$1.indentOnInput(),
|
|
730
|
+
keyHandler,
|
|
630
731
|
view.keymap.of([
|
|
631
732
|
...commands.defaultKeymap,
|
|
632
733
|
]),
|
|
633
|
-
changeHandler,
|
|
634
|
-
keyHandler,
|
|
635
734
|
language(),
|
|
636
|
-
autocompletion(variables),
|
|
637
|
-
theme,
|
|
638
735
|
linter,
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
lintHandler
|
|
736
|
+
lintHandler,
|
|
737
|
+
tooltipLayout,
|
|
738
|
+
theme
|
|
643
739
|
];
|
|
644
740
|
|
|
645
741
|
if (readOnly) {
|
|
@@ -700,4 +796,15 @@ FeelEditor.prototype.getSelection = function() {
|
|
|
700
796
|
return this._cmEditor.state.selection;
|
|
701
797
|
};
|
|
702
798
|
|
|
799
|
+
/**
|
|
800
|
+
* Set variables to be used for autocompletion.
|
|
801
|
+
* @param {Variable[]} variables
|
|
802
|
+
* @returns {void}
|
|
803
|
+
*/
|
|
804
|
+
FeelEditor.prototype.setVariables = function(variables) {
|
|
805
|
+
this._cmEditor.dispatch({
|
|
806
|
+
effects: autocompletionConf.reconfigure(variablesFacet.of(variables))
|
|
807
|
+
});
|
|
808
|
+
};
|
|
809
|
+
|
|
703
810
|
module.exports = FeelEditor;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bpmn-io/feel-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Editor for FEEL expressions.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -45,17 +45,17 @@
|
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@babel/core": "^7.20.2",
|
|
48
|
-
"@
|
|
49
|
-
"@codemirror/
|
|
50
|
-
"@codemirror/
|
|
51
|
-
"@codemirror/
|
|
52
|
-
"@codemirror/
|
|
53
|
-
"@codemirror/
|
|
54
|
-
"@
|
|
48
|
+
"@bpmn-io/feel-lint": "^0.1.1",
|
|
49
|
+
"@codemirror/autocomplete": "^6.3.2",
|
|
50
|
+
"@codemirror/commands": "^6.1.2",
|
|
51
|
+
"@codemirror/language": "^6.3.1",
|
|
52
|
+
"@codemirror/lint": "^6.1.0",
|
|
53
|
+
"@codemirror/state": "^6.1.4",
|
|
54
|
+
"@codemirror/view": "^6.5.1",
|
|
55
|
+
"@lezer/highlight": "^1.1.2",
|
|
55
56
|
"babel-loader": "^9.1.0",
|
|
56
57
|
"babel-plugin-istanbul": "^6.1.1",
|
|
57
|
-
"lang-feel": "^0.0
|
|
58
|
-
"lezer-feel": "^0.14.1",
|
|
58
|
+
"lang-feel": "^0.1.0",
|
|
59
59
|
"min-dom": "^4.0.1"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"mocha": "^10.0.0",
|
|
82
82
|
"mocha-test-container-support": "^0.2.0",
|
|
83
83
|
"npm-run-all": "^4.1.5",
|
|
84
|
-
"puppeteer": "^19.
|
|
84
|
+
"puppeteer": "^19.3.0",
|
|
85
85
|
"rollup": "^3.3.0",
|
|
86
86
|
"sinon": "^14.0.0",
|
|
87
87
|
"sinon-chai": "^3.7.0",
|