@finos/legend-application-studio 28.13.15 → 28.14.1

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 (36) hide show
  1. package/lib/__lib__/LegendStudioEvent.d.ts +4 -1
  2. package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioEvent.js +4 -0
  4. package/lib/__lib__/LegendStudioEvent.js.map +1 -1
  5. package/lib/components/editor/editor-group/EditorGroup.js +1 -1
  6. package/lib/components/editor/editor-group/EditorGroup.js.map +1 -1
  7. package/lib/components/editor/editor-group/GrammarTextEditor.d.ts.map +1 -1
  8. package/lib/components/editor/editor-group/GrammarTextEditor.js +258 -219
  9. package/lib/components/editor/editor-group/GrammarTextEditor.js.map +1 -1
  10. package/lib/components/workspace-setup/WorkspaceSetup.js +1 -1
  11. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  12. package/lib/index.css +2 -2
  13. package/lib/index.css.map +1 -1
  14. package/lib/package.json +1 -1
  15. package/lib/stores/editor/EditorGraphState.d.ts.map +1 -1
  16. package/lib/stores/editor/EditorGraphState.js +8 -1
  17. package/lib/stores/editor/EditorGraphState.js.map +1 -1
  18. package/lib/stores/editor/GraphEditFormModeState.js +1 -1
  19. package/lib/stores/editor/GraphEditFormModeState.js.map +1 -1
  20. package/lib/stores/editor/GraphEditGrammarModeState.d.ts +4 -0
  21. package/lib/stores/editor/GraphEditGrammarModeState.d.ts.map +1 -1
  22. package/lib/stores/editor/GraphEditGrammarModeState.js +29 -2
  23. package/lib/stores/editor/GraphEditGrammarModeState.js.map +1 -1
  24. package/lib/stores/editor/editor-state/GrammarTextEditorState.d.ts +2 -1
  25. package/lib/stores/editor/editor-state/GrammarTextEditorState.d.ts.map +1 -1
  26. package/lib/stores/editor/editor-state/GrammarTextEditorState.js +5 -2
  27. package/lib/stores/editor/editor-state/GrammarTextEditorState.js.map +1 -1
  28. package/package.json +2 -2
  29. package/src/__lib__/LegendStudioEvent.ts +5 -0
  30. package/src/components/editor/editor-group/EditorGroup.tsx +1 -1
  31. package/src/components/editor/editor-group/GrammarTextEditor.tsx +418 -355
  32. package/src/components/workspace-setup/WorkspaceSetup.tsx +1 -1
  33. package/src/stores/editor/EditorGraphState.ts +12 -0
  34. package/src/stores/editor/GraphEditFormModeState.ts +1 -1
  35. package/src/stores/editor/GraphEditGrammarModeState.ts +49 -1
  36. package/src/stores/editor/editor-state/GrammarTextEditorState.ts +7 -2
@@ -16,21 +16,22 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
16
  */
17
17
  import { useEffect, useState, useRef, useCallback, forwardRef } from 'react';
18
18
  import { observer } from 'mobx-react-lite';
19
- import { editor as monacoEditorAPI, languages as monacoLanguagesAPI, } from 'monaco-editor';
20
- import { ContextMenu, clsx, WordWrapIcon, MoreHorizontalIcon, FoldIcon, UnfoldIcon, PanelContent, MenuContent, MenuContentItem, } from '@finos/legend-art';
19
+ import { editor as monacoEditorAPI, languages as monacoLanguagesAPI, KeyCode, KeyMod, } from 'monaco-editor';
20
+ import { ContextMenu, PanelContent, MenuContent, MenuContentItem, PanelLoadingIndicator, CaretDownIcon, DropdownMenu, MenuContentItemIcon, CheckIcon, MenuContentItemLabel, } from '@finos/legend-art';
21
21
  import { DEFAULT_TAB_SIZE, useApplicationStore, useApplicationNavigationContext, } from '@finos/legend-application';
22
22
  import { disposeCodeEditor, getBaseCodeEditorOptions, resetLineNumberGutterWidth, getCodeEditorValue, normalizeLineEnding, setWarningMarkers, clearMarkers, setErrorMarkers, moveCursorToPosition, CODE_EDITOR_THEME, CODE_EDITOR_LANGUAGE, getInlineSnippetSuggestions, getParserKeywordSuggestions, getParserElementSnippetSuggestions, getSectionParserNameFromLineText, } from '@finos/legend-lego/code-editor';
23
23
  import { CORE_DND_TYPE, } from '../../../stores/editor/utils/DnDUtils.js';
24
24
  import { useDrop } from 'react-dnd';
25
25
  import { flowResult } from 'mobx';
26
26
  import { useEditorStore } from '../EditorStoreProvider.js';
