@finos/legend-application-studio 28.13.15 → 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.
- package/lib/__lib__/LegendStudioEvent.d.ts +4 -1
- package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioEvent.js +4 -0
- package/lib/__lib__/LegendStudioEvent.js.map +1 -1
- package/lib/components/editor/editor-group/EditorGroup.js +1 -1
- package/lib/components/editor/editor-group/EditorGroup.js.map +1 -1
- package/lib/components/editor/editor-group/GrammarTextEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/GrammarTextEditor.js +258 -219
- package/lib/components/editor/editor-group/GrammarTextEditor.js.map +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.js +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/editor/EditorGraphState.d.ts.map +1 -1
- package/lib/stores/editor/EditorGraphState.js +8 -1
- package/lib/stores/editor/EditorGraphState.js.map +1 -1
- package/lib/stores/editor/GraphEditFormModeState.js +1 -1
- package/lib/stores/editor/GraphEditFormModeState.js.map +1 -1
- package/lib/stores/editor/GraphEditGrammarModeState.d.ts +4 -0
- package/lib/stores/editor/GraphEditGrammarModeState.d.ts.map +1 -1
- package/lib/stores/editor/GraphEditGrammarModeState.js +29 -2
- package/lib/stores/editor/GraphEditGrammarModeState.js.map +1 -1
- package/lib/stores/editor/editor-state/GrammarTextEditorState.d.ts +2 -1
- package/lib/stores/editor/editor-state/GrammarTextEditorState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/GrammarTextEditorState.js +5 -2
- package/lib/stores/editor/editor-state/GrammarTextEditorState.js.map +1 -1
- package/package.json +1 -1
- package/src/__lib__/LegendStudioEvent.ts +5 -0
- package/src/components/editor/editor-group/EditorGroup.tsx +1 -1
- package/src/components/editor/editor-group/GrammarTextEditor.tsx +418 -355
- package/src/components/workspace-setup/WorkspaceSetup.tsx +1 -1
- package/src/stores/editor/EditorGraphState.ts +12 -0
- package/src/stores/editor/GraphEditFormModeState.ts +1 -1
- package/src/stores/editor/GraphEditGrammarModeState.ts +49 -1
- 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 {
|
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
|
-
}, [
|
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
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
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
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
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
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
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
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
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
|
-
|
999
|
+
return { contents: [] };
|
1000
|
+
},
|
934
1001
|
},
|
935
|
-
|
936
|
-
);
|
1002
|
+
);
|
937
1003
|
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
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
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
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
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
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
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
1040
|
+
// inline code snippet suggestions
|
1041
|
+
suggestions = suggestions.concat(
|
1042
|
+
getInlineSnippetSuggestions(position, model),
|
1043
|
+
);
|
978
1044
|
|
979
|
-
|
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
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
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
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
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
|
-
|
1145
|
-
|
1146
|
-
foldingModel.
|
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
|
-
<
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
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>
|