@finos/legend-application-studio
Version:
Legend Studio application core
845 lines • 46.9 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect, useState, useRef, useCallback, forwardRef } from 'react';
import { observer } from 'mobx-react-lite';
import { editor as monacoEditorAPI, languages as monacoLanguagesAPI, KeyCode, KeyMod, } from 'monaco-editor';
import { ContextMenu, PanelContent, MenuContent, MenuContentItem, PanelLoadingIndicator, CaretDownIcon, ControlledDropdownMenu, MenuContentItemIcon, CheckIcon, MenuContentItemLabel, Dialog, ModalFooter, Modal, ModalHeader, ModalBody, ModalTitle, ModalFooterButton, } from '@finos/legend-art';
import { DEFAULT_TAB_SIZE, useApplicationStore, useApplicationNavigationContext, } from '@finos/legend-application';
import { getBaseCodeEditorOptions, resetLineNumberGutterWidth, getCodeEditorValue, normalizeLineEnding, setWarningMarkers, clearMarkers, setErrorMarkers, moveCursorToPosition, CODE_EDITOR_THEME, CODE_EDITOR_LANGUAGE, getInlineSnippetSuggestions, getParserKeywordSuggestions, getParserElementSnippetSuggestions, getSectionParserNameFromLineText, } from '@finos/legend-code-editor';
import { disposeCodeEditor } from '@finos/legend-lego/code-editor';
import { CORE_DND_TYPE, } from '../../../stores/editor/utils/DnDUtils.js';
import { useDrop } from 'react-dnd';
import { flowResult } from 'mobx';
import { useEditorStore } from '../EditorStoreProvider.js';
import { LogEvent, assertErrorThrown, assertTrue, hasWhiteSpace, isNonNullable, } from '@finos/legend-shared';
import { ELEMENT_PATH_DELIMITER, PARSER_SECTION_MARKER, PURE_CONNECTION_NAME, PURE_ELEMENT_NAME, PURE_PARSER, isValidFullPath, } from '@finos/legend-graph';
import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../../__lib__/LegendStudioDocumentation.js';
import { BLANK_CLASS_SNIPPET, CLASS_WITH_CONSTRAINT_SNIPPET, CLASS_WITH_INHERITANCE_SNIPPET, CLASS_WITH_PROPERTY_SNIPPET, DATA_WITH_EXTERNAL_FORMAT_SNIPPET, DATA_WITH_MODEL_STORE_SNIPPET, createDataElementSnippetWithEmbeddedDataSuggestionSnippet, SIMPLE_PROFILE_SNIPPET, SIMPLE_ENUMERATION_SNIPPET, SIMPLE_ASSOCIATION_SNIPPET, SIMPLE_MEASURE_SNIPPET, BLANK_FUNCTION_SNIPPET, SIMPLE_FUNCTION_SNIPPET, SIMPLE_RUNTIME_SNIPPET, JSON_MODEL_CONNECTION_SNIPPET, XML_MODEL_CONNECTION_SNIPPET, MODEL_CHAIN_CONNECTION_SNIPPET, RELATIONAL_DATABASE_CONNECTION_SNIPPET, BLANK_RELATIONAL_DATABASE_SNIPPET, SIMPLE_GENERATION_SPECIFICATION_SNIPPET, BLANK_SERVICE_SNIPPET, SERVICE_WITH_SINGLE_EXECUTION_SNIPPET, SERVICE_WITH_MULTI_EXECUTION_SNIPPET, BLANK_MAPPING_SNIPPET, MAPPING_WITH_M2M_CLASS_MAPPING_SNIPPET, MAPPING_WITH_ENUMERATION_MAPPING_SNIPPET, MAPPING_WITH_RELATIONAL_CLASS_MAPPING_SNIPPET, POST_PROCESSOR_RELATIONAL_DATABASE_CONNECTION_SNIPPET, createConnectionSnippetWithPostProcessorSuggestionSnippet, } from '../../../__lib__/LegendStudioCodeSnippet.js';
import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../__lib__/LegendStudioApplicationNavigationContext.js';
import { LEGEND_STUDIO_SETTING_KEY } from '../../../__lib__/LegendStudioSetting.js';
import { GRAMMAR_MODE_EDITOR_ACTION, GraphEditGrammarModeState, } from '../../../stores/editor/GraphEditGrammarModeState.js';
import { LEGEND_STUDIO_APP_EVENT } from '../../../__lib__/LegendStudioEvent.js';
import { FileSystem_FileViewer } from './ArtifactGenerationViewer.js';
export const GrammarTextEditorHeaderTabContextMenu = observer(forwardRef(function GrammarTextEditorHeaderTabContextMenu(props, ref) {
const editorStore = useEditorStore();
const applicationStore = useApplicationStore();
const leaveTextMode = applicationStore.guardUnhandledError(() => flowResult(editorStore.toggleTextMode()));
return (_jsx(MenuContent, { ref: ref, children: _jsx(MenuContentItem, { onClick: leaveTextMode, children: "Leave Text Mode" }) }));
}));
const getParserDocumetation = (editorStore, parserKeyword) => {
switch (parserKeyword) {
case PURE_PARSER.PURE: {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_PURE);
}
case PURE_PARSER.MAPPING: {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_MAPPING);
}
case PURE_PARSER.CONNECTION: {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_CONNECTION);
}
case PURE_PARSER.RUNTIME: {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_RUNTIME);
}
case PURE_PARSER.SERVICE: {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_SERVICE);
}
case PURE_PARSER.RELATIONAL: {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_RELATIONAL);
}
case PURE_PARSER.FILE_GENERATION_SPECIFICATION: {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_FILE_GENERATION);
}
case PURE_PARSER.GENERATION_SPECIFICATION: {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_GENERATION_SPECIFICATION);
}
case PURE_PARSER.DATA: {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_DATA);
}
default: {
const parserDocumentationGetters = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraPureGrammarParserDocumentationGetters?.() ?? []);
for (const docGetter of parserDocumentationGetters) {
const doc = docGetter(editorStore, parserKeyword);
if (doc) {
return doc;
}
}
}
}
return undefined;
};
const getParserElementDocumentation = (editorStore, parserKeyword, elementKeyword) => {
switch (parserKeyword) {
case PURE_PARSER.PURE: {
if (elementKeyword === PURE_ELEMENT_NAME.CLASS) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_CLASS);
}
else if (elementKeyword === PURE_ELEMENT_NAME.PROFILE) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_PROFILE);
}
else if (elementKeyword === PURE_ELEMENT_NAME.ENUMERATION) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_ENUMERATION);
}
else if (elementKeyword === PURE_ELEMENT_NAME.MEASURE) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_MEASURE);
}
else if (elementKeyword === PURE_ELEMENT_NAME.ASSOCIATION) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_ASSOCIATION);
}
else if (elementKeyword === PURE_ELEMENT_NAME.FUNCTION) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_FUNCTION);
}
return undefined;
}
case PURE_PARSER.MAPPING: {
if (elementKeyword === PURE_ELEMENT_NAME.MAPPING) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_MAPPING);
}
return undefined;
}
case PURE_PARSER.CONNECTION: {
if (elementKeyword === PURE_CONNECTION_NAME.JSON_MODEL_CONNECTION) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_CONNECTION_JSON_MODEL_CONNECTION);
}
else if (elementKeyword === PURE_CONNECTION_NAME.XML_MODEL_CONNECTION) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_CONNECTION_XML_MODEL_CONNECTION);
}
else if (elementKeyword === PURE_CONNECTION_NAME.MODEL_CHAIN_CONNECTION) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_CONNECTION_MODEL_CHAIN_CONNECTION);
}
else if (elementKeyword === PURE_CONNECTION_NAME.RELATIONAL_DATABASE_CONNECTION) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_CONNECTION_RELATIONAL_DATABASE_CONNECTION);
}
// TODO: introduce extension mechanism
return undefined;
}
case PURE_PARSER.RUNTIME: {
if (elementKeyword === PURE_ELEMENT_NAME.RUNTIME) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_RUNTIME);
}
return undefined;
}
case PURE_PARSER.SERVICE: {
if (elementKeyword === PURE_ELEMENT_NAME.SERVICE) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_SERVICE);
}
return undefined;
}
case PURE_PARSER.RELATIONAL: {
if (elementKeyword === PURE_ELEMENT_NAME.DATABASE) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_DATABASE);
}
return undefined;
}
case PURE_PARSER.FILE_GENERATION_SPECIFICATION: {
if (elementKeyword === PURE_ELEMENT_NAME.FILE_GENERATION) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_FILE_GENERATION_SPECIFICATION);
}
return undefined;
}
case PURE_PARSER.GENERATION_SPECIFICATION: {
if (elementKeyword === PURE_ELEMENT_NAME.GENERATION_SPECIFICATION) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.CONCEPT_ELEMENT_GENERATION_SPECIFICATION);
}
return undefined;
}
case PURE_PARSER.DATA: {
if (elementKeyword === PURE_ELEMENT_NAME.DATA_ELEMENT) {
return editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_DATA);
}
return undefined;
}
default: {
const parserElementDocumentationGetters = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraPureGrammarParserElementDocumentationGetters?.() ?? []);
for (const docGetter of parserElementDocumentationGetters) {
const doc = docGetter(editorStore, parserKeyword, elementKeyword);
if (doc) {
return doc;
}
}
}
}
return undefined;
};
const collectParserKeywordSuggestions = (editorStore) => {
const parserKeywordSuggestions = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraPureGrammarParserKeywordSuggestionGetters?.() ?? [])
.flatMap((suggestionGetter) => suggestionGetter(editorStore));
return [
{
text: PURE_PARSER.PURE,
description: `(core Pure)`,
documentation: editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_PURE),
insertText: PURE_PARSER.PURE,
},
{
text: PURE_PARSER.MAPPING,
description: `(dsl)`,
documentation: editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_MAPPING),
insertText: PURE_PARSER.MAPPING,
},
{
text: PURE_PARSER.CONNECTION,
description: `(dsl)`,
documentation: editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_CONNECTION),
insertText: PURE_PARSER.CONNECTION,
},
{
text: PURE_PARSER.RUNTIME,
description: `(dsl)`,
documentation: editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_RUNTIME),
insertText: PURE_PARSER.RUNTIME,
},
{
text: PURE_PARSER.RELATIONAL,
description: `(external store)`,
documentation: editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_RELATIONAL),
insertText: PURE_PARSER.RELATIONAL,
},
{
text: PURE_PARSER.SERVICE,
description: `(dsl)`,
documentation: editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_SERVICE),
insertText: PURE_PARSER.SERVICE,
},
{
text: PURE_PARSER.GENERATION_SPECIFICATION,
description: `(dsl)`,
documentation: editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_GENERATION_SPECIFICATION),
insertText: PURE_PARSER.GENERATION_SPECIFICATION,
},
{
text: PURE_PARSER.FILE_GENERATION_SPECIFICATION,
description: `(dsl)`,
documentation: editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_FILE_GENERATION),
insertText: PURE_PARSER.FILE_GENERATION_SPECIFICATION,
},
{
text: PURE_PARSER.DATA,
description: `(dsl)`,
documentation: editorStore.applicationStore.documentationService.getDocEntry(LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER_DATA),
insertText: PURE_PARSER.DATA,
},
...parserKeywordSuggestions,
];
};
const collectParserElementSnippetSuggestions = (editorStore, parserKeyword) => {
switch (parserKeyword) {
case PURE_PARSER.PURE: {
return [
// class
{
text: PURE_ELEMENT_NAME.CLASS,
description: '(blank)',
insertText: BLANK_CLASS_SNIPPET,
},
{
text: PURE_ELEMENT_NAME.CLASS,
description: 'with property',
insertText: CLASS_WITH_PROPERTY_SNIPPET,
},
{
text: PURE_ELEMENT_NAME.CLASS,
description: 'with inheritance',
insertText: CLASS_WITH_INHERITANCE_SNIPPET,
},
{
text: PURE_ELEMENT_NAME.CLASS,
description: 'with constraint',
insertText: CLASS_WITH_CONSTRAINT_SNIPPET,
},
// profile
{
text: PURE_ELEMENT_NAME.PROFILE,
insertText: SIMPLE_PROFILE_SNIPPET,
},
// enumeration
{
text: PURE_ELEMENT_NAME.ENUMERATION,
insertText: SIMPLE_ENUMERATION_SNIPPET,
},
// association
{
text: PURE_ELEMENT_NAME.ASSOCIATION,
insertText: SIMPLE_ASSOCIATION_SNIPPET,
},
// measure
{
text: PURE_ELEMENT_NAME.MEASURE,
insertText: SIMPLE_MEASURE_SNIPPET,
},
// function
{
text: PURE_ELEMENT_NAME.FUNCTION,
description: '(blank)',
insertText: BLANK_FUNCTION_SNIPPET,
},
{
text: PURE_ELEMENT_NAME.FUNCTION,
insertText: SIMPLE_FUNCTION_SNIPPET,
},
];
}
case PURE_PARSER.MAPPING: {
return [
{
text: PURE_ELEMENT_NAME.MAPPING,
description: '(blank)',
insertText: BLANK_MAPPING_SNIPPET,
},
{
text: PURE_ELEMENT_NAME.MAPPING,
description: 'with model-to-model mapping',
insertText: MAPPING_WITH_M2M_CLASS_MAPPING_SNIPPET,
},
{
text: PURE_ELEMENT_NAME.MAPPING,
description: 'with relational mapping',
insertText: MAPPING_WITH_RELATIONAL_CLASS_MAPPING_SNIPPET,
},
{
text: PURE_ELEMENT_NAME.MAPPING,
description: 'with enumeration mapping',
insertText: MAPPING_WITH_ENUMERATION_MAPPING_SNIPPET,
},
];
}
case PURE_PARSER.CONNECTION: {
const embeddedPostProcessorSnippetSuggestions = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraPostProcessorSnippetSuggestions?.() ?? []);
const connectionSnippetSuggestions = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraNewConnectionSnippetSuggestions?.() ?? []);
return [
{
text: PURE_CONNECTION_NAME.JSON_MODEL_CONNECTION,
description: 'JSON model connection',
insertText: JSON_MODEL_CONNECTION_SNIPPET,
},
{
text: PURE_CONNECTION_NAME.XML_MODEL_CONNECTION,
description: 'XML model connection',
insertText: XML_MODEL_CONNECTION_SNIPPET,
},
{
text: PURE_CONNECTION_NAME.MODEL_CHAIN_CONNECTION,
description: 'model chain connection',
insertText: MODEL_CHAIN_CONNECTION_SNIPPET,
},
{
text: PURE_CONNECTION_NAME.RELATIONAL_DATABASE_CONNECTION,
description: 'relational database connection',
insertText: RELATIONAL_DATABASE_CONNECTION_SNIPPET,
},
{
text: PURE_CONNECTION_NAME.RELATIONAL_DATABASE_CONNECTION,
description: 'relational database connection with post-processor',
insertText: POST_PROCESSOR_RELATIONAL_DATABASE_CONNECTION_SNIPPET,
},
...connectionSnippetSuggestions.map((suggestion) => ({
text: suggestion.text,
description: suggestion.description,
insertText: suggestion.insertText,
})),
...embeddedPostProcessorSnippetSuggestions.map((suggestion) => ({
text: PURE_CONNECTION_NAME.RELATIONAL_DATABASE_CONNECTION,
description: suggestion.description,
insertText: createConnectionSnippetWithPostProcessorSuggestionSnippet(suggestion.text),
})),
];
}
case PURE_PARSER.RUNTIME: {
return [
{
text: PURE_ELEMENT_NAME.RUNTIME,
insertText: SIMPLE_RUNTIME_SNIPPET,
},
];
}
case PURE_PARSER.SERVICE: {
return [
{
text: PURE_ELEMENT_NAME.SERVICE,
description: '(blank)',
insertText: BLANK_SERVICE_SNIPPET,
},
{
text: PURE_ELEMENT_NAME.SERVICE,
description: 'with single execution',
insertText: SERVICE_WITH_SINGLE_EXECUTION_SNIPPET,
},
{
text: PURE_ELEMENT_NAME.SERVICE,
description: 'with multi execution',
insertText: SERVICE_WITH_MULTI_EXECUTION_SNIPPET,
},
];
}
case PURE_PARSER.RELATIONAL: {
return [
{
text: PURE_ELEMENT_NAME.DATABASE,
description: '(blank)',
insertText: BLANK_RELATIONAL_DATABASE_SNIPPET,
},
];
}
case PURE_PARSER.FILE_GENERATION_SPECIFICATION: {
return [
// TODO?: add extension mechanism for suggestion for different file generations
];
}
case PURE_PARSER.GENERATION_SPECIFICATION: {
return [
{
text: PURE_ELEMENT_NAME.GENERATION_SPECIFICATION,
description: '(blank)',
insertText: SIMPLE_GENERATION_SPECIFICATION_SNIPPET,
},
];
}
case PURE_PARSER.DATA: {
const embeddedDateSnippetSuggestions = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraEmbeddedDataSnippetSuggestions?.() ?? []);
return [
{
text: PURE_ELEMENT_NAME.DATA_ELEMENT,
description: 'with external format',
insertText: DATA_WITH_EXTERNAL_FORMAT_SNIPPET,
},
{
text: PURE_ELEMENT_NAME.DATA_ELEMENT,
description: 'using model store',
insertText: DATA_WITH_MODEL_STORE_SNIPPET,
},
...embeddedDateSnippetSuggestions.map((suggestion) => ({
text: PURE_ELEMENT_NAME.DATA_ELEMENT,
description: suggestion.description,
insertText: createDataElementSnippetWithEmbeddedDataSuggestionSnippet(suggestion.text),
})),
];
}
default: {
const parserElementSnippetSuggestionsGetters = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraPureGrammarParserElementSnippetSuggestionsGetters?.() ??
[]);
for (const snippetSuggestionsGetter of parserElementSnippetSuggestionsGetters) {
const snippetSuggestions = snippetSuggestionsGetter(editorStore, parserKeyword);
if (snippetSuggestions) {
return snippetSuggestions;
}
}
}
}
return [];
};
const resolveElementPathFromCurrentPosition = (_editor, grammarModeState) => {
let elementPath = '';
try {
const model = _editor.getModel();
let position = _editor.getPosition();
let maxWords = 0;
if (model) {
while (position && maxWords < 30) {
const currentWord = model.getWordAtPosition(position);
const lineNumber = position.lineNumber;
position = null;
maxWords += 1;
if (currentWord) {
const wordStartPost = {
lineNumber: lineNumber,
column: currentWord.startColumn,
};
const startPost = model.modifyPosition(wordStartPost, -2);
elementPath = currentWord.word + elementPath;
const pathDelimiterRange = {
startLineNumber: startPost.lineNumber,
startColumn: startPost.column,
endLineNumber: wordStartPost.lineNumber,
endColumn: wordStartPost.column,
};
const packageRange = model.getValueInRange(model.validateRange(pathDelimiterRange));
if (packageRange === ELEMENT_PATH_DELIMITER) {
elementPath = packageRange + elementPath;
position = model.modifyPosition(startPost, -1);
}
}
}
}
assertTrue(isValidFullPath(elementPath), `Unable to go to element definition. Not valid element path: ${elementPath}`);
return elementPath;
}
catch (error) {
assertErrorThrown(error);
grammarModeState.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.TEXT_MODE_ACTION_KEYBOARD_SHORTCUT_GO_TO_DEFINITION__ERROR), error);
}
return undefined;
};
const goToElement = (_editor, grammarModeState) => {
grammarModeState.editorStore.applicationStore.logService.info(LogEvent.create(LEGEND_STUDIO_APP_EVENT.TEXT_MODE_ACTION_KEYBOARD_SHORTCUT_GO_TO_DEFINITION__LAUNCH));
const elementPath = resolveElementPathFromCurrentPosition(_editor, grammarModeState);
if (elementPath) {
flowResult(grammarModeState.goToElement(elementPath)).catch(grammarModeState.editorStore.applicationStore.alertUnhandledError);
}
};
const FileSystem_FileModal = observer((props) => {
const { graphEditGrammarModeState, generatedFile } = props;
const applicationStore = graphEditGrammarModeState.editorStore.applicationStore;
const closeModal = () => graphEditGrammarModeState.setGeneratedFile(undefined);
return (_jsx(Dialog, { open: true, classes: {
root: 'editor-modal__root-container',
container: 'editor-modal__container',
paper: 'editor-modal__content',
}, onClose: closeModal, children: _jsxs(Modal, { darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled, className: "editor-modal", children: [_jsx(ModalHeader, { children: _jsx(ModalTitle, { title: "Generated Artifact" }) }), _jsx(ModalBody, { children: _jsx(FileSystem_FileViewer, { generatedArtifact: generatedFile, visitGenerator: undefined, generator: undefined }) }), _jsx(ModalFooter, { children: _jsx(ModalFooterButton, { onClick: closeModal, text: "Close", type: "secondary" }) })] }) }));
});
export const GrammarTextEditor = observer(() => {
const [editor, setEditor] = useState();
const editorStore = useEditorStore();
const applicationStore = useApplicationStore();
const grammarModeState = editorStore.getGraphEditorMode(GraphEditGrammarModeState);
const grammarTextEditorState = grammarModeState.grammarTextEditorState;
const error = editorStore.graphState.error;
const warnings = editorStore.graphState.warnings;
const [elementsFolded, setFoldingElements] = useState(false);
const forcedCursorPosition = grammarTextEditorState.forcedCursorPosition;
const wordWrapOtion = grammarTextEditorState.wordWrapOtion;
const value = normalizeLineEnding(grammarTextEditorState.graphGrammarText);
const ref = useRef(null);
const hoverProviderDisposer = useRef(undefined);
const suggestionProviderDisposer = useRef(undefined);
const leaveTextMode = applicationStore.guardUnhandledError(() => flowResult(editorStore.toggleTextMode()));
const globalCompile = applicationStore.guardUnhandledError(() => flowResult(grammarModeState.globalCompile()));
const toggleWordWrap = () => {
grammarTextEditorState.setWrapText(!grammarTextEditorState.wrapText);
editorStore.applicationStore.settingService.persistValue(LEGEND_STUDIO_SETTING_KEY.EDITOR_WRAP_TEXT, grammarTextEditorState.wrapText);
};
useEffect(() => {
if (!editor && ref.current) {
const element = ref.current;
const _editor = monacoEditorAPI.create(element, {
...getBaseCodeEditorOptions(),
language: CODE_EDITOR_LANGUAGE.PURE,
theme: CODE_EDITOR_THEME.DEFAULT_DARK,
renderValidationDecorations: 'on',
wordWrap: grammarTextEditorState.wordWrapOtion,
readOnly: editorStore.editorMode.disableEditing,
});
_editor.onDidChangeModelContent(() => {
grammarTextEditorState.setGraphGrammarText(getCodeEditorValue(_editor));
clearMarkers();
// NOTE: we can technically can reset the current element label regex string here
// but if we do that on first load, the cursor will not jump to the current element
// also, it's better to place that logic in an effect that watches for the regex string.
// this is done by watching `forcedCursorPosition` in the useEffect
});
_editor.focus(); // focus on the editor initially
_editor.getModel()?.updateOptions({ tabSize: DEFAULT_TAB_SIZE });
_editor.addAction({
id: GRAMMAR_MODE_EDITOR_ACTION.GO_TO_ELEMENT_DEFINITION,
label: 'Go To Element',
keybindings: [KeyMod.CtrlCmd | KeyCode.KeyB],
run: (ed) => {
goToElement(ed, grammarModeState);
},
});
setEditor(_editor);
}
}, [
editorStore,
applicationStore,
editor,
grammarTextEditorState,
grammarModeState,
]);
// Drag and Drop
const extraElementDragTypes = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraPureGrammarTextEditorDragElementTypes?.() ?? []);
const handleDrop = useCallback((item) => {
if (editor) {
editor.trigger('keyboard', 'type', {
text: item.data.packageableElement.path,
});
}
}, [editor]);
const [, dropConnector] = useDrop(() => ({
accept: [
...extraElementDragTypes,
CORE_DND_TYPE.PROJECT_EXPLORER_PACKAGE,
CORE_DND_TYPE.PROJECT_EXPLORER_CLASS,
CORE_DND_TYPE.PROJECT_EXPLORER_ASSOCIATION,
CORE_DND_TYPE.PROJECT_EXPLORER_MEASURE,
CORE_DND_TYPE.PROJECT_EXPLORER_ENUMERATION,
CORE_DND_TYPE.PROJECT_EXPLORER_PROFILE,
CORE_DND_TYPE.PROJECT_EXPLORER_FUNCTION,
CORE_DND_TYPE.PROJECT_EXPLORER_FLAT_DATA,
CORE_DND_TYPE.PROJECT_EXPLORER_DATABASE,
CORE_DND_TYPE.PROJECT_EXPLORER_MAPPING,
CORE_DND_TYPE.PROJECT_EXPLORER_SERVICE,
CORE_DND_TYPE.PROJECT_EXPLORER_CONNECTION,
CORE_DND_TYPE.PROJECT_EXPLORER_RUNTIME,
CORE_DND_TYPE.PROJECT_EXPLORER_FILE_GENERATION,
CORE_DND_TYPE.PROJECT_EXPLORER_GENERATION_TREE,
CORE_DND_TYPE.PROJECT_EXPLORER_DATA,
],
drop: (item) => handleDrop(item),
}), [extraElementDragTypes, handleDrop]);
dropConnector(ref);
if (editor) {
// Set the value of the editor
const currentValue = getCodeEditorValue(editor);
if (currentValue !== value) {
editor.setValue(value);
}
resetLineNumberGutterWidth(editor);
const editorModel = editor.getModel();
if (editorModel) {
// hover
hoverProviderDisposer.current?.dispose();
hoverProviderDisposer.current = monacoLanguagesAPI.registerHoverProvider(CODE_EDITOR_LANGUAGE.PURE, {
provideHover: (model, position) => {
const currentWord = model.getWordAtPosition(position);
if (!currentWord) {
return { contents: [] };
}
// show documention for parser section
const lineTextIncludingWordRange = {
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: currentWord.endColumn,
};
const lineTextIncludingWord = model.getValueInRange(lineTextIncludingWordRange);
// NOTE: we don't need to trim here since the leading whitespace in front of
// the section header is considered invalid syntax in the grammar
if (!hasWhiteSpace(lineTextIncludingWord) &&
lineTextIncludingWord.startsWith(PARSER_SECTION_MARKER)) {
const parserKeyword = lineTextIncludingWord.substring(PARSER_SECTION_MARKER.length);
const doc = getParserDocumetation(editorStore, parserKeyword);
if (doc) {
return {
range: lineTextIncludingWordRange,
contents: [
doc.markdownText
? {
value: doc.markdownText.value,
}
: undefined,
doc.url
? {
value: `[See documentation](${doc.url})`,
}
: undefined,
].filter(isNonNullable),
};
}
}
// show documentation for parser element
const textUntilPosition = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const allParserSectionHeaders =
// NOTE: since `###Pure` is implicitly considered as the first section, we prepend it to the text
`${PARSER_SECTION_MARKER}${PURE_PARSER.PURE}\n${textUntilPosition}`
.split('\n')
.filter((line) => line.startsWith(PARSER_SECTION_MARKER));
const currentSectionParserKeyword = getSectionParserNameFromLineText(allParserSectionHeaders[allParserSectionHeaders.length - 1] ??
'');
if (currentSectionParserKeyword) {
const doc = getParserElementDocumentation(editorStore, currentSectionParserKeyword, currentWord.word);
if (doc) {
return {
range: {
startLineNumber: position.lineNumber,
startColumn: currentWord.startColumn,
endLineNumber: position.lineNumber,
endColumn: currentWord.endColumn,
},
contents: [
doc.markdownText
? {
value: doc.markdownText.value,
}
: undefined,
doc.url
? {
value: `[See documentation](${doc.url})`,
}
: undefined,
].filter(isNonNullable),
};
}
}
return { contents: [] };
},
});
// suggestion
suggestionProviderDisposer.current?.dispose();
suggestionProviderDisposer.current =
monacoLanguagesAPI.registerCompletionItemProvider(CODE_EDITOR_LANGUAGE.PURE, {
// NOTE: we need to specify this to show suggestions for section
// because by default, only alphanumeric characters trigger completion item provider
// See https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.CompletionContext.html#triggerCharacter
// See https://github.com/microsoft/monaco-editor/issues/2530#issuecomment-861757198
triggerCharacters: ['#'],
provideCompletionItems: (model, position) => {
let suggestions = [];
// suggestions for parser keyword
suggestions = suggestions.concat(getParserKeywordSuggestions(position, model, collectParserKeywordSuggestions(editorStore)));
// suggestions for parser element snippets
suggestions = suggestions.concat(getParserElementSnippetSuggestions(position, model, (parserName) => collectParserElementSnippetSuggestions(editorStore, parserName)));
// inline code snippet suggestions
suggestions = suggestions.concat(getInlineSnippetSuggestions(position, model));
return { suggestions };
},
});
}
}
function toggleAutoFoldingElements() {
const autoFoldingElements = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
[]);
const foldingClass = editor?.getContribution('editor.contrib.folding');
if (editor && foldingClass) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
foldingClass.getFoldingModel().then((foldingModel) => {
const model = editor.getModel();
const toggleFoldingLines = [];
model?.getLinesContent().forEach((line, j) => {
autoFoldingElements.forEach((elementName) => {
if (line.match(new RegExp(`^${elementName}`))) {
toggleFoldingLines.push(j + 2);
}
});
});
const toFold = !elementsFolded;
toggleFoldingLines.forEach((foldingLineRegion, i) => {
if (foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]) {
if (foldingModel._regions.isCollapsed(foldingModel.getAllRegionsAtLine(foldingLineRegion)[0]
.regionIndex) !== toFold) {
foldingModel.toggleCollapseState(foldingModel.getAllRegionsAtLine(foldingLineRegion));
}
}
});
setFoldingElements(toFold);
});
}
}
// below use effects watch over `forcedCursorPosition`, `wordWrapOtion`, `error`, `warnings` and reset them to the editor as needed
useEffect(() => {
if (editor && forcedCursorPosition) {
moveCursorToPosition(editor, forcedCursorPosition);
}
}, [editor, forcedCursorPosition]);
useEffect(() => {
if (editor) {
editor.updateOptions({
wordWrap: wordWrapOtion,
});
}
}, [editor, wordWrapOtion]);
useEffect(() => {
const editorModel = editor?.getModel();
if (editorModel && (error?.sourceInformation || warnings.length)) {
if (error?.sourceInformation) {
setErrorMarkers(editorModel, [
{
message: error.message,
startLineNumber: error.sourceInformation.startLine,
startColumn: error.sourceInformation.startColumn,
endLineNumber: error.sourceInformation.endLine,
endColumn: error.sourceInformation.endColumn,
},
]);
}
if (warnings.length) {
setWarningMarkers(editorModel, warnings
.map((warning) => {
if (!warning.sourceInformation) {
return undefined;
}
return {
message: warning.message,
startLineNumber: warning.sourceInformation.startLine,
startColumn: warning.sourceInformation.startColumn,
endLineNumber: warning.sourceInformation.endLine,
endColumn: warning.sourceInformation.endColumn,
};
})
.filter(isNonNullable));
}
}
}, [editor, error, warnings]);
// first load with grammar. auto fold element sections
useEffect(() => {
if (editor) {
const model = editor.getModel();
const autoFoldingElements = editorStore.pluginManager
.getApplicationPlugins()
.flatMap((plugin) => plugin.getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
[]);
const foldingClass = editor.getContribution('editor.contrib.folding');
if (foldingClass && model) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
foldingClass.getFoldingModel().then((foldingModel) => {
if (foldingModel) {
const elementLinesToBeFolded = [];
model.getLinesContent().forEach((line, idx) => {
autoFoldingElements.forEach((elementName) => {
if (line.match(new RegExp(`^${elementName}`))) {
elementLinesToBeFolded.push(idx + 2);
}
});
});
elementLinesToBeFolded.forEach((lineToBeFolded) => {
const regionToFold = foldingModel.getAllRegionsAtLine(lineToBeFolded);
foldingModel.toggleCollapseState(regionToFold);
});
setFoldingElements(true);
}
});
}
}
}, [editor, editorStore.pluginManager]);
// NOTE: dispose the editor to prevent potential memory-leak
useEffect(() => () => {
if (editor) {
disposeCodeEditor(editor);
}
// Dispose the providers properly to avoid ending up with duplicated suggestions
hoverProviderDisposer.current?.dispose();
suggestionProviderDisposer.current?.dispose();
}, [editor]);
useApplicationNavigationContext(LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.TEXT_MODE_EDITOR);
return (_jsxs("div", { className: "panel editor-group", children: [_jsxs("div", { className: "panel__header editor-group__header", children: [_jsx("div", { className: "editor-group__header__tabs", children: _jsx(ContextMenu, { className: "editor-group__text-mode__tab editor-group__text-mode__tab--active", content: _jsx(GrammarTextEditorHeaderTabContextMenu, {}), children: _jsx("div", { className: "editor-group__text-mode__tab__label", children: grammarModeState.headerLabel }) }) }), _jsxs("div", { className: "editor-group__header__actions", children: [_jsx("div", { className: "editor-group__text-mode__action", children: _jsx("button", { title: "Compile (F9)", onClick: globalCompile, className: "editor-group__text-mode-btn btn--dark", children: "Compile" }) }), _jsx("div", { className: "editor-group__text-mode__action", children: _jsx("button", { title: "Click to exit text mode and go back to form mode (F8)", onClick: leaveTextMode, className: "editor-group__text-mode-btn btn--dark", children: "Exit Text Mode" }) }), _jsx("div", { className: "query-builder__header__actions", children: _jsxs(ControlledDropdownMenu, { className: "query-builder__header__advanced-dropdown", title: "Show Advanced Menu...", content: _jsxs(MenuContent, { children: [_jsxs(MenuContentItem, { onClick: toggleWordWrap, children: [_jsx(MenuContentItemIcon, { children: grammarTextEditorState.wrapText ? _jsx(CheckIcon, {}) : null }), _jsx(MenuContentItemLabel, { children: "Wrap Overflowing Words" })] }), _jsxs(MenuContentItem, { onClick: toggleAutoFoldingElements, children: [_jsx(MenuContentItemIcon, { children: elementsFolded ? _jsx(CheckIcon, {}) : null }), _jsx(MenuContentItemLabel, { children: "Auto Fold Elements" })] })] }), menuProps: {
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
transformOrigin: { vertical: 'top', horizontal: 'right' },
elevation: 7,
}, children: [_jsx("div", { className: "query-builder__header__advanced-dropdown__label", children: "Advanced" }), _jsx(CaretDownIcon, { className: "query-builder__header__advanced-dropdown__icon" })] }) })] })] }), _jsxs(PanelContent, { className: "editor-group__content", children: [_jsx(PanelLoadingIndicator, { isLoading: editorStore.graphState.isRunningGlobalCompile }), _jsx("div", { className: "code-editor__container", children: _jsx("div", { className: "code-editor__body", ref: ref }) })] }), grammarModeState.generatedFile && (_jsx(FileSystem_FileModal, { generatedFile: grammarModeState.generatedFile, graphEditGrammarModeState: grammarModeState }))] }));
});
//# sourceMappingURL=GrammarTextEditor.js.map