27
- import { hasWhiteSpace, isNonNullable } from '@finos/legend-shared';
28
- import { PARSER_SECTION_MARKER, PURE_CONNECTION_NAME, PURE_ELEMENT_NAME, PURE_PARSER, } from '@finos/legend-graph';
27
+ import { LogEvent, assertErrorThrown, assertTrue, hasWhiteSpace, isNonNullable, } from '@finos/legend-shared';
28
+ import { ELEMENT_PATH_DELIMITER, PARSER_SECTION_MARKER, PURE_CONNECTION_NAME, PURE_ELEMENT_NAME, PURE_PARSER, isValidFullPath, } from '@finos/legend-graph';
29
29
  import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../../__lib__/LegendStudioDocumentation.js';
30
30
  import { BLANK_CLASS_SNIPPET, CLASS_WITH_CONSTRAINT_SNIPPET, CLASS_WITH_INHERITANCE_SNIPPET, CLASS_WITH_PROPERTY_SNIPPET, DATA_WITH_EXTERNAL_FORMAT_SNIPPET, DATA_WITH_MODEL_STORE_SNIPPET, createDataElementSnippetWithEmbeddedDataSuggestionSnippet, SIMPLE_PROFILE_SNIPPET, SIMPLE_ENUMERATION_SNIPPET, SIMPLE_ASSOCIATION_SNIPPET, SIMPLE_MEASURE_SNIPPET, BLANK_FUNCTION_SNIPPET, SIMPLE_FUNCTION_SNIPPET, SIMPLE_RUNTIME_SNIPPET, JSON_MODEL_CONNECTION_SNIPPET, XML_MODEL_CONNECTION_SNIPPET, MODEL_CHAIN_CONNECTION_SNIPPET, RELATIONAL_DATABASE_CONNECTION_SNIPPET, BLANK_RELATIONAL_DATABASE_SNIPPET, SIMPLE_GENERATION_SPECIFICATION_SNIPPET, BLANK_SERVICE_SNIPPET, SERVICE_WITH_SINGLE_EXECUTION_SNIPPET, SERVICE_WITH_MULTI_EXECUTION_SNIPPET, BLANK_MAPPING_SNIPPET, MAPPING_WITH_M2M_CLASS_MAPPING_SNIPPET, MAPPING_WITH_ENUMERATION_MAPPING_SNIPPET, MAPPING_WITH_RELATIONAL_CLASS_MAPPING_SNIPPET, POST_PROCESSOR_RELATIONAL_DATABASE_CONNECTION_SNIPPET, createConnectionSnippetWithPostProcessorSuggestionSnippet, } from '../../../__lib__/LegendStudioCodeSnippet.js';
31
31
  import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../__lib__/LegendStudioApplicationNavigationContext.js';
32
32
  import { LEGEND_STUDIO_SETTING_KEY } from '../../../__lib__/LegendStudioSetting.js';
