@finos/legend-application-studio 28.13.14 → 28.14.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 (40) 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 -215
  9. package/lib/components/editor/editor-group/GrammarTextEditor.js.map +1 -1
  10. package/lib/components/workspace-setup/WorkspaceSetup.js +7 -7
  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/EditorStore.d.ts.map +1 -1
  19. package/lib/stores/editor/EditorStore.js +8 -2
  20. package/lib/stores/editor/EditorStore.js.map +1 -1
  21. package/lib/stores/editor/GraphEditFormModeState.js +1 -1
  22. package/lib/stores/editor/GraphEditFormModeState.js.map +1 -1
  23. package/lib/stores/editor/GraphEditGrammarModeState.d.ts +4 -0
  24. package/lib/stores/editor/GraphEditGrammarModeState.d.ts.map +1 -1
  25. package/lib/stores/editor/GraphEditGrammarModeState.js +29 -2
  26. package/lib/stores/editor/GraphEditGrammarModeState.js.map +1 -1
  27. package/lib/stores/editor/editor-state/GrammarTextEditorState.d.ts +2 -1
  28. package/lib/stores/editor/editor-state/GrammarTextEditorState.d.ts.map +1 -1
  29. package/lib/stores/editor/editor-state/GrammarTextEditorState.js +5 -2
  30. package/lib/stores/editor/editor-state/GrammarTextEditorState.js.map +1 -1
  31. package/package.json +4 -4
  32. package/src/__lib__/LegendStudioEvent.ts +5 -0
  33. package/src/components/editor/editor-group/EditorGroup.tsx +1 -1
  34. package/src/components/editor/editor-group/GrammarTextEditor.tsx +418 -351
  35. package/src/components/workspace-setup/WorkspaceSetup.tsx +15 -15
  36. package/src/stores/editor/EditorGraphState.ts +12 -0
  37. package/src/stores/editor/EditorStore.ts +16 -2
  38. package/src/stores/editor/GraphEditFormModeState.ts +1 -1
  39. package/src/stores/editor/GraphEditGrammarModeState.ts +49 -1
  40. 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,146 +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 (!editorStore.graphState.areProblemsStale &&
540
- error?.sourceInformation) {
541
- setErrorMarkers(editorModel, [
542
- {
543
- message: error.message,
544
- startLineNumber: error.sourceInformation.startLine,
545
- startColumn: error.sourceInformation.startColumn,
546
- endLineNumber: error.sourceInformation.endLine,
547
- endColumn: error.sourceInformation.endColumn,
548
- },
549
- ]);
550
- }
551
- if (!editorStore.graphState.areProblemsStale &&
552
- editorStore.graphState.warnings.length) {
553
- setWarningMarkers(editorModel, editorStore.graphState.warnings
554
- .map((warning) => {
555
- if (!warning.sourceInformation) {
556
- 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: [] };
557
613
  }
558
- return {
559
- message: warning.message,
560
- startLineNumber: warning.sourceInformation.startLine,
561
- startColumn: warning.sourceInformation.startColumn,
562
- endLineNumber: warning.sourceInformation.endLine,
563
- 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,
564
620
  };
565
- })
566
- .filter(isNonNullable));
567
- }
568
- }
569
- // Disable editing if user is in viewer mode
570
- editor.updateOptions({ readOnly: editorStore.editorMode.disableEditing });
571
- // hover
572
- hoverProviderDisposer.current?.dispose();
573
- hoverProviderDisposer.current = monacoLanguagesAPI.registerHoverProvider(CODE_EDITOR_LANGUAGE.PURE, {
574
- provideHover: (model, position) => {
575
- const currentWord = model.getWordAtPosition(position);
576
- if (!currentWord) {
577
- return { contents: [] };
578
- }
579
- // show documention for parser section
580
- const lineTextIncludingWordRange = {
581
- startLineNumber: position.lineNumber,
582
- startColumn: 1,
583
- endLineNumber: position.lineNumber,
584
- endColumn: currentWord.endColumn,
585
- };
586
- const lineTextIncludingWord = model.getValueInRange(lineTextIncludingWordRange);
587
- // NOTE: we don't need to trim here since the leading whitespace in front of
588
- // the section header is considered invalid syntax in the grammar
589
- if (!hasWhiteSpace(lineTextIncludingWord) &&
590
- lineTextIncludingWord.startsWith(PARSER_SECTION_MARKER)) {
591
- const parserKeyword = lineTextIncludingWord.substring(PARSER_SECTION_MARKER.length);
592
- const doc = getParserDocumetation(editorStore, parserKeyword);
593
- if (doc) {
594
- return {
595
- range: lineTextIncludingWordRange,
596
- contents: [
597
- doc.markdownText
598
- ? {
599
- value: doc.markdownText.value,
600
- }
601
- : undefined,
602
- doc.url
603
- ? {
604
- value: `[See documentation](${doc.url})`,
605
- }
606
- : undefined,
607
- ].filter(isNonNullable),
608
- };
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
+ }
609
645
  }
610
- }
611
- // show documentation for parser element
612
- const textUntilPosition = model.getValueInRange({
613
- startLineNumber: 1,
614
- startColumn: 1,
615
- endLineNumber: position.lineNumber,
616
- endColumn: position.column,
617
- });
618
- const allParserSectionHeaders =
619
- // NOTE: since `###Pure` is implicitly considered as the first section, we prepend it to the text
620
- `${PARSER_SECTION_MARKER}${PURE_PARSER.PURE}\n${textUntilPosition}`
621
- .split('\n')
622
- .filter((line) => line.startsWith(PARSER_SECTION_MARKER));
623
- const currentSectionParserKeyword = getSectionParserNameFromLineText(allParserSectionHeaders[allParserSectionHeaders.length - 1] ?? '');
624
- if (currentSectionParserKeyword) {
625
- const doc = getParserElementDocumentation(editorStore, currentSectionParserKeyword, currentWord.word);
626
- if (doc) {
627
- return {
628
- range: {
629
- startLineNumber: position.lineNumber,
630
- startColumn: currentWord.startColumn,
631
- endLineNumber: position.lineNumber,
632
- endColumn: currentWord.endColumn,
633
- },
634
- contents: [
635
- doc.markdownText
636
- ? {
637
- value: doc.markdownText.value,
638
- }
639
- : undefined,
640
- doc.url
641
- ? {
642
- value: `[See documentation](${doc.url})`,
643
- }
644
- : undefined,
645
- ].filter(isNonNullable),
646
- };
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
+ }
647
684
  }
648
- }
649
- return { contents: [] };
650
- },
651
- });
652
- // suggestion
653
- suggestionProviderDisposer.current?.dispose();
654
- suggestionProviderDisposer.current =
655
- monacoLanguagesAPI.registerCompletionItemProvider(CODE_EDITOR_LANGUAGE.PURE, {
656
- // NOTE: we need to specify this to show suggestions for section
657
- // because by default, only alphanumeric characters trigger completion item provider
658
- // See https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.CompletionContext.html#triggerCharacter
659
- // See https://github.com/microsoft/monaco-editor/issues/2530#issuecomment-861757198
660
- triggerCharacters: ['#'],
661
- provideCompletionItems: (model, position) => {
662
- let suggestions = [];
663
- // suggestions for parser keyword
664
- suggestions = suggestions.concat(getParserKeywordSuggestions(position, model, collectParserKeywordSuggestions(editorStore)));
665
- // suggestions for parser element snippets
666
- suggestions = suggestions.concat(getParserElementSnippetSuggestions(position, model, (parserName) => collectParserElementSnippetSuggestions(editorStore, parserName)));
667
- // inline code snippet suggestions
668
- suggestions = suggestions.concat(getInlineSnippetSuggestions(position, model));
669
- return { suggestions };
685
+ return { contents: [] };
670
686
  },
671
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
+ }
672
709
  }
