@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
@@ -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,206 +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
- !editorStore.graphState.areProblemsStale &&
795
- error?.sourceInformation
796
- ) {
797
- setErrorMarkers(editorModel, [
798
- {
799
- message: error.message,
800
- startLineNumber: error.sourceInformation.startLine,
801
- startColumn: error.sourceInformation.startColumn,
802
- endLineNumber: error.sourceInformation.endLine,
803
- endColumn: error.sourceInformation.endColumn,
804
- },
805
- ]);
806
- }
807
-
808
- if (
809
- !editorStore.graphState.areProblemsStale &&
810
- editorStore.graphState.warnings.length
811
- ) {
812
- setWarningMarkers(
813
- editorModel,
814
- editorStore.graphState.warnings
815
- .map((warning) => {
816
- if (!warning.sourceInformation) {
817
- return undefined;
818
- }
819
- return {
820
- message: warning.message,
821
- startLineNumber: warning.sourceInformation.startLine,
822
- startColumn: warning.sourceInformation.startColumn,
823
- endLineNumber: warning.sourceInformation.endLine,
824
- endColumn: warning.sourceInformation.endColumn,
825
- };
826
- })
827
- .filter(isNonNullable),
828
- );
829
- }
830
- }
831
- // Disable editing if user is in viewer mode
832
- editor.updateOptions({ readOnly: editorStore.editorMode.disableEditing });
833
-
834
- // hover
835
- hoverProviderDisposer.current?.dispose();
836
- hoverProviderDisposer.current = monacoLanguagesAPI.registerHoverProvider(
837
- CODE_EDITOR_LANGUAGE.PURE,
838
- {
839
- provideHover: (model, position) => {
840
- const currentWord = model.getWordAtPosition(position);
841
- if (!currentWord) {
842
- return { contents: [] };
843
- }
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
+ }
844
912
 
845
- // show documention for parser section
846
- const lineTextIncludingWordRange = {
847
- startLineNumber: position.lineNumber,
848
- startColumn: 1,
849
- endLineNumber: position.lineNumber,
850
- endColumn: currentWord.endColumn,
851
- };
852
- const lineTextIncludingWord = model.getValueInRange(
853
- lineTextIncludingWordRange,
854
- );
855
- // NOTE: we don't need to trim here since the leading whitespace in front of
856
- // the section header is considered invalid syntax in the grammar
857
- if (
858
- !hasWhiteSpace(lineTextIncludingWord) &&
859
- lineTextIncludingWord.startsWith(PARSER_SECTION_MARKER)
860
- ) {
861
- const parserKeyword = lineTextIncludingWord.substring(
862
- 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,
863
922
  );
864
- const doc = getParserDocumetation(editorStore, parserKeyword);
865
- if (doc) {
866
- return {
867
- range: lineTextIncludingWordRange,
868
- contents: [
869
- doc.markdownText
870
- ? {
871
- value: doc.markdownText.value,
872
- }
873
- : undefined,
874
- doc.url
875
- ? {
876
- value: `[See documentation](${doc.url})`,
877
- }
878
- : undefined,
879
- ].filter(isNonNullable),
880
- };
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
+ }
881
950
  }
882
- }
883
951
 
884
- // show documentation for parser element
885
- const textUntilPosition = model.getValueInRange({
886
- startLineNumber: 1,
887
- startColumn: 1,
888
- endLineNumber: position.lineNumber,
889
- endColumn: position.column,
890
- });
891
- const allParserSectionHeaders =
892
- // NOTE: since `###Pure` is implicitly considered as the first section, we prepend it to the text
893
- `${PARSER_SECTION_MARKER}${PURE_PARSER.PURE}\n${textUntilPosition}`
894
- .split('\n')
895
- .filter((line) => line.startsWith(PARSER_SECTION_MARKER));
896
- const currentSectionParserKeyword = getSectionParserNameFromLineText(
897
- allParserSectionHeaders[allParserSectionHeaders.length - 1] ?? '',
898
- );
899
- if (currentSectionParserKeyword) {
900
- const doc = getParserElementDocumentation(
901
- editorStore,
902
- currentSectionParserKeyword,
903
- currentWord.word,
904
- );
905
- if (doc) {
906
- return {
907
- range: {
908
- startLineNumber: position.lineNumber,
909
- startColumn: currentWord.startColumn,
910
- endLineNumber: position.lineNumber,
911
- endColumn: currentWord.endColumn,
912
- },
913
- contents: [
914
- doc.markdownText
915
- ? {
916
- value: doc.markdownText.value,
917
- }
918
- : undefined,
919
- doc.url
920
- ? {
921
- value: `[See documentation](${doc.url})`,
922
- }
923
- : undefined,
924
- ].filter(isNonNullable),
925
- };
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
+ }
926
997
  }
927
- }
928
998
 