33
- import { GraphEditGrammarModeState } from '../../../stores/editor/GraphEditGrammarModeState.js';
33
+ import { GRAMMAR_MODE_EDITOR_ACTION, GraphEditGrammarModeState, } from '../../../stores/editor/GraphEditGrammarModeState.js';
34
+ import { LEGEND_STUDIO_APP_EVENT } from '../../../__lib__/LegendStudioEvent.js';
34
35
  export const GrammarTextEditorHeaderTabContextMenu = observer(forwardRef(function GrammarTextEditorHeaderTabContextMenu(props, ref) {
35
36
  const editorStore = useEditorStore();
36
37
  const applicationStore = useApplicationStore();
@@ -451,6 +452,55 @@ const collectParserElementSnippetSuggestions = (editorStore, parserKeyword) => {
451
452
  }
452
453
  return [];
453
454
  };
455
+ const resolveElementPathFromCurrentPosition = (_editor, grammarModeState) => {
456
+ let elementPath = '';
457
+ try {
458
+ const model = _editor.getModel();
459
+ let position = _editor.getPosition();
460
+ let maxWords = 0;
461
+ if (model) {
462
+ while (position && maxWords < 30) {
463
+ const currentWord = model.getWordAtPosition(position);
464
+ const lineNumber = position.lineNumber;
465
+ position = null;
466
+ maxWords += 1;
467
+ if (currentWord) {
468
+ const wordStartPost = {
469
+ lineNumber: lineNumber,
470
+ column: currentWord.startColumn,
471
+ };
472
+ const startPost = model.modifyPosition(wordStartPost, -2);
473
+ elementPath = currentWord.word + elementPath;
474
+ const pathDelimiterRange = {
475
+ startLineNumber: startPost.lineNumber,
476
+ startColumn: startPost.column,
477
+ endLineNumber: wordStartPost.lineNumber,
478
+ endColumn: wordStartPost.column,
479
+ };
480
+ const packageRange = model.getValueInRange(model.validateRange(pathDelimiterRange));
481
+ if (packageRange === ELEMENT_PATH_DELIMITER) {
482
+ elementPath = packageRange + elementPath;
483
+ position = model.modifyPosition(startPost, -1);
484
+ }
485
+ }
486
+ }
487
+ }
488
+ assertTrue(isValidFullPath(elementPath), `Unable to go to element definition. Not valid element path: ${elementPath}`);
489
+ return elementPath;
490
+ }
491
+ catch (error) {
492
+ assertErrorThrown(error);
493
+ grammarModeState.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.TEXT_MODE_ACTION_KEYBOARD_SHORTCUT_GO_TO_DEFINITION__ERROR), error);
494
+ }
495
+ return undefined;
496
+ };
497
+ const goToElement = (_editor, grammarModeState) => {
498
+ grammarModeState.editorStore.applicationStore.logService.info(LogEvent.create(LEGEND_STUDIO_APP_EVENT.TEXT_MODE_ACTION_KEYBOARD_SHORTCUT_GO_TO_DEFINITION__LAUNCH));
499
+ const elementPath = resolveElementPathFromCurrentPosition(_editor, grammarModeState);
500
+ if (elementPath) {
501
+ flowResult(grammarModeState.goToElement(elementPath)).catch(grammarModeState.editorStore.applicationStore.alertUnhandledError);
502
+ }
503
+ };
454
504
  export const GrammarTextEditor = observer(() => {
455
505
  const [editor, setEditor] = useState();
456
506
  const editorStore = useEditorStore();
@@ -458,13 +508,16 @@ export const GrammarTextEditor = observer(() => {
458
508
  const grammarModeState = editorStore.getGraphEditorMode(GraphEditGrammarModeState);
459
509
  const grammarTextEditorState = grammarModeState.grammarTextEditorState;
460
510
  const error = editorStore.graphState.error;
511
+ const warnings = editorStore.graphState.warnings;
461
512
  const [elementsFolded, setFoldingElements] = useState(false);
462
513
  const forcedCursorPosition = grammarTextEditorState.forcedCursorPosition;
514
+ const wordWrapOtion = grammarTextEditorState.wordWrapOtion;
463
515
  const value = normalizeLineEnding(grammarTextEditorState.graphGrammarText);
464
516
  const textEditorRef = useRef(null);
465
517
  const hoverProviderDisposer = useRef(undefined);
466
518
  const suggestionProviderDisposer = useRef(undefined);
467
519
  const leaveTextMode = applicationStore.guardUnhandledError(() => flowResult(editorStore.toggleTextMode()));
520
+ const globalCompile = applicationStore.guardUnhandledError(() => flowResult(grammarModeState.globalCompile()));
468
521
  const toggleWordWrap = () => {
469
522
  grammarTextEditorState.setWrapText(!grammarTextEditorState.wrapText);
470
523
  editorStore.applicationStore.settingService.persistValue(LEGEND_STUDIO_SETTING_KEY.EDITOR_WRAP_TEXT, grammarTextEditorState.wrapText);
@@ -477,18 +530,36 @@ export const GrammarTextEditor = observer(() => {
477
530
  language: CODE_EDITOR_LANGUAGE.PURE,
478
531
  theme: CODE_EDITOR_THEME.DEFAULT_DARK,
479
532
  renderValidationDecorations: 'on',
533
+ wordWrap: grammarTextEditorState.wordWrapOtion,
534
+ readOnly: editorStore.editorMode.disableEditing,
480
535
  });
481
536
  _editor.onDidChangeModelContent(() => {
482
537
  grammarTextEditorState.setGraphGrammarText(getCodeEditorValue(_editor));
483
538
  clearMarkers();
484
539
  // NOTE: we can technically can reset the current element label regex string here
485
540
  // but if we do that on first load, the cursor will not jump to the current element
486
- // also, it's better to place that logic in an effect that watches for the regex string
541
+ // also, it's better to place that logic in an effect that watches for the regex string.
542
+ // this is done by watching `forcedCursorPosition` in the useEffect
487
543
  });
488
544
  _editor.focus(); // focus on the editor initially
545
+ _editor.getModel()?.updateOptions({ tabSize: DEFAULT_TAB_SIZE });
546
+ _editor.addAction({
547
+ id: GRAMMAR_MODE_EDITOR_ACTION.GO_TO_ELEMENT_DEFINITION,
548
+ label: 'Go To Element',
549
+ keybindings: [KeyMod.CtrlCmd | KeyCode.KeyB],
550
+ run: (ed) => {
551
+ goToElement(ed, grammarModeState);
552
+ },
553
+ });
489
554
  setEditor(_editor);
490
555
  }
491
- }, [editorStore, applicationStore, editor, grammarTextEditorState]);
556
+ }, [
557
+ editorStore,
558
+ applicationStore,
559
+ editor,
560
+ grammarTextEditorState,
561
+ grammarModeState,
562
+ ]);
492
563
  // Drag and Drop
