UNPKG

@finos/legend-application-pure-ide

Version:
405 lines 17.4 kB
/** * 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 { ActionAlertActionType, ActionAlertType, } from '@finos/legend-application'; import { clearMarkers, setErrorMarkers, CODE_EDITOR_LANGUAGE, } from '@finos/legend-code-editor'; import { DIRECTORY_PATH_DELIMITER } from '@finos/legend-graph'; import { at, assertErrorThrown } from '@finos/legend-shared'; import { action, computed, flow, flowResult, makeObservable, observable, } from 'mobx'; import { editor as monacoEditorAPI, Uri, } from 'monaco-editor'; import { ConceptType } from '../server/models/ConceptTree.js'; import { FileCoordinate, trimPathLeadingSlash, } from '../server/models/File.js'; import { FIND_USAGE_FUNCTION_PATH, } from '../server/models/Usage.js'; import { PureIDETabState } from './PureIDETabManagerState.js'; import { LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY } from '../__lib__/LegendPureIDECommand.js'; const getFileEditorLanguage = (filePath) => { const extension = filePath.split('.').at(-1); switch (extension) { case 'pure': return CODE_EDITOR_LANGUAGE.PURE; case 'java': return CODE_EDITOR_LANGUAGE.JAVA; case 'md': return CODE_EDITOR_LANGUAGE.MARKDOWN; case 'sql': return CODE_EDITOR_LANGUAGE.SQL; case 'json': return CODE_EDITOR_LANGUAGE.JSON; case 'xml': return CODE_EDITOR_LANGUAGE.XML; case 'yml': case 'yaml': return CODE_EDITOR_LANGUAGE.YAML; case 'graphql': return CODE_EDITOR_LANGUAGE.GRAPHQL; default: return CODE_EDITOR_LANGUAGE.TEXT; } }; class FileTextEditorState { model; editor; _dummyCursorObservable = {}; language; viewState; forcedCursorPosition; wrapText = false; constructor(fileEditorState) { makeObservable(this, { viewState: observable.ref, editor: observable.ref, _dummyCursorObservable: observable.ref, forcedCursorPosition: observable.ref, wrapText: observable, cursorObserver: computed, notifyCursorObserver: action, setViewState: action, setEditor: action, setForcedCursorPosition: action, setWrapText: action, }); this.language = getFileEditorLanguage(fileEditorState.filePath); this.model = monacoEditorAPI.createModel('', this.language, Uri.file(`/${fileEditorState.uuid}.pure`)); this.model.setValue(fileEditorState.file.content); } // trigger for the manual observer of editor cursor notifyCursorObserver() { this._dummyCursorObservable = {}; } // subscriber for the manual observer of editor cursor get cursorObserver() { // eslint-disable-next-line @typescript-eslint/no-unused-expressions this._dummyCursorObservable; // manually trigger cursor observer return this.editor ? { position: this.editor.getPosition() ?? undefined, selection: this.editor.getSelection() ?? undefined, } : undefined; } setViewState(val) { this.viewState = val; } setEditor(val) { this.editor = val; } setForcedCursorPosition(val) { this.forcedCursorPosition = val; } setWrapText(val) { const oldVal = this.wrapText; this.wrapText = val; if (oldVal !== val && this.editor) { this.editor.updateOptions({ wordWrap: val ? 'on' : 'off', }); this.editor.focus(); } } } export class FileEditorRenameConceptState { fileEditorState; concept; coordinate; constructor(fileEditorState, concept, coordiate) { this.fileEditorState = fileEditorState; this.concept = concept; this.coordinate = coordiate; } } export class FileEditorState extends PureIDETabState { filePath; textEditorState; _currentHashCode; file; renameConceptState; showGoToLinePrompt = false; constructor(ideStore, file, filePath) { super(ideStore); makeObservable(this, { _currentHashCode: observable, file: observable, renameConceptState: observable, showGoToLinePrompt: observable, hasChanged: computed, resetChangeDetection: action, setFile: action, setShowGoToLinePrompt: action, setConceptToRenameState: flow, runTest: flow, }); this.file = file; this._currentHashCode = file.hashCode; this.filePath = filePath; this.textEditorState = new FileTextEditorState(this); } get label() { return trimPathLeadingSlash(this.filePath); } get description() { return `File: ${trimPathLeadingSlash(this.filePath)}${this.file.RO ? ' (readonly)' : ''}`; } get fileName() { return at(this.filePath.split(DIRECTORY_PATH_DELIMITER), -1); } match(tab) { return tab instanceof FileEditorState && this.filePath === tab.filePath; } onClose() { // dispose text model to avoid memory leak this.textEditorState.model.dispose(); } get hasChanged() { return this._currentHashCode !== this.file.hashCode; } resetChangeDetection() { this._currentHashCode = this.file.hashCode; } setFile(val) { this.file = val; this.textEditorState.model.setValue(val.content); this.resetChangeDetection(); } setShowGoToLinePrompt(val) { this.showGoToLinePrompt = val; } *setConceptToRenameState(coordinate) { if (!coordinate) { this.renameConceptState = undefined; return; } if (this.hasChanged) { this.ideStore.applicationStore.notificationService.notifyWarning(`Can't rename concept: source is not compiled`); return; } const concept = (yield this.ideStore.getConceptInfo(coordinate)); this.renameConceptState = concept ? new FileEditorRenameConceptState(this, concept, coordinate) : undefined; } *runTest(coordinate) { if (!coordinate) { return; } if (this.hasChanged) { this.ideStore.applicationStore.notificationService.notifyWarning(`Can't run test: source is not compiled`); return; } const concept = (yield this.ideStore.getConceptInfo(coordinate)); if (concept?.pureType === ConceptType.FUNCTION) { if (concept.pct) { this.ideStore.setPCTRunPath(concept.path); } else if (concept.test) { flowResult(this.ideStore.executeTests(concept.path, false)).catch(this.ideStore.applicationStore.alertUnhandledError); } else { this.ideStore.applicationStore.notificationService.notifyWarning(`Can't run test: function not marked as test`); } } else { this.ideStore.applicationStore.notificationService.notifyWarning(`Can't run test: not a function`); } } showError(coordinate) { setErrorMarkers(this.textEditorState.model, [ { message: coordinate.error.message, startLineNumber: coordinate.line, startColumn: coordinate.column, endLineNumber: coordinate.line, endColumn: coordinate.column, }, ], this.uuid); } clearError() { clearMarkers(this.uuid); } registerCommands() { if (this.textEditorState.language === CODE_EDITOR_LANGUAGE.PURE) { this.ideStore.applicationStore.commandService.registerCommand({ key: LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.GO_TO_DEFINITION, trigger: () => this.ideStore.tabManagerState.currentTab === this && Boolean(this.textEditorState.editor?.hasTextFocus()), action: () => { const currentPosition = this.textEditorState.editor?.getPosition(); if (currentPosition) { flowResult(this.ideStore.executeNavigation(new FileCoordinate(this.filePath, currentPosition.lineNumber, currentPosition.column))).catch(this.ideStore.applicationStore.alertUnhandledError); } }, }); this.ideStore.applicationStore.commandService.registerCommand({ key: LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.GO_BACK, action: () => { flowResult(this.ideStore.navigateBack()).catch(this.ideStore.applicationStore.alertUnhandledError); }, }); this.ideStore.applicationStore.commandService.registerCommand({ key: LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.REVEAL_CONCEPT_IN_TREE, trigger: () => this.ideStore.tabManagerState.currentTab === this && Boolean(this.textEditorState.editor?.hasTextFocus()), action: () => { const currentPosition = this.textEditorState.editor?.getPosition(); if (currentPosition) { this.ideStore .revealConceptInTree(new FileCoordinate(this.filePath, currentPosition.lineNumber, currentPosition.column)) .catch(this.ideStore.applicationStore.alertUnhandledError); } }, }); this.ideStore.applicationStore.commandService.registerCommand({ key: LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.FIND_USAGES, trigger: () => this.ideStore.tabManagerState.currentTab === this && Boolean(this.textEditorState.editor?.hasTextFocus()), action: () => { const currentPosition = this.textEditorState.editor?.getPosition(); if (currentPosition) { const coordinate = new FileCoordinate(this.filePath, currentPosition.lineNumber, currentPosition.column); this.findConceptUsages(coordinate); } }, }); this.ideStore.applicationStore.commandService.registerCommand({ key: LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.RENAME_CONCEPT, trigger: () => this.ideStore.tabManagerState.currentTab === this && Boolean(this.textEditorState.editor?.hasTextFocus()), action: () => { const currentPosition = this.textEditorState.editor?.getPosition(); if (currentPosition) { const currentWord = this.textEditorState.model.getWordAtPosition(currentPosition); if (!currentWord) { return; } const coordinate = new FileCoordinate(this.filePath, currentPosition.lineNumber, currentPosition.column); flowResult(this.setConceptToRenameState(coordinate)).catch(this.ideStore.applicationStore.alertUnhandledError); } }, }); } this.ideStore.applicationStore.commandService.registerCommand({ key: LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.DELETE_LINE, trigger: () => this.ideStore.tabManagerState.currentTab === this && Boolean(this.textEditorState.editor?.hasTextFocus()), action: () => { const currentPosition = this.textEditorState.editor?.getPosition(); if (currentPosition) { this.textEditorState.model.pushEditOperations([], [ { range: { startLineNumber: currentPosition.lineNumber, startColumn: 1, endLineNumber: currentPosition.lineNumber + 1, endColumn: 1, }, text: '', forceMoveMarkers: true, }, ], () => null); } }, }); this.ideStore.applicationStore.commandService.registerCommand({ key: LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.GO_TO_LINE, trigger: () => this.ideStore.tabManagerState.currentTab === this, action: () => { this.setShowGoToLinePrompt(true); }, }); this.ideStore.applicationStore.commandService.registerCommand({ key: LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.TOGGLE_TEXT_WRAP, trigger: () => this.ideStore.tabManagerState.currentTab === this, action: () => { this.textEditorState.setWrapText(!this.textEditorState.wrapText); }, }); } findConceptUsages(coordinate) { const proceed = () => { flowResult(this.ideStore.findUsagesFromCoordinate(coordinate)).catch(this.ideStore.applicationStore.alertUnhandledError); }; if (this.hasChanged) { this.ideStore.applicationStore.alertService.setActionAlertInfo({ message: 'Source is not compiled, finding concept usages might be inaccurate. Do you want compile to proceed?', type: ActionAlertType.CAUTION, actions: [ { label: 'Compile and Proceed', type: ActionAlertActionType.PROCEED_WITH_CAUTION, handler: () => { flowResult(this.ideStore.executeGo()) .then(proceed) .catch(this.ideStore.applicationStore.alertUnhandledError); }, }, { label: 'Abort', type: ActionAlertActionType.PROCEED, default: true, }, ], }); } else { proceed(); } } async renameConcept(newName) { if (!this.renameConceptState) { return; } const concept = this.renameConceptState.concept; try { this.ideStore.applicationStore.alertService.setBlockingAlert({ message: 'Finding concept usages...', showLoading: true, }); const usages = await this.ideStore.findConceptUsages(concept.pureType === ConceptType.ENUM_VALUE ? FIND_USAGE_FUNCTION_PATH.ENUM : concept.pureType === ConceptType.PROPERTY || concept.pureType === ConceptType.QUALIFIED_PROPERTY ? FIND_USAGE_FUNCTION_PATH.PROPERTY : FIND_USAGE_FUNCTION_PATH.ELEMENT, (concept.owner ? [`'${concept.owner}'`] : []).concat(`'${concept.path}'`)); await flowResult(this.ideStore.renameConcept(concept.pureName, newName, concept.pureType, usages)); this.textEditorState.setForcedCursorPosition({ lineNumber: this.renameConceptState.coordinate.line, column: this.renameConceptState.coordinate.column, }); } catch (error) { assertErrorThrown(error); this.ideStore.applicationStore.notificationService.notifyError(error); } finally { this.ideStore.applicationStore.alertService.setBlockingAlert(undefined); } } deregisterCommands() { if (this.textEditorState.language === CODE_EDITOR_LANGUAGE.PURE) { [ LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.GO_TO_DEFINITION, LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.GO_BACK, LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.REVEAL_CONCEPT_IN_TREE, LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.FIND_USAGES, LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.RENAME_CONCEPT, ].forEach((key) => this.ideStore.applicationStore.commandService.deregisterCommand(key)); } [ LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.GO_TO_LINE, LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.DELETE_LINE, LEGEND_PURE_IDE_PURE_FILE_EDITOR_COMMAND_KEY.TOGGLE_TEXT_WRAP, ].forEach((key) => this.ideStore.applicationStore.commandService.deregisterCommand(key)); } } //# sourceMappingURL=FileEditorState.js.map