929
- return { contents: [] };
999
+ return { contents: [] };
1000
+ },
930
1001
  },
931
- },
932
- );
1002
+ );
933
1003
 
934
- // suggestion
935
- suggestionProviderDisposer.current?.dispose();
936
- suggestionProviderDisposer.current =
937
- monacoLanguagesAPI.registerCompletionItemProvider(
938
- CODE_EDITOR_LANGUAGE.PURE,
939
- {
940
- // NOTE: we need to specify this to show suggestions for section
941
- // because by default, only alphanumeric characters trigger completion item provider
942
- // See https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.CompletionContext.html#triggerCharacter
943
- // See https://github.com/microsoft/monaco-editor/issues/2530#issuecomment-861757198
944
- triggerCharacters: ['#'],
945
- provideCompletionItems: (model, position) => {
946
- 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[] = [];
947
1017
 
948
- // suggestions for parser keyword
949
- suggestions = suggestions.concat(
950
- getParserKeywordSuggestions(
951
- position,
952
- model,
953
- collectParserKeywordSuggestions(editorStore),
954
- ),
955
- );
1018
+ // suggestions for parser keyword
1019
+ suggestions = suggestions.concat(
1020
+ getParserKeywordSuggestions(
1021
+ position,
1022
+ model,
1023
+ collectParserKeywordSuggestions(editorStore),
1024
+ ),
1025
+ );
956
1026
 
957
- // suggestions for parser element snippets
958
- suggestions = suggestions.concat(
959
- getParserElementSnippetSuggestions(
960
- position,
961
- model,
962
- (parserName: string) =>
963
- collectParserElementSnippetSuggestions(
964
- editorStore,
965
- parserName,
966
- ),
967
- ),
968
- );
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
+ );
969
1039
 
970
- // inline code snippet suggestions
971
- suggestions = suggestions.concat(
972
- getInlineSnippetSuggestions(position, model),
973
- );
1040
+ // inline code snippet suggestions
1041
+ suggestions = suggestions.concat(
1042
+ getInlineSnippetSuggestions(position, model),
1043
+ );
974
1044
 
975
- return { suggestions };
1045
+ return { suggestions };
1046
+ },
976
1047
  },
977
- },
978
- );
1048
+ );
1049
+ }
979
1050
  }
980
1051
 