673
710
  function toggleAutoFoldingElements() {
674
711
  const autoFoldingElements = editorStore.pluginManager
@@ -676,37 +713,32 @@ export const GrammarTextEditor = observer(() => {
676
713
  .flatMap((plugin) => plugin.getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
677
714
  []);
678
715
  const foldingClass = editor?.getContribution('editor.contrib.folding');
679
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
680
- foldingClass.getFoldingModel().then((foldingModel) => {
681
- const model = editor?.getModel();
682
- const toggleFoldingLines = [];
683
- model?.getLinesContent().forEach((line, j) => {
684
- autoFoldingElements.forEach((elementName) => {
685
- if (line.match(new RegExp(`^${elementName}`))) {
686
- toggleFoldingLines.push(j + 2);
687
- }
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
+ });
688
727
  });
689
- });
690
- let allElementsFolded = true;
691
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
692
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
693
- if (foldingModel._regions.isCollapsed(foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
694
- .regionIndex) === false) {
695
- allElementsFolded = false;
696
- }
697
- }
698
- });
699
- setFoldingElements(!allElementsFolded);
700
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
701
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
702
- if (foldingModel._regions.isCollapsed(foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
703
- .regionIndex) !== elementsFolded) {
704
- 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
+ }
705
735
  }
706
- }
736
+ });
737
+ setFoldingElements(toFold);
707
738
  });
