@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
@@ -20,17 +20,20 @@ import {
20
20
  type IDisposable,
21
21
  editor as monacoEditorAPI,
22
22
  languages as monacoLanguagesAPI,
23
+ KeyCode,
24
+ KeyMod,
23
25
  } from 'monaco-editor';
24
26
  import {
25
27
  ContextMenu,
26
- clsx,
27
- WordWrapIcon,
28
- MoreHorizontalIcon,
29
- FoldIcon,
30
- UnfoldIcon,
31
28
  PanelContent,
32
29
  MenuContent,
33
30
  MenuContentItem,
31
+ PanelLoadingIndicator,
32
+ CaretDownIcon,
33
+ DropdownMenu,
34
+ MenuContentItemIcon,
35
+ CheckIcon,
36
+ MenuContentItemLabel,
34
37
  } from '@finos/legend-art';
35
38
  import {
36
39
  DEFAULT_TAB_SIZE,
@@ -64,12 +67,20 @@ import { useDrop } from 'react-dnd';
64
67
  import type { DSL_LegendStudioApplicationPlugin_Extension } from '../../../stores/LegendStudioApplicationPlugin.js';
65
68
  import { flowResult } from 'mobx';
66
69
  import { useEditorStore } from '../EditorStoreProvider.js';
67
- import { hasWhiteSpace, isNonNullable } from '@finos/legend-shared';
68
70
  import {
71
+ LogEvent,
72
+ assertErrorThrown,
73
+ assertTrue,
74
+ hasWhiteSpace,
75
+ isNonNullable,
76
+ } from '@finos/legend-shared';
77
+ import {
78
+ ELEMENT_PATH_DELIMITER,
69
79
  PARSER_SECTION_MARKER,
70
80
  PURE_CONNECTION_NAME,
71
81
  PURE_ELEMENT_NAME,
72
82
  PURE_PARSER,
83
+ isValidFullPath,
73
84
  } from '@finos/legend-graph';
74
85
  import type { EditorStore } from '../../../stores/editor/EditorStore.js';
75
86
  import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../../__lib__/LegendStudioDocumentation.js';
@@ -109,7 +120,11 @@ import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../__lib
109
120
  import type { DSL_Mapping_LegendStudioApplicationPlugin_Extension } from '../../../stores/extensions/DSL_Mapping_LegendStudioApplicationPlugin_Extension.js';
110
121
  import type { STO_Relational_LegendStudioApplicationPlugin_Extension } from '../../../stores/extensions/STO_Relational_LegendStudioApplicationPlugin_Extension.js';
111
122
  import { LEGEND_STUDIO_SETTING_KEY } from '../../../__lib__/LegendStudioSetting.js';
112
- import { GraphEditGrammarModeState } from '../../../stores/editor/GraphEditGrammarModeState.js';
123
+ import {
124
+ GRAMMAR_MODE_EDITOR_ACTION,
125
+ GraphEditGrammarModeState,
126
+ } from '../../../stores/editor/GraphEditGrammarModeState.js';
127
+ import { LEGEND_STUDIO_APP_EVENT } from '../../../__lib__/LegendStudioEvent.js';
113
128
 
114
129
  export const GrammarTextEditorHeaderTabContextMenu = observer(
115
130
  forwardRef<HTMLDivElement, { children?: React.ReactNode }>(
@@ -678,6 +693,81 @@ const collectParserElementSnippetSuggestions = (
678
693
  return [];
679
694
  };
680
695
 
696
+ const resolveElementPathFromCurrentPosition = (
697
+ _editor: monacoEditorAPI.ICodeEditor,
698
+ grammarModeState: GraphEditGrammarModeState,
699
+ ): string | undefined => {
700
+ let elementPath = '';
701
+ try {
702
+ const model = _editor.getModel();
703
+ let position = _editor.getPosition();
704
+ let maxWords = 0;
705
+ if (model) {
706
+ while (position && maxWords < 30) {
707
+ const currentWord = model.getWordAtPosition(position);
708
+ const lineNumber = position.lineNumber;
709
+ position = null;
710
+ maxWords += 1;
711
+ if (currentWord) {
712
+ const wordStartPost = {
713
+ lineNumber: lineNumber,
714
+ column: currentWord.startColumn,
715
+ };
716
+ const startPost = model.modifyPosition(wordStartPost, -2);
717
+ elementPath = currentWord.word + elementPath;
718
+ const pathDelimiterRange = {
719
+ startLineNumber: startPost.lineNumber,
720
+ startColumn: startPost.column,
721
+ endLineNumber: wordStartPost.lineNumber,
722
+ endColumn: wordStartPost.column,
723
+ };
724
+ const packageRange = model.getValueInRange(
725
+ model.validateRange(pathDelimiterRange),
726
+ );
727
+ if (packageRange === ELEMENT_PATH_DELIMITER) {
728
+ elementPath = packageRange + elementPath;
729
+ position = model.modifyPosition(startPost, -1);
730
+ }
731
+ }
732
+ }
733
+ }
734
+ assertTrue(
735
+ isValidFullPath(elementPath),
736
+ `Unable to go to element definition. Not valid element path: ${elementPath}`,
737
+ );
738
+ return elementPath;
739
+ } catch (error) {
740
+ assertErrorThrown(error);
741
+ grammarModeState.editorStore.applicationStore.logService.error(
742
+ LogEvent.create(
743
+ LEGEND_STUDIO_APP_EVENT.TEXT_MODE_ACTION_KEYBOARD_SHORTCUT_GO_TO_DEFINITION__ERROR,
744
+ ),
745
+ error,
746
+ );
747
+ }
748
+ return undefined;
749
+ };
750
+
751
+ const goToElement = (
752
+ _editor: monacoEditorAPI.ICodeEditor,
753
+ grammarModeState: GraphEditGrammarModeState,
754
+ ): void => {
755
+ grammarModeState.editorStore.applicationStore.logService.info(
756
+ LogEvent.create(
757
+ LEGEND_STUDIO_APP_EVENT.TEXT_MODE_ACTION_KEYBOARD_SHORTCUT_GO_TO_DEFINITION__LAUNCH,
758
+ ),
759
+ );
760
+ const elementPath = resolveElementPathFromCurrentPosition(
761
+ _editor,
762
+ grammarModeState,
763
+ );
764
+ if (elementPath) {
765
+ flowResult(grammarModeState.goToElement(elementPath)).catch(
766
+ grammarModeState.editorStore.applicationStore.alertUnhandledError,
767
+ );
768
+ }
769
+ };
770
+
681
771
  export const GrammarTextEditor = observer(() => {
682
772
  const [editor, setEditor] = useState<
683
773
  monacoEditorAPI.IStandaloneCodeEditor | undefined
@@ -689,9 +779,11 @@ export const GrammarTextEditor = observer(() => {
689
779
  );
690
780
  const grammarTextEditorState = grammarModeState.grammarTextEditorState;
691
781
  const error = editorStore.graphState.error;
782
+ const warnings = editorStore.graphState.warnings;
692
783
  const [elementsFolded, setFoldingElements] = useState(false);
693
784
 
694
785
  const forcedCursorPosition = grammarTextEditorState.forcedCursorPosition;
786
+ const wordWrapOtion = grammarTextEditorState.wordWrapOtion;
695
787
  const value = normalizeLineEnding(grammarTextEditorState.graphGrammarText);
696
788
  const textEditorRef = useRef<HTMLDivElement>(null);
697
789
  const hoverProviderDisposer = useRef<IDisposable | undefined>(undefined);
@@ -701,6 +793,10 @@ export const GrammarTextEditor = observer(() => {
701
793
  flowResult(editorStore.toggleTextMode()),
702
794
  );
703
795
 
796
+ const globalCompile = applicationStore.guardUnhandledError(() =>
797
+ flowResult(grammarModeState.globalCompile()),
798
+ );
799
+
704
800
  const toggleWordWrap = (): void => {
705
801
  grammarTextEditorState.setWrapText(!grammarTextEditorState.wrapText);
706
802
  editorStore.applicationStore.settingService.persistValue(
@@ -717,18 +813,36 @@ export const GrammarTextEditor = observer(() => {
717
813
  language: CODE_EDITOR_LANGUAGE.PURE,
718
814
  theme: CODE_EDITOR_THEME.DEFAULT_DARK,
719
815
  renderValidationDecorations: 'on',
816
+ wordWrap: grammarTextEditorState.wordWrapOtion,
817
+ readOnly: editorStore.editorMode.disableEditing,
720
818
  });
721
819
  _editor.onDidChangeModelContent(() => {
722
820
  grammarTextEditorState.setGraphGrammarText(getCodeEditorValue(_editor));
723
821
  clearMarkers();
724
822
  // NOTE: we can technically can reset the current element label regex string here
725
823
  // but if we do that on first load, the cursor will not jump to the current element
726
- // also, it's better to place that logic in an effect that watches for the regex string
824
+ // also, it's better to place that logic in an effect that watches for the regex string.
825
+ // this is done by watching `forcedCursorPosition` in the useEffect
727
826
  });
728
827
  _editor.focus(); // focus on the editor initially
828
+ _editor.getModel()?.updateOptions({ tabSize: DEFAULT_TAB_SIZE });
829
+ _editor.addAction({
830
+ id: GRAMMAR_MODE_EDITOR_ACTION.GO_TO_ELEMENT_DEFINITION,
831
+ label: 'Go To Element',
832
+ keybindings: [KeyMod.CtrlCmd | KeyCode.KeyB],
833
+ run: (ed: monacoEditorAPI.ICodeEditor): void => {
834
+ goToElement(ed, grammarModeState);
835
+ },
836
+ });
729
837
  setEditor(_editor);
730
838
  }
731
- }, [editorStore, applicationStore, editor, grammarTextEditorState]);
839
+ }, [
840
+ editorStore,
841
+ applicationStore,
842
+ editor,
843
+ grammarTextEditorState,
844
+ grammarModeState,
845
+ ]);
732
846
 
733
847
  // Drag and Drop
734
848
  const extraElementDragTypes = editorStore.pluginManager
@@ -776,210 +890,163 @@ export const GrammarTextEditor = observer(() => {
776
890
  [extraElementDragTypes, handleDrop],
777
891
  );
778
892
  dropConnector(textEditorRef);
779
-
780
893
  if (editor) {
781
894
  // Set the value of the editor
782
895
  const currentValue = getCodeEditorValue(editor);
783
896
  if (currentValue !== value) {
784
897
  editor.setValue(value);
785
898
  }
786
- editor.updateOptions({
787
- wordWrap: grammarTextEditorState.wrapText ? 'on' : 'off',
788
- });
789
899
  resetLineNumberGutterWidth(editor);
790
900
  const editorModel = editor.getModel();
791
901
  if (editorModel) {
792
- editorModel.updateOptions({ tabSize: DEFAULT_TAB_SIZE });
793
- if (
794
- error?.sourceInformation &&
795
- // We should only calculate if problems are stale if there is an error/warning as
796
- // calculating staleness takes time
797
- !editorStore.graphState.areProblemsStale
798
- ) {
799
- setErrorMarkers(editorModel, [
800
- {
801
- message: error.message,
802
- startLineNumber: error.sourceInformation.startLine,
803
- startColumn: error.sourceInformation.startColumn,
804
- endLineNumber: error.sourceInformation.endLine,
805
- endColumn: error.sourceInformation.endColumn,
806
- },
807
- ]);
808
- }
809
-
810
- if (
811
- editorStore.graphState.warnings.length &&
812
- // We should only calculate if problems are stale if there is an error/warning as
813
- // calculating staleness takes time
814
- !editorStore.graphState.areProblemsStale
815
- ) {
816
- setWarningMarkers(
817
- editorModel,
818
- editorStore.graphState.warnings
819
- .map((warning) => {
820
- if (!warning.sourceInformation) {
821
- return undefined;
822
- }
823
- return {
824
- message: warning.message,
825
- startLineNumber: warning.sourceInformation.startLine,
826
- startColumn: warning.sourceInformation.startColumn,
827
- endLineNumber: warning.sourceInformation.endLine,
828
- endColumn: warning.sourceInformation.endColumn,
829
- };
830
- })
831
- .filter(isNonNullable),
832
- );
833
- }
834
- }
835
- // Disable editing if user is in viewer mode
836
- editor.updateOptions({ readOnly: editorStore.editorMode.disableEditing });
837
-
838
- // hover
839
- hoverProviderDisposer.current?.dispose();
840
- hoverProviderDisposer.current = monacoLanguagesAPI.registerHoverProvider(
841
- CODE_EDITOR_LANGUAGE.PURE,
842
- {
843
- provideHover: (model, position) => {
844
- const currentWord = model.getWordAtPosition(position);
845
- if (!currentWord) {
846
- return { contents: [] };
847
- }
902
+ // hover
903
+ hoverProviderDisposer.current?.dispose();
904
+ hoverProviderDisposer.current = monacoLanguagesAPI.registerHoverProvider(
905
+ CODE_EDITOR_LANGUAGE.PURE,
906
+ {
907
+ provideHover: (model, position) => {
908
+ const currentWord = model.getWordAtPosition(position);
909
+ if (!currentWord) {
910
+ return { contents: [] };
911
+ }
848
912
 
849
- // show documention for parser section
850
- const lineTextIncludingWordRange = {
851
- startLineNumber: position.lineNumber,
852
- startColumn: 1,
853
- endLineNumber: position.lineNumber,
854
- endColumn: currentWord.endColumn,
855
- };
856
- const lineTextIncludingWord = model.getValueInRange(
857
- lineTextIncludingWordRange,
858
- );
859
- // NOTE: we don't need to trim here since the leading whitespace in front of
860
- // the section header is considered invalid syntax in the grammar
861
- if (
862
- !hasWhiteSpace(lineTextIncludingWord) &&
863
- lineTextIncludingWord.startsWith(PARSER_SECTION_MARKER)
864
- ) {
865
- const parserKeyword = lineTextIncludingWord.substring(
866
- PARSER_SECTION_MARKER.length,
913
+ // show documention for parser section
914
+ const lineTextIncludingWordRange = {
915
+ startLineNumber: position.lineNumber,
916
+ startColumn: 1,
917
+ endLineNumber: position.lineNumber,
918
+ endColumn: currentWord.endColumn,
919
+ };
920
+ const lineTextIncludingWord = model.getValueInRange(
921
+ lineTextIncludingWordRange,
867
922
  );
868
- const doc = getParserDocumetation(editorStore, parserKeyword);
869
- if (doc) {
870
- return {
871
- range: lineTextIncludingWordRange,
872
- contents: [
873
- doc.markdownText
874
- ? {
875
- value: doc.markdownText.value,
876
- }
877
- : undefined,
878
- doc.url
879
- ? {
880
- value: `[See documentation](${doc.url})`,
881
- }
882
- : undefined,
883
- ].filter(isNonNullable),
884
- };
923
+ // NOTE: we don't need to trim here since the leading whitespace in front of
924
+ // the section header is considered invalid syntax in the grammar
925
+ if (
926
+ !hasWhiteSpace(lineTextIncludingWord) &&
927
+ lineTextIncludingWord.startsWith(PARSER_SECTION_MARKER)
928
+ ) {
929
+ const parserKeyword = lineTextIncludingWord.substring(
930
+ PARSER_SECTION_MARKER.length,
931
+ );
932
+ const doc = getParserDocumetation(editorStore, parserKeyword);
933
+ if (doc) {
934
+ return {
935
+ range: lineTextIncludingWordRange,
936
+ contents: [
937
+ doc.markdownText
938
+ ? {
939
+ value: doc.markdownText.value,
940
+ }
941
+ : undefined,
942
+ doc.url
943
+ ? {
944
+ value: `[See documentation](${doc.url})`,
945
+ }
946
+ : undefined,
947
+ ].filter(isNonNullable),
948
+ };
949
+ }
885
950
  }
886
- }
887
951
 
888
- // show documentation for parser element
889
- const textUntilPosition = model.getValueInRange({
890
- startLineNumber: 1,
891
- startColumn: 1,
892
- endLineNumber: position.lineNumber,
893
- endColumn: position.column,
894
- });
895
- const allParserSectionHeaders =
896
- // NOTE: since `###Pure` is implicitly considered as the first section, we prepend it to the text
897
- `${PARSER_SECTION_MARKER}${PURE_PARSER.PURE}\n${textUntilPosition}`
898
- .split('\n')
899
- .filter((line) => line.startsWith(PARSER_SECTION_MARKER));
900
- const currentSectionParserKeyword = getSectionParserNameFromLineText(
901
- allParserSectionHeaders[allParserSectionHeaders.length - 1] ?? '',
902
- );
903
- if (currentSectionParserKeyword) {
904
- const doc = getParserElementDocumentation(
905
- editorStore,
906
- currentSectionParserKeyword,
907
- currentWord.word,
908
- );
909
- if (doc) {
910
- return {
911
- range: {
912
- startLineNumber: position.lineNumber,
913
- startColumn: currentWord.startColumn,
914
- endLineNumber: position.lineNumber,
915
- endColumn: currentWord.endColumn,
916
- },
917
- contents: [
918
- doc.markdownText
919
- ? {
920
- value: doc.markdownText.value,
921
- }
922
- : undefined,
923
- doc.url
924
- ? {
925
- value: `[See documentation](${doc.url})`,
926
- }
927
- : undefined,
928
- ].filter(isNonNullable),
929
- };
952
+ // show documentation for parser element
953
+ const textUntilPosition = model.getValueInRange({
954
+ startLineNumber: 1,
955
+ startColumn: 1,
956
+ endLineNumber: position.lineNumber,
957
+ endColumn: position.column,
958
+ });
959
+ const allParserSectionHeaders =
960
+ // NOTE: since `###Pure` is implicitly considered as the first section, we prepend it to the text
961
+ `${PARSER_SECTION_MARKER}${PURE_PARSER.PURE}\n${textUntilPosition}`
962
+ .split('\n')
963
+ .filter((line) => line.startsWith(PARSER_SECTION_MARKER));
964
+ const currentSectionParserKeyword =
965
+ getSectionParserNameFromLineText(
966
+ allParserSectionHeaders[allParserSectionHeaders.length - 1] ??
967
+ '',
968
+ );
969
+ if (currentSectionParserKeyword) {
970
+ const doc = getParserElementDocumentation(
971
+ editorStore,
972
+ currentSectionParserKeyword,
973
+ currentWord.word,
974
+ );
975
+ if (doc) {
976
+ return {
977
+ range: {
978
+ startLineNumber: position.lineNumber,
979
+ startColumn: currentWord.startColumn,
980
+ endLineNumber: position.lineNumber,
981
+ endColumn: currentWord.endColumn,
982
+ },
983
+ contents: [
984
+ doc.markdownText
985
+ ? {
986
+ value: doc.markdownText.value,
987
+ }
988
+ : undefined,
989
+ doc.url
990
+ ? {
991
+ value: `[See documentation](${doc.url})`,
992
+ }
993
+ : undefined,
994
+ ].filter(isNonNullable),
995
+ };
996
+ }
930
997
  }
931
- }
932
998
 
933
- return { contents: [] };
999
+ return { contents: [] };
1000
+ },
934
1001
  },
935
- },
936
- );
1002
+ );
937
1003
 
938
- // suggestion
939
- suggestionProviderDisposer.current?.dispose();
940
- suggestionProviderDisposer.current =
941
- monacoLanguagesAPI.registerCompletionItemProvider(
942
- CODE_EDITOR_LANGUAGE.PURE,
943
- {
944
- // NOTE: we need to specify this to show suggestions for section
945
- // because by default, only alphanumeric characters trigger completion item provider
946
- // See https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.CompletionContext.html#triggerCharacter
947
- // See https://github.com/microsoft/monaco-editor/issues/2530#issuecomment-861757198
948
- triggerCharacters: ['#'],
949
- provideCompletionItems: (model, position) => {
950
- let suggestions: monacoLanguagesAPI.CompletionItem[] = [];
1004
+ // suggestion
1005
+ suggestionProviderDisposer.current?.dispose();
1006
+ suggestionProviderDisposer.current =
1007
+ monacoLanguagesAPI.registerCompletionItemProvider(
1008
+ CODE_EDITOR_LANGUAGE.PURE,
1009
+ {
1010
+ // NOTE: we need to specify this to show suggestions for section
1011
+ // because by default, only alphanumeric characters trigger completion item provider
1012
+ // See https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.CompletionContext.html#triggerCharacter
1013
+ // See https://github.com/microsoft/monaco-editor/issues/2530#issuecomment-861757198
1014
+ triggerCharacters: ['#'],
1015
+ provideCompletionItems: (model, position) => {
1016
+ let suggestions: monacoLanguagesAPI.CompletionItem[] = [];
951
1017
 
952
- // suggestions for parser keyword
953
- suggestions = suggestions.concat(
954
- getParserKeywordSuggestions(
955
- position,
956
- model,
957
- collectParserKeywordSuggestions(editorStore),
958
- ),
959
- );
1018
+ // suggestions for parser keyword
1019
+ suggestions = suggestions.concat(
1020
+ getParserKeywordSuggestions(
1021
+ position,
1022
+ model,
1023
+ collectParserKeywordSuggestions(editorStore),
1024
+ ),
1025
+ );
960
1026
 
961
- // suggestions for parser element snippets
962
- suggestions = suggestions.concat(
963
- getParserElementSnippetSuggestions(
964
- position,
965
- model,
966
- (parserName: string) =>
967
- collectParserElementSnippetSuggestions(
968
- editorStore,
969
- parserName,
970
- ),
971
- ),
972
- );
1027
+ // suggestions for parser element snippets
1028
+ suggestions = suggestions.concat(
1029
+ getParserElementSnippetSuggestions(
1030
+ position,
1031
+ model,
1032
+ (parserName: string) =>
1033
+ collectParserElementSnippetSuggestions(
1034
+ editorStore,
1035
+ parserName,
1036
+ ),
1037
+ ),
1038
+ );
973
1039
 
974
- // inline code snippet suggestions
975
- suggestions = suggestions.concat(
976
- getInlineSnippetSuggestions(position, model),
977
- );
1040
+ // inline code snippet suggestions
1041
+ suggestions = suggestions.concat(
1042
+ getInlineSnippetSuggestions(position, model),
1043
+ );
978
1044
 
979
- return { suggestions };
1045
+ return { suggestions };
1046
+ },
980
1047
  },
981
- },
982
- );
1048
+ );
1049
+ }
983
1050
  }
984
1051
 
985
1052
  function toggleAutoFoldingElements(): void {
@@ -993,80 +1060,7 @@ export const GrammarTextEditor = observer(() => {
993
1060
  [],
994
1061
  );
995
1062
  const foldingClass = editor?.getContribution('editor.contrib.folding');
996
-
997
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
998
- (foldingClass as any).getFoldingModel().then(
999
- (foldingModel: {
1000
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1001
- _regions: any;
1002
- onDidChange: (arg0: () => void) => void;
1003
- getRegionAtLine: (arg0: unknown) => unknown;
1004
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1005
- getAllRegionsAtLine(arg0: unknown): any;
1006
- toggleCollapseState: (arg0: unknown) => unknown;
1007
- }) => {
1008
- const model = editor?.getModel();
1009
- const toggleFoldingLines: number[] = [];
1010
- model?.getLinesContent().forEach((line, j) => {
1011
- autoFoldingElements.forEach((elementName) => {
1012
- if (line.match(new RegExp(`^${elementName}`))) {
1013
- toggleFoldingLines.push(j + 2);
1014
- }
1015
- });
1016
- });
1017
- let allElementsFolded = true;
1018
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
1019
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
1020
- if (
1021
- foldingModel._regions.isCollapsed(
1022
- foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
1023
- .regionIndex,
1024
- ) === false
1025
- ) {
1026
- allElementsFolded = false;
1027
- }
1028
- }
1029
- });
1030
-
1031
- setFoldingElements(!allElementsFolded);
1032
-
1033
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
1034
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
1035
- if (
1036
- foldingModel._regions.isCollapsed(
1037
- foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
1038
- .regionIndex,
1039
- ) !== elementsFolded
1040
- ) {
1041
- foldingModel.toggleCollapseState(
1042
- foldingModel.getAllRegionsAtLine(foldingLineRegion),
1043
- );
1044
- }
1045
- }
1046
- });
1047
- },
1048
- );
1049
- }
1050
-
1051
- useEffect(() => {
1052
- if (editor && forcedCursorPosition) {
1053
- moveCursorToPosition(editor, forcedCursorPosition);
1054
- }
1055
- }, [editor, forcedCursorPosition]);
1056
-
1057
- useEffect(() => {
1058
- if (editor) {
1059
- const autoFoldingElements = editorStore.pluginManager
1060
- .getApplicationPlugins()
1061
- .flatMap(
1062
- (plugin) =>
1063
- (
1064
- plugin as DSL_LegendStudioApplicationPlugin_Extension
1065
- ).getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
1066
- [],
1067
- );
1068
- const foldingClass = editor.getContribution('editor.contrib.folding');
1069
-
1063
+ if (editor && foldingClass) {
1070
1064
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1071
1065
  (foldingClass as any).getFoldingModel().then(
1072
1066
  (foldingModel: {
@@ -1078,39 +1072,89 @@ export const GrammarTextEditor = observer(() => {
1078
1072
  getAllRegionsAtLine(arg0: unknown): any;
1079
1073
  toggleCollapseState: (arg0: unknown) => unknown;
1080
1074
  }) => {
1081
- foldingModel.onDidChange(() => {
1082
- const model = editor.getModel();
1083
- const toggleFoldingLines: number[] = [];
1084
- model?.getLinesContent().forEach((line, j) => {
1085
- autoFoldingElements.forEach((elementName) => {
1086
- if (line.match(new RegExp(`^${elementName}`))) {
1087
- toggleFoldingLines.push(j + 2);
1088
- }
1089
- });
1090
- });
1091
- let allElementsFolded = true;
1092
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
1093
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
1094
- if (
1095
- foldingModel._regions.isCollapsed(
1096
- foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
1097
- .regionIndex,
1098
- ) === false
1099
- ) {
1100
- allElementsFolded = false;
1101
- }
1075
+ const model = editor.getModel();
1076
+ const toggleFoldingLines: number[] = [];
1077
+ model?.getLinesContent().forEach((line, j) => {
1078
+ autoFoldingElements.forEach((elementName) => {
1079
+ if (line.match(new RegExp(`^${elementName}`))) {
1080
+ toggleFoldingLines.push(j + 2);
1102
1081
  }
1103
1082
  });
1104
-
1105
- setFoldingElements(!allElementsFolded);
1106
1083
  });
1084
+ const toFold = !elementsFolded;
1085
+ toggleFoldingLines.forEach((foldingLineRegion, i) => {
1086
+ if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
1087
+ if (
1088
+ foldingModel._regions.isCollapsed(
1089
+ foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
1090
+ .regionIndex,
1091
+ ) !== toFold
1092
+ ) {
1093
+ foldingModel.toggleCollapseState(
1094
+ foldingModel.getAllRegionsAtLine(foldingLineRegion),
1095
+ );
1096
+ }
1097
+ }
1098
+ });
1099
+ setFoldingElements(toFold);
1107
1100
  },
1108
1101
  );
1109
1102
  }
1110
- });
1103
+ }
1104
+
1105
+ // below use effects watch over `forcedCursorPosition`, `wordWrapOtion`, `error`, `warnings` and reset them to the editor as needed
1106
+ useEffect(() => {
1107
+ if (editor && forcedCursorPosition) {
1108
+ moveCursorToPosition(editor, forcedCursorPosition);
1109
+ }
1110
+ }, [editor, forcedCursorPosition]);
1111
+ useEffect(() => {
1112
+ if (editor) {
1113
+ editor.updateOptions({
1114
+ wordWrap: wordWrapOtion,
1115
+ });
1116
+ }
1117
+ }, [editor, wordWrapOtion]);
1118
+ useEffect(() => {
1119
+ const editorModel = editor?.getModel();
1120
+ if (editorModel && (error?.sourceInformation || warnings.length)) {
1121
+ if (error?.sourceInformation) {
1122
+ setErrorMarkers(editorModel, [
1123
+ {
1124
+ message: error.message,
1125
+ startLineNumber: error.sourceInformation.startLine,
1126
+ startColumn: error.sourceInformation.startColumn,
1127
+ endLineNumber: error.sourceInformation.endLine,
1128
+ endColumn: error.sourceInformation.endColumn,
1129
+ },
1130
+ ]);
1131
+ }
1132
+ if (warnings.length) {
1133
+ setWarningMarkers(
1134
+ editorModel,
1135
+ warnings
1136
+ .map((warning) => {
1137
+ if (!warning.sourceInformation) {
1138
+ return undefined;
1139
+ }
1140
+ return {
1141
+ message: warning.message,
1142
+ startLineNumber: warning.sourceInformation.startLine,
1143
+ startColumn: warning.sourceInformation.startColumn,
1144
+ endLineNumber: warning.sourceInformation.endLine,
1145
+ endColumn: warning.sourceInformation.endColumn,
1146
+ };
1147
+ })
1148
+ .filter(isNonNullable),
1149
+ );
1150
+ }
1151
+ }
1152
+ }, [editor, error, warnings]);
1111
1153
 
1154
+ // first load with grammar. auto fold element sections
1112
1155
  useEffect(() => {
1113
1156
  if (editor) {
1157
+ const model = editor.getModel();
1114
1158
  const autoFoldingElements = editorStore.pluginManager
1115
1159
  .getApplicationPlugins()
1116
1160
  .flatMap(
@@ -1121,33 +1165,38 @@ export const GrammarTextEditor = observer(() => {
1121
1165
  [],
1122
1166
  );
1123
1167
  const foldingClass = editor.getContribution('editor.contrib.folding');
1124
-
1125
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1126
- (foldingClass as any)
1127
- .getFoldingModel()
1128
- .then(
1129
- (foldingModel: {
1130
- onDidChange: (arg0: () => void) => void;
1131
- getRegionAtLine: (arg0: unknown) => unknown;
1132
- getAllRegionsAtLine(arg0: unknown): unknown;
1133
- toggleCollapseState: (arg0: unknown) => unknown;
1134
- }) => {
1135
- const model = editor.getModel();
1136
- const foldingLines: number[] = [];
1137
- model?.getLinesContent().forEach((line, j) => {
1138
- autoFoldingElements.forEach((elementName) => {
1139
- if (line.match(new RegExp(`^${elementName}`))) {
1140
- foldingLines.push(j + 2);
1168
+ if (foldingClass && model) {
1169
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1170
+ (foldingClass as any).getFoldingModel().then(
1171
+ (
1172
+ foldingModel:
1173
+ | {
1174
+ onDidChange: (arg0: () => void) => void;
1175
+ getRegionAtLine: (arg0: unknown) => unknown;
1176
+ getAllRegionsAtLine(arg0: unknown): unknown;
1177
+ toggleCollapseState: (arg0: unknown) => unknown;
1141
1178
  }
1179
+ | undefined,
1180
+ ) => {
1181
+ if (foldingModel) {
1182
+ const elementLinesToBeFolded: number[] = [];
1183
+ model.getLinesContent().forEach((line, idx) => {
1184
+ autoFoldingElements.forEach((elementName) => {
1185
+ if (line.match(new RegExp(`^${elementName}`))) {
1186
+ elementLinesToBeFolded.push(idx + 2);
1187
+ }
1188
+ });
1142
1189
  });
1143
- });
1144
- foldingLines.forEach((foldingLineRegion, i) => {
1145
- foldingModel.toggleCollapseState(
1146
- foldingModel.getAllRegionsAtLine(foldingLineRegion),
1147
- );
1148
- });
1190
+ elementLinesToBeFolded.forEach((lineToBeFolded) => {
1191
+ const regionToFold =
1192
+ foldingModel.getAllRegionsAtLine(lineToBeFolded);
1193
+ foldingModel.toggleCollapseState(regionToFold);
1194
+ });
1195
+ setFoldingElements(true);
1196
+ }
1149
1197
  },
1150
1198
  );
1199
+ }
1151
1200
  }
1152
1201
  }, [editor, editorStore.pluginManager]);
1153
1202
 
@@ -1173,19 +1222,6 @@ export const GrammarTextEditor = observer(() => {
1173
1222
  <div className="panel editor-group">
1174
1223
  <div className="panel__header editor-group__header">
1175
1224
  <div className="editor-group__header__tabs">
1176
- <div className="editor-group__text-mode__tab">
1177
- <button
1178
- className="editor-group__text-mode__tab__label"
1179
- disabled={
1180
- editorStore.graphState.isApplicationLeavingGraphEditMode
1181
- }
1182
- onClick={leaveTextMode}
1183
- tabIndex={-1}
1184
- title="Click to exit text mode and go back to form mode"
1185
- >
1186
- <MoreHorizontalIcon />
1187
- </button>
1188
- </div>
1189
1225
  <ContextMenu
1190
1226
  className="editor-group__text-mode__tab editor-group__text-mode__tab--active"
1191
1227
  content={<GrammarTextEditorHeaderTabContextMenu />}
@@ -1196,39 +1232,66 @@ export const GrammarTextEditor = observer(() => {
1196
1232
  </ContextMenu>
1197
1233
  </div>
1198
1234
  <div className="editor-group__header__actions">
1199
- <button
1200
- className={clsx('editor-group__header__action', {
1201
- 'editor-group__header__action--active':
1202
- grammarTextEditorState.wrapText,
1203
- })}
1204
- onClick={toggleWordWrap}
1205
- tabIndex={-1}
1206
- title={`[${
1207
- grammarTextEditorState.wrapText ? 'on' : 'off'
1208
- }] Toggle word wrap`}
1209
- >
1210
- <WordWrapIcon className="editor-group__icon__word-wrap" />
1211
- </button>
1212
- <button
1213
- className={clsx('editor-group__header__action', {
1214
- 'editor-group__header__action--active': elementsFolded,
1215
- })}
1216
- onClick={toggleAutoFoldingElements}
1217
- tabIndex={-1}
1218
- title={`${
1219
- !elementsFolded ? 'Unfold' : 'Fold'
1220
- } auto-folding elements`}
1221
- >
1222
- {!elementsFolded && (
1223
- <UnfoldIcon className="editor-group__icon__word-wrap" />
1224
- )}
1225
- {elementsFolded && (
1226
- <FoldIcon className="editor-group__icon__word-wrap" />
1227
- )}
1228
- </button>
1235
+ <div className="editor-group__text-mode__action">
1236
+ <button
1237
+ title="Compile (F9)"
1238
+ onClick={globalCompile}
1239
+ className="editor-group__text-mode-btn btn--dark"
1240
+ >
1241
+ Compile
1242
+ </button>
1243
+ </div>
1244
+ <div className="editor-group__text-mode__action">
1245
+ <button
1246
+ title="Click to exit text mode and go back to form mode (F8)"
1247
+ onClick={leaveTextMode}
1248
+ className="editor-group__text-mode-btn btn--dark"
1249
+ >
1250
+ Exit Text Mode
1251
+ </button>
1252
+ </div>
1253
+ <div className="query-builder__header__actions">
1254
+ <DropdownMenu
1255
+ className="query-builder__header__advanced-dropdown"
1256
+ title="Show Advanced Menu..."
1257
+ content={
1258
+ <MenuContent>
1259
+ <MenuContentItem onClick={toggleWordWrap}>
1260
+ <MenuContentItemIcon>
1261
+ {grammarTextEditorState.wrapText ? <CheckIcon /> : null}
1262
+ </MenuContentItemIcon>
1263
+ <MenuContentItemLabel>
1264
+ Wrap Overflowing Words
1265
+ </MenuContentItemLabel>
1266
+ </MenuContentItem>
1267
+ <MenuContentItem onClick={toggleAutoFoldingElements}>
1268
+ <MenuContentItemIcon>
1269
+ {elementsFolded ? <CheckIcon /> : null}
1270
+ </MenuContentItemIcon>
1271
+ <MenuContentItemLabel>
1272
+ Auto Fold Elements
1273
+ </MenuContentItemLabel>
1274
+ </MenuContentItem>
1275
+ </MenuContent>
1276
+ }
1277
+ menuProps={{
1278
+ anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
1279
+ transformOrigin: { vertical: 'top', horizontal: 'right' },
1280
+ elevation: 7,
1281
+ }}
1282
+ >
1283
+ <div className="query-builder__header__advanced-dropdown__label">
1284
+ Advanced
1285
+ </div>
1286
+ <CaretDownIcon className="query-builder__header__advanced-dropdown__icon" />
1287
+ </DropdownMenu>
1288
+ </div>
1229
1289
  </div>
1230
1290
  </div>
1231
1291
  <PanelContent className="editor-group__content">
1292
+ <PanelLoadingIndicator
1293
+ isLoading={editorStore.graphState.isRunningGlobalCompile}
1294
+ />
1232
1295
  <div className="code-editor__container">
1233
1296
  <div className="code-editor__body" ref={textEditorRef} />
1234
1297
  </div>