981
1052
  function toggleAutoFoldingElements(): void {
@@ -989,80 +1060,7 @@ export const GrammarTextEditor = observer(() => {
989
1060
  [],
990
1061
  );
991
1062
  const foldingClass = editor?.getContribution('editor.contrib.folding');
992
-
993
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
994
- (foldingClass as any).getFoldingModel().then(
995
- (foldingModel: {
996
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
997
- _regions: any;
998
- onDidChange: (arg0: () => void) => void;
999
- getRegionAtLine: (arg0: unknown) => unknown;
1000
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1001
- getAllRegionsAtLine(arg0: unknown): any;
1002
- toggleCollapseState: (arg0: unknown) => unknown;
1003
- }) => {
1004
- const model = editor?.getModel();
1005
- const toggleFoldingLines: number[] = [];
1006
- model?.getLinesContent().forEach((line, j) => {
1007
- autoFoldingElements.forEach((elementName) => {
1008
- if (line.match(new RegExp(`^${elementName}`))) {
1009
- toggleFoldingLines.push(j + 2);
1010
- }
1011
- });
1012
- });
1013
- let allElementsFolded = true;
1014
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
1015
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
1016
- if (
1017
- foldingModel._regions.isCollapsed(
1018
- foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
1019
- .regionIndex,
1020
- ) === false
1021
- ) {
1022
- allElementsFolded = false;
1023
- }
1024
- }
1025
- });
1026
-
1027
- setFoldingElements(!allElementsFolded);
1028
-
1029
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
1030
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
1031
- if (
1032
- foldingModel._regions.isCollapsed(
1033
- foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
1034
- .regionIndex,
1035
- ) !== elementsFolded
1036
- ) {
1037
- foldingModel.toggleCollapseState(
1038
- foldingModel.getAllRegionsAtLine(foldingLineRegion),
1039
- );
1040
- }
1041
- }
1042
- });
1043
- },
1044
- );
1045
- }
1046
-
1047
- useEffect(() => {
1048
- if (editor && forcedCursorPosition) {
1049
- moveCursorToPosition(editor, forcedCursorPosition);
1050
- }
1051
- }, [editor, forcedCursorPosition]);
1052
-
1053
- useEffect(() => {
1054
- if (editor) {
1055
- const autoFoldingElements = editorStore.pluginManager
1056
- .getApplicationPlugins()
1057
- .flatMap(
1058
- (plugin) =>
1059
- (
1060
- plugin as DSL_LegendStudioApplicationPlugin_Extension
1061
- ).getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
1062
- [],
1063
- );
1064
- const foldingClass = editor.getContribution('editor.contrib.folding');
1065
-
1063
+ if (editor && foldingClass) {
1066
1064
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1067
1065
  (foldingClass as any).getFoldingModel().then(
1068
1066
  (foldingModel: {
@@ -1074,39 +1072,89 @@ export const GrammarTextEditor = observer(() => {
1074
1072
  getAllRegionsAtLine(arg0: unknown): any;
1075
1073
  toggleCollapseState: (arg0: unknown) => unknown;
1076
1074
  }) => {
1077
- foldingModel.onDidChange(() => {
1078
- const model = editor.getModel();
1079
- const toggleFoldingLines: number[] = [];
1080
- model?.getLinesContent().forEach((line, j) => {
1081
- autoFoldingElements.forEach((elementName) => {
1082
- if (line.match(new RegExp(`^${elementName}`))) {
1083
- toggleFoldingLines.push(j + 2);
1084
- }
1085
- });
1086
- });
1087
- let allElementsFolded = true;
1088
- toggleFoldingLines.forEach((foldingLineRegion, i) => {
1089
- if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
1090
- if (
1091
- foldingModel._regions.isCollapsed(
1092
- foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
1093
- .regionIndex,
1094
- ) === false
1095
- ) {
1096
- allElementsFolded = false;
1097
- }
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);
1098
1081
  }
1099
1082
  });
1100
-
1101
- setFoldingElements(!allElementsFolded);
1102
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);
1103
1100
  },
1104
1101
  );
1105
1102
  }
1106
- });
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]);
1107
1153
 