708
- });
739
+ }
709
740
  }
741
+ // below use effects watch over `forcedCursorPosition`, `wordWrapOtion`, `error`, `warnings` and reset them to the editor as needed
710
742
  useEffect(() => {
711
743
  if (editor && forcedCursorPosition) {
712
744
  moveCursorToPosition(editor, forcedCursorPosition);
@@ -714,61 +746,72 @@ export const GrammarTextEditor = observer(() => {
714
746
  }, [editor, forcedCursorPosition]);
715
747
  useEffect(() => {
716
748
  if (editor) {
717
- const autoFoldingElements = editorStore.pluginManager
718
- .getApplicationPlugins()
719
- .flatMap((plugin) => plugin.getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
720
- []);
721
- const foldingClass = editor.getContribution('editor.contrib.folding');
722
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
723
- foldingClass.getFoldingModel().then((foldingModel) => {
724
- foldingModel.onDidChange(() => {
725
- const model = editor.getModel();
726
- const toggleFoldingLines = [];
727
- model?.getLinesContent().forEach((line, j) => {
728
- autoFoldingElements.forEach((elementName) => {
729
- if (line.match(new RegExp(`^${elementName}`))) {
730
- toggleFoldingLines.push(j + 2);
731
- }
732
- });
733
- });
734
- let allElementsFolded = true;
735
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
736
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
737
- if (foldingModel._regions.isCollapsed(foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
738
- .regionIndex) === false) {
739
- allElementsFolded = false;
740
- }
741
- }
742
- });
743
- setFoldingElements(!allElementsFolded);
744
- });
749
+ editor.updateOptions({
750
+ wordWrap: wordWrapOtion,
745
751
  });
746
752
  }
747
- });
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
748
787
  useEffect(() => {
749
788
  if (editor) {
789
+ const model = editor.getModel();
750
790
  const autoFoldingElements = editorStore.pluginManager
751
791
  .getApplicationPlugins()
752
792
  .flatMap((plugin) => plugin.getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
753
793
  []);
754
794
  const foldingClass = editor.getContribution('editor.contrib.folding');
755
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
756
- foldingClass
757
- .getFoldingModel()
758
- .then((foldingModel) => {
759
- const model = editor.getModel();
760
- const foldingLines = [];
761
- model?.getLinesContent().forEach((line, j) => {
762
- autoFoldingElements.forEach((elementName) => {
763
- if (line.match(new RegExp(`^${elementName}`))) {
764
- foldingLines.push(j + 2);
765
- }
766
- });
767
- });
768
- foldingLines.forEach((foldingLineRegion, i) => {
769
- 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
+ }
770
813
  });
771
- });
814
+ }
772
815
  }
773
816
  }, [editor, editorStore.pluginManager]);
774
817
  // NOTE: dispose the editor to prevent potential memory-leak
@@ -781,10 +824,10 @@ export const GrammarTextEditor = observer(() => {
781
824
  suggestionProviderDisposer.current?.dispose();
782
825
  }, [editor]);
783
826
  useApplicationNavigationContext(LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.TEXT_MODE_EDITOR);
784
- 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', {
785
- 'editor-group__header__action--active': grammarTextEditorState.wrapText,
786
- }), 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', {
787
- 'editor-group__header__action--active': elementsFolded,
788
- }), 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 }) })] })] }));
789
832
  });
790
833
  //# sourceMappingURL=GrammarTextEditor.js.map