493
564
  const extraElementDragTypes = editorStore.pluginManager
494
565
  .getApplicationPlugins()
@@ -529,150 +600,112 @@ export const GrammarTextEditor = observer(() => {
529
600
  if (currentValue !== value) {
530
601
  editor.setValue(value);
531
602
  }
532
- editor.updateOptions({
533
- wordWrap: grammarTextEditorState.wrapText ? 'on' : 'off',
534
- });
535
603
  resetLineNumberGutterWidth(editor);
536
604
  const editorModel = editor.getModel();
537
605
  if (editorModel) {
538
- editorModel.updateOptions({ tabSize: DEFAULT_TAB_SIZE });
539
- if (error?.sourceInformation &&
540
- // We should only calculate if problems are stale if there is an error/warning as
541
- // calculating staleness takes time
542
- !editorStore.graphState.areProblemsStale) {
543
- setErrorMarkers(editorModel, [
544
- {
545
- message: error.message,
546
- startLineNumber: error.sourceInformation.startLine,
547
- startColumn: error.sourceInformation.startColumn,
548
- endLineNumber: error.sourceInformation.endLine,
549
- endColumn: error.sourceInformation.endColumn,
550
- },
551
- ]);
552
- }
553
- if (editorStore.graphState.warnings.length &&
554
- // We should only calculate if problems are stale if there is an error/warning as
555
- // calculating staleness takes time
556
- !editorStore.graphState.areProblemsStale) {
557
- setWarningMarkers(editorModel, editorStore.graphState.warnings
558
- .map((warning) => {
559
- if (!warning.sourceInformation) {
560
- return undefined;
606
+ // hover
607
+ hoverProviderDisposer.current?.dispose();
608
+ hoverProviderDisposer.current = monacoLanguagesAPI.registerHoverProvider(CODE_EDITOR_LANGUAGE.PURE, {
609
+ provideHover: (model, position) => {
610
+ const currentWord = model.getWordAtPosition(position);
611
+ if (!currentWord) {
612
+ return { contents: [] };
561
613
  }
562
- return {
563
- message: warning.message,
564
- startLineNumber: warning.sourceInformation.startLine,
565
- startColumn: warning.sourceInformation.startColumn,
566
- endLineNumber: warning.sourceInformation.endLine,
567
- endColumn: warning.sourceInformation.endColumn,
614
+ // show documention for parser section
615
+ const lineTextIncludingWordRange = {
616
+ startLineNumber: position.lineNumber,
617
+ startColumn: 1,
618
+ endLineNumber: position.lineNumber,
619
+ endColumn: currentWord.endColumn,
568
620
  };
569
- })
570
- .filter(isNonNullable));
571
- }
572
- }
573
- // Disable editing if user is in viewer mode
574
- editor.updateOptions({ readOnly: editorStore.editorMode.disableEditing });
575
- // hover
576
- hoverProviderDisposer.current?.dispose();
577
- hoverProviderDisposer.current = monacoLanguagesAPI.registerHoverProvider(CODE_EDITOR_LANGUAGE.PURE, {
578
- provideHover: (model, position) => {
579
- const currentWord = model.getWordAtPosition(position);
580
- if (!currentWord) {
581
- return { contents: [] };
582
- }
583
- // show documention for parser section
584
- const lineTextIncludingWordRange = {
585
- startLineNumber: position.lineNumber,
586
- startColumn: 1,
587
- endLineNumber: position.lineNumber,
588
- endColumn: currentWord.endColumn,
589
- };
590
- const lineTextIncludingWord = model.getValueInRange(lineTextIncludingWordRange);
591
- // NOTE: we don't need to trim here since the leading whitespace in front of
592
- // the section header is considered invalid syntax in the grammar
593
- if (!hasWhiteSpace(lineTextIncludingWord) &&
594
- lineTextIncludingWord.startsWith(PARSER_SECTION_MARKER)) {
595
- const parserKeyword = lineTextIncludingWord.substring(PARSER_SECTION_MARKER.length);
596
- const doc = getParserDocumetation(editorStore, parserKeyword);
597
- if (doc) {
598
- return {
599
- range: lineTextIncludingWordRange,
600
- contents: [
601
- doc.markdownText
602
- ? {
603
- value: doc.markdownText.value,
604
- }
605
- : undefined,
606
- doc.url
607
- ? {
608
- value: `[See documentation](${doc.url})`,
609
- }
610
- : undefined,
611
- ].filter(isNonNullable),
612
- };
621
+ const lineTextIncludingWord = model.getValueInRange(lineTextIncludingWordRange);
622
+ // NOTE: we don't need to trim here since the leading whitespace in front of
623
+ // the section header is considered invalid syntax in the grammar
624
+ if (!hasWhiteSpace(lineTextIncludingWord) &&
625
+ lineTextIncludingWord.startsWith(PARSER_SECTION_MARKER)) {
626
+ const parserKeyword = lineTextIncludingWord.substring(PARSER_SECTION_MARKER.length);
627
+ const doc = getParserDocumetation(editorStore, parserKeyword);
628
+ if (doc) {
629
+ return {
630
+ range: lineTextIncludingWordRange,
631
+ contents: [
632
+ doc.markdownText
633
+ ? {
634
+ value: doc.markdownText.value,
635
+ }
636
+ : undefined,
637
+ doc.url
638
+ ? {
639
+ value: `[See documentation](${doc.url})`,
640
+ }
641
+ : undefined,
642
+ ].filter(isNonNullable),
643
+ };
644
+ }
613
645
  }
614
- }
615
- // show documentation for parser element
616
- const textUntilPosition = model.getValueInRange({
617
- startLineNumber: 1,
618
- startColumn: 1,
619
- endLineNumber: position.lineNumber,
620
- endColumn: position.column,
621
- });
622
- const allParserSectionHeaders =
623
- // NOTE: since `###Pure` is implicitly considered as the first section, we prepend it to the text
624
- `${PARSER_SECTION_MARKER}${PURE_PARSER.PURE}\n${textUntilPosition}`
625
- .split('\n')
626
- .filter((line) => line.startsWith(PARSER_SECTION_MARKER));
627
- const currentSectionParserKeyword = getSectionParserNameFromLineText(allParserSectionHeaders[allParserSectionHeaders.length - 1] ?? '');
628
- if (currentSectionParserKeyword) {
629
- const doc = getParserElementDocumentation(editorStore, currentSectionParserKeyword, currentWord.word);
630
- if (doc) {
631
- return {
632
- range: {
633
- startLineNumber: position.lineNumber,
634
- startColumn: currentWord.startColumn,
635
- endLineNumber: position.lineNumber,
636
- endColumn: currentWord.endColumn,
637
- },
638
- contents: [
639
- doc.markdownText
640
- ? {
641
- value: doc.markdownText.value,
642
- }
643
- : undefined,
644
- doc.url
645
- ? {
646
- value: `[See documentation](${doc.url})`,
647
- }
648
- : undefined,
649
- ].filter(isNonNullable),
650
- };
646
+ // show documentation for parser element
647
+ const textUntilPosition = model.getValueInRange({
648
+ startLineNumber: 1,
649
+ startColumn: 1,
650
+ endLineNumber: position.lineNumber,
651
+ endColumn: position.column,
652
+ });
653
+ const allParserSectionHeaders =
654
+ // NOTE: since `###Pure` is implicitly considered as the first section, we prepend it to the text
655
+ `${PARSER_SECTION_MARKER}${PURE_PARSER.PURE}\n${textUntilPosition}`
656
+ .split('\n')
657
+ .filter((line) => line.startsWith(PARSER_SECTION_MARKER));
658
+ const currentSectionParserKeyword = getSectionParserNameFromLineText(allParserSectionHeaders[allParserSectionHeaders.length - 1] ??
659
+ '');
660
+ if (currentSectionParserKeyword) {
661
+ const doc = getParserElementDocumentation(editorStore, currentSectionParserKeyword, currentWord.word);
662
+ if (doc) {
663
+ return {
664
+ range: {
665
+ startLineNumber: position.lineNumber,
666
+ startColumn: currentWord.startColumn,
667
+ endLineNumber: position.lineNumber,
668
+ endColumn: currentWord.endColumn,
669
+ },
670
+ contents: [
671
+ doc.markdownText
672
+ ? {
673
+ value: doc.markdownText.value,
674
+ }
675
+ : undefined,
676
+ doc.url
677
+ ? {
678
+ value: `[See documentation](${doc.url})`,
679
+ }
680
+ : undefined,
681
+ ].filter(isNonNullable),
682
+ };
683
+ }
651
684
  }
652
- }
653
- return { contents: [] };
654
- },
655
- });
656
- // suggestion
657
- suggestionProviderDisposer.current?.dispose();
658
- suggestionProviderDisposer.current =
659
- monacoLanguagesAPI.registerCompletionItemProvider(CODE_EDITOR_LANGUAGE.PURE, {
660
- // NOTE: we need to specify this to show suggestions for section
661
- // because by default, only alphanumeric characters trigger completion item provider
662
- // See https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.CompletionContext.html#triggerCharacter
663
- // See https://github.com/microsoft/monaco-editor/issues/2530#issuecomment-861757198
664
- triggerCharacters: ['#'],
665
- provideCompletionItems: (model, position) => {
666
- let suggestions = [];
667
- // suggestions for parser keyword
668
- suggestions = suggestions.concat(getParserKeywordSuggestions(position, model, collectParserKeywordSuggestions(editorStore)));
669
- // suggestions for parser element snippets
670
- suggestions = suggestions.concat(getParserElementSnippetSuggestions(position, model, (parserName) => collectParserElementSnippetSuggestions(editorStore, parserName)));
671
- // inline code snippet suggestions
672
- suggestions = suggestions.concat(getInlineSnippetSuggestions(position, model));
673
- return { suggestions };
685
+ return { contents: [] };
674
686
  },
675
687
  });
688
+ // suggestion
689
+ suggestionProviderDisposer.current?.dispose();
690
+ suggestionProviderDisposer.current =
691
+ monacoLanguagesAPI.registerCompletionItemProvider(CODE_EDITOR_LANGUAGE.PURE, {
692
+ // NOTE: we need to specify this to show suggestions for section
693
+ // because by default, only alphanumeric characters trigger completion item provider
694
+ // See https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.CompletionContext.html#triggerCharacter
695
+ // See https://github.com/microsoft/monaco-editor/issues/2530#issuecomment-861757198
696
+ triggerCharacters: ['#'],
697
+ provideCompletionItems: (model, position) => {
698
+ let suggestions = [];
699
+ // suggestions for parser keyword
700
+ suggestions = suggestions.concat(getParserKeywordSuggestions(position, model, collectParserKeywordSuggestions(editorStore)));
701
+ // suggestions for parser element snippets
702
+ suggestions = suggestions.concat(getParserElementSnippetSuggestions(position, model, (parserName) => collectParserElementSnippetSuggestions(editorStore, parserName)));
703
+ // inline code snippet suggestions
704
+ suggestions = suggestions.concat(getInlineSnippetSuggestions(position, model));
705
+ return { suggestions };
706
+ },
707
+ });
708
+ }
676
709
  }
677
710
  function toggleAutoFoldingElements() {
678
711
  const autoFoldingElements = editorStore.pluginManager
@@ -680,37 +713,32 @@ export const GrammarTextEditor = observer(() => {
680
713
  .flatMap((plugin) => plugin.getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
681
714
  []);
682
715
  const foldingClass = editor?.getContribution('editor.contrib.folding');
683
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
684
- foldingClass.getFoldingModel().then((foldingModel) => {
685
- const model = editor?.getModel();
686
- const toggleFoldingLines = [];
687
- model?.getLinesContent().forEach((line, j) => {
688
- autoFoldingElements.forEach((elementName) => {
689
- if (line.match(new RegExp(`^${elementName}`))) {
690
- toggleFoldingLines.push(j + 2);
691
- }
716
+ if (editor && foldingClass) {
717
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
718
+ foldingClass.getFoldingModel().then((foldingModel) => {
719
+ const model = editor.getModel();
720
+ const toggleFoldingLines = [];
721
+ model?.getLinesContent().forEach((line, j) => {
722
+ autoFoldingElements.forEach((elementName) => {
723
+ if (line.match(new RegExp(`^${elementName}`))) {
724
+ toggleFoldingLines.push(j + 2);
725
+ }
726
+ });
692
727
  });
693
- });
694
- let allElementsFolded = true;
695
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
696
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
697
- if (foldingModel._regions.isCollapsed(foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
698
- .regionIndex) === false) {
699
- allElementsFolded = false;
700
- }
701
- }
702
- });
703
- setFoldingElements(!allElementsFolded);
704
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
705
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
706
- if (foldingModel._regions.isCollapsed(foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
707
- .regionIndex) !== elementsFolded) {
708
- foldingModel.toggleCollapseState(foldingModel.getAllRegionsAtLine(foldingLineRegion));
728
+ const toFold = !elementsFolded;
729
+ toggleFoldingLines.forEach((foldingLineRegion, i) => {
730
+ if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
731
+ if (foldingModel._regions.isCollapsed(foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
732
+ .regionIndex) !== toFold) {
733
+ foldingModel.toggleCollapseState(foldingModel.getAllRegionsAtLine(foldingLineRegion));
734
+ }
709
735
  }
710
- }
736
+ });
737
+ setFoldingElements(toFold);
711
738
  });
712
- });
739
+ }
713
740
  }
741
+ // below use effects watch over `forcedCursorPosition`, `wordWrapOtion`, `error`, `warnings` and reset them to the editor as needed
714
742
  useEffect(() => {
715
743
  if (editor && forcedCursorPosition) {
716
744
  moveCursorToPosition(editor, forcedCursorPosition);
@@ -718,61 +746,72 @@ export const GrammarTextEditor = observer(() => {
718
746
  }, [editor, forcedCursorPosition]);
719
747
  useEffect(() => {
720
748
  if (editor) {
721
- const autoFoldingElements = editorStore.pluginManager
722
- .getApplicationPlugins()
723
- .flatMap((plugin) => plugin.getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
724
- []);
725
- const foldingClass = editor.getContribution('editor.contrib.folding');
726
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
727
- foldingClass.getFoldingModel().then((foldingModel) => {
728
- foldingModel.onDidChange(() => {
729
- const model = editor.getModel();
730
- const toggleFoldingLines = [];
731
- model?.getLinesContent().forEach((line, j) => {
732
- autoFoldingElements.forEach((elementName) => {
733
- if (line.match(new RegExp(`^${elementName}`))) {
734
- toggleFoldingLines.push(j + 2);
735
- }
736
- });
737
- });
738
- let allElementsFolded = true;
739
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
740
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
741
- if (foldingModel._regions.isCollapsed(foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
742
- .regionIndex) === false) {
743
- allElementsFolded = false;
744
- }
745
- }
746
- });
747
- setFoldingElements(!allElementsFolded);
748
- });
749
+ editor.updateOptions({
750
+ wordWrap: wordWrapOtion,
749
751
  });
750
752
  }
751
- });
753
+ }, [editor, wordWrapOtion]);
754
+ useEffect(() => {
755
+ const editorModel = editor?.getModel();
756
+ if (editorModel && (error?.sourceInformation || warnings.length)) {
757
+ if (error?.sourceInformation) {
758
+ setErrorMarkers(editorModel, [
759
+ {
760
+ message: error.message,
761
+ startLineNumber: error.sourceInformation.startLine,
762
+ startColumn: error.sourceInformation.startColumn,
763
+ endLineNumber: error.sourceInformation.endLine,
764
+ endColumn: error.sourceInformation.endColumn,
765
+ },
766
+ ]);
767
+ }
768
+ if (warnings.length) {
769
+ setWarningMarkers(editorModel, warnings
770
+ .map((warning) => {
771
+ if (!warning.sourceInformation) {
772
+ return undefined;
773
+ }
774
+ return {
775
+ message: warning.message,
776
+ startLineNumber: warning.sourceInformation.startLine,
777
+ startColumn: warning.sourceInformation.startColumn,
778
+ endLineNumber: warning.sourceInformation.endLine,
779
+ endColumn: warning.sourceInformation.endColumn,
780
+ };
781
+ })
782
+ .filter(isNonNullable));
783
+ }
784
+ }
785
+ }, [editor, error, warnings]);
786
+ // first load with grammar. auto fold element sections
752
787
  useEffect(() => {
753
788
  if (editor) {
789
+ const model = editor.getModel();
754
790
  const autoFoldingElements = editorStore.pluginManager
755
791
  .getApplicationPlugins()
756
792
  .flatMap((plugin) => plugin.getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
757
793
  []);
758
794
  const foldingClass = editor.getContribution('editor.contrib.folding');
759
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
760
- foldingClass
761
- .getFoldingModel()
762
- .then((foldingModel) => {
763
- const model = editor.getModel();
764
- const foldingLines = [];
765
- model?.getLinesContent().forEach((line, j) => {
766
- autoFoldingElements.forEach((elementName) => {
767
- if (line.match(new RegExp(`^${elementName}`))) {
768
- foldingLines.push(j + 2);
769
- }
770
- });
771
- });
772
- foldingLines.forEach((foldingLineRegion, i) => {
773
- foldingModel.toggleCollapseState(foldingModel.getAllRegionsAtLine(foldingLineRegion));
795
+ if (foldingClass && model) {
796
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
797
+ foldingClass.getFoldingModel().then((foldingModel) => {
798
+ if (foldingModel) {
799
+ const elementLinesToBeFolded = [];
800
+ model.getLinesContent().forEach((line, idx) => {
801
+ autoFoldingElements.forEach((elementName) => {
802
+ if (line.match(new RegExp(`^${elementName}`))) {
803
+ elementLinesToBeFolded.push(idx + 2);
804
+ }
805
+ });
806
+ });
807
+ elementLinesToBeFolded.forEach((lineToBeFolded) => {
808
+ const regionToFold = foldingModel.getAllRegionsAtLine(lineToBeFolded);
809
+ foldingModel.toggleCollapseState(regionToFold);
810
+ });
811
+ setFoldingElements(true);
812
+ }
774
813
  });
775
- });
814
+ }
776
815
  }
777
816
  }, [editor, editorStore.pluginManager]);
778
817
  // NOTE: dispose the editor to prevent potential memory-leak
@@ -785,10 +824,10 @@ export const GrammarTextEditor = observer(() => {
785
824
  suggestionProviderDisposer.current?.dispose();
786
825
  }, [editor]);
787
826
  useApplicationNavigationContext(LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.TEXT_MODE_EDITOR);
788
- return (_jsxs("div", { className: "panel editor-group", children: [_jsxs("div", { className: "panel__header editor-group__header", children: [_jsxs("div", { className: "editor-group__header__tabs", children: [_jsx("div", { className: "editor-group__text-mode__tab", children: _jsx("button", { className: "editor-group__text-mode__tab__label", disabled: editorStore.graphState.isApplicationLeavingGraphEditMode, onClick: leaveTextMode, tabIndex: -1, title: "Click to exit text mode and go back to form mode", children: _jsx(MoreHorizontalIcon, {}) }) }), _jsx(ContextMenu, { className: "editor-group__text-mode__tab editor-group__text-mode__tab--active", content: _jsx(GrammarTextEditorHeaderTabContextMenu, {}), children: _jsx("div", { className: "editor-group__text-mode__tab__label", children: grammarModeState.headerLabel }) })] }), _jsxs("div", { className: "editor-group__header__actions", children: [_jsx("button", { className: clsx('editor-group__header__action', {
789
- 'editor-group__header__action--active': grammarTextEditorState.wrapText,
790
- }), onClick: toggleWordWrap, tabIndex: -1, title: `[${grammarTextEditorState.wrapText ? 'on' : 'off'}] Toggle word wrap`, children: _jsx(WordWrapIcon, { className: "editor-group__icon__word-wrap" }) }), _jsxs("button", { className: clsx('editor-group__header__action', {
791
- 'editor-group__header__action--active': elementsFolded,
792
- }), onClick: toggleAutoFoldingElements, tabIndex: -1, title: `${!elementsFolded ? 'Unfold' : 'Fold'} auto-folding elements`, children: [!elementsFolded && (_jsx(UnfoldIcon, { className: "editor-group__icon__word-wrap" })), elementsFolded && (_jsx(FoldIcon, { className: "editor-group__icon__word-wrap" }))] })] })] }), _jsx(PanelContent, { className: "editor-group__content", children: _jsx("div", { className: "code-editor__container", children: _jsx("div", { className: "code-editor__body", ref: textEditorRef }) }) })] }));
827
+ return (_jsxs("div", { className: "panel editor-group", children: [_jsxs("div", { className: "panel__header editor-group__header", children: [_jsx("div", { className: "editor-group__header__tabs", children: _jsx(ContextMenu, { className: "editor-group__text-mode__tab editor-group__text-mode__tab--active", content: _jsx(GrammarTextEditorHeaderTabContextMenu, {}), children: _jsx("div", { className: "editor-group__text-mode__tab__label", children: grammarModeState.headerLabel }) }) }), _jsxs("div", { className: "editor-group__header__actions", children: [_jsx("div", { className: "editor-group__text-mode__action", children: _jsx("button", { title: "Compile (F9)", onClick: globalCompile, className: "editor-group__text-mode-btn btn--dark", children: "Compile" }) }), _jsx("div", { className: "editor-group__text-mode__action", children: _jsx("button", { title: "Click to exit text mode and go back to form mode (F8)", onClick: leaveTextMode, className: "editor-group__text-mode-btn btn--dark", children: "Exit Text Mode" }) }), _jsx("div", { className: "query-builder__header__actions", children: _jsxs(DropdownMenu, { className: "query-builder__header__advanced-dropdown", title: "Show Advanced Menu...", content: _jsxs(MenuContent, { children: [_jsxs(MenuContentItem, { onClick: toggleWordWrap, children: [_jsx(MenuContentItemIcon, { children: grammarTextEditorState.wrapText ? _jsx(CheckIcon, {}) : null }), _jsx(MenuContentItemLabel, { children: "Wrap Overflowing Words" })] }), _jsxs(MenuContentItem, { onClick: toggleAutoFoldingElements, children: [_jsx(MenuContentItemIcon, { children: elementsFolded ? _jsx(CheckIcon, {}) : null }), _jsx(MenuContentItemLabel, { children: "Auto Fold Elements" })] })] }), menuProps: {
828
+ anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
829
+ transformOrigin: { vertical: 'top', horizontal: 'right' },
830
+ elevation: 7,
831
+ }, children: [_jsx("div", { className: "query-builder__header__advanced-dropdown__label", children: "Advanced" }), _jsx(CaretDownIcon, { className: "query-builder__header__advanced-dropdown__icon" })] }) })] })] }), _jsxs(PanelContent, { className: "editor-group__content", children: [_jsx(PanelLoadingIndicator, { isLoading: editorStore.graphState.isRunningGlobalCompile }), _jsx("div", { className: "code-editor__container", children: _jsx("div", { className: "code-editor__body", ref: textEditorRef }) })] })] }));
793
832
  });
794
833
  //# sourceMappingURL=GrammarTextEditor.js.map