1154
+ // first load with grammar. auto fold element sections
1108
1155
  useEffect(() => {
1109
1156
  if (editor) {
1157
+ const model = editor.getModel();
1110
1158
  const autoFoldingElements = editorStore.pluginManager
1111
1159
  .getApplicationPlugins()
1112
1160
  .flatMap(
@@ -1117,33 +1165,38 @@ export const GrammarTextEditor = observer(() => {
1117
1165
  [],
1118
1166
  );
1119
1167
  const foldingClass = editor.getContribution('editor.contrib.folding');
1120
-
1121
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1122
- (foldingClass as any)
1123
- .getFoldingModel()
1124
- .then(
1125
- (foldingModel: {
1126
- onDidChange: (arg0: () => void) => void;
1127
- getRegionAtLine: (arg0: unknown) => unknown;
1128
- getAllRegionsAtLine(arg0: unknown): unknown;
1129
- toggleCollapseState: (arg0: unknown) => unknown;
1130
- }) => {
1131
- const model = editor.getModel();
1132
- const foldingLines: number[] = [];
1133
- model?.getLinesContent().forEach((line, j) => {
1134
- autoFoldingElements.forEach((elementName) => {
1135
- if (line.match(new RegExp(`^${elementName}`))) {
1136
- 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;
1137
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
+ });
1138
1189
  });
1139
- });
1140
- foldingLines.forEach((foldingLineRegion, i) => {
1141
- foldingModel.toggleCollapseState(
1142
- foldingModel.getAllRegionsAtLine(foldingLineRegion),
1143
- );
1144
- });
1190
+ elementLinesToBeFolded.forEach((lineToBeFolded) => {
1191
+ const regionToFold =
1192
+ foldingModel.getAllRegionsAtLine(lineToBeFolded);
1193
+ foldingModel.toggleCollapseState(regionToFold);
1194
+ });
1195
+ setFoldingElements(true);
1196
+ }
1145
1197
  },
1146
1198
  );
1199
+ }
1147
1200
  }
1148
1201
  }, [editor, editorStore.pluginManager]);
1149
1202
 
@@ -1169,19 +1222,6 @@ export const GrammarTextEditor = observer(() => {
1169
1222
  <div className="panel editor-group">
1170
1223
  <div className="panel__header editor-group__header">
1171
1224
  <div className="editor-group__header__tabs">
1172
- <div className="editor-group__text-mode__tab">
1173
- <button
1174
- className="editor-group__text-mode__tab__label"
1175
- disabled={
1176
- editorStore.graphState.isApplicationLeavingGraphEditMode
1177
- }
1178
- onClick={leaveTextMode}
1179
- tabIndex={-1}
1180
- title="Click to exit text mode and go back to form mode"
1181
- >
1182
- <MoreHorizontalIcon />
1183
- </button>
1184
- </div>
1185
1225
  <ContextMenu
1186
1226
  className="editor-group__text-mode__tab editor-group__text-mode__tab--active"
1187
1227
  content={<GrammarTextEditorHeaderTabContextMenu />}
@@ -1192,39 +1232,66 @@ export const GrammarTextEditor = observer(() => {
1192
1232
  </ContextMenu>
1193
1233
  </div>
1194
1234
  <div className="editor-group__header__actions">
1195
- <button
1196
- className={clsx('editor-group__header__action', {
1197
- 'editor-group__header__action--active':
1198
- grammarTextEditorState.wrapText,
1199
- })}
1200
- onClick={toggleWordWrap}
1201
- tabIndex={-1}
1202
- title={`[${
1203
- grammarTextEditorState.wrapText ? 'on' : 'off'
1204
- }] Toggle word wrap`}
1205
- >
1206
- <WordWrapIcon className="editor-group__icon__word-wrap" />
1207
- </button>
1208
- <button
1209
- className={clsx('editor-group__header__action', {
1210
- 'editor-group__header__action--active': elementsFolded,
1211
- })}
1212
- onClick={toggleAutoFoldingElements}
1213
- tabIndex={-1}
1214
- title={`${
1215
- !elementsFolded ? 'Unfold' : 'Fold'
1216
- } auto-folding elements`}
1217
- >
1218
- {!elementsFolded && (
1219
- <UnfoldIcon className="editor-group__icon__word-wrap" />
1220
- )}
1221
- {elementsFolded && (
1222
- <FoldIcon className="editor-group__icon__word-wrap" />
1223
- )}
1224
- </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>
1225
1289
  </div>
1226
1290
  </div>
1227
1291
  <PanelContent className="editor-group__content">
1292
+ <PanelLoadingIndicator
1293
+ isLoading={editorStore.graphState.isRunningGlobalCompile}
1294
+ />
1228
1295
  <div className="code-editor__container">
1229
1296
  <div className="code-editor__body" ref={textEditorRef} />
1230
1297
  </div>