UNPKG

@monaco-editor/react

Version:

Monaco Editor for React - use the monaco-editor in any React application without needing to use webpack (or rollup/parcel/etc) configuration files / plugins

233 lines (206 loc) 7.78 kB
import loader from '@monaco-editor/loader'; import React, { useState, useRef, useCallback, useEffect } from 'react'; import PropTypes from 'prop-types'; import MonacoContainer from '../MonacoContainer/index.js'; import useMount from '../hooks/useMount/index.js'; import useUpdate from '../hooks/useUpdate/index.js'; import { getOrCreateModel, noop, isUndefined } from '../utils/index.js'; import usePrevious from '../hooks/usePrevious/index.js'; const viewStates = new Map(); function Editor({ defaultValue, defaultLanguage, defaultPath, value, language, path, /* === */ theme, line, loading, options, overrideServices, saveViewState, keepCurrentModel, /* === */ width, height, className, wrapperProps, /* === */ beforeMount, onMount, onChange, onValidate }) { const [isEditorReady, setIsEditorReady] = useState(false); const [isMonacoMounting, setIsMonacoMounting] = useState(true); const monacoRef = useRef(null); const editorRef = useRef(null); const containerRef = useRef(null); const onMountRef = useRef(onMount); const beforeMountRef = useRef(beforeMount); const subscriptionRef = useRef(null); const valueRef = useRef(value); const previousPath = usePrevious(path); useMount(() => { const cancelable = loader.init(); cancelable.then(monaco => (monacoRef.current = monaco) && setIsMonacoMounting(false)).catch(error => (error === null || error === void 0 ? void 0 : error.type) !== 'cancelation' && console.error('Monaco initialization: error:', error)); return () => editorRef.current ? disposeEditor() : cancelable.cancel(); }); useUpdate(() => { const model = getOrCreateModel(monacoRef.current, defaultValue || value, defaultLanguage || language, path); if (model !== editorRef.current.getModel()) { saveViewState && viewStates.set(previousPath, editorRef.current.saveViewState()); editorRef.current.setModel(model); saveViewState && editorRef.current.restoreViewState(viewStates.get(path)); } }, [path], isEditorReady); useUpdate(() => { editorRef.current.updateOptions(options); }, [options], isEditorReady); useUpdate(() => { if (editorRef.current.getOption(monacoRef.current.editor.EditorOption.readOnly)) { editorRef.current.setValue(value); } else { if (value !== editorRef.current.getValue()) { editorRef.current.executeEdits('', [{ range: editorRef.current.getModel().getFullModelRange(), text: value, forceMoveMarkers: true }]); editorRef.current.pushUndoStop(); } } }, [value], isEditorReady); useUpdate(() => { monacoRef.current.editor.setModelLanguage(editorRef.current.getModel(), language); }, [language], isEditorReady); useUpdate(() => { // reason for undefined check: https://github.com/suren-atoyan/monaco-react/pull/188 if (!isUndefined(line)) { editorRef.current.revealLine(line); } }, [line], isEditorReady); useUpdate(() => { monacoRef.current.editor.setTheme(theme); }, [theme], isEditorReady); const createEditor = useCallback(() => { beforeMountRef.current(monacoRef.current); const autoCreatedModelPath = path || defaultPath; const defaultModel = getOrCreateModel(monacoRef.current, value || defaultValue, defaultLanguage || language, autoCreatedModelPath); editorRef.current = monacoRef.current.editor.create(containerRef.current, { model: defaultModel, automaticLayout: true, ...options }, overrideServices); saveViewState && editorRef.current.restoreViewState(viewStates.get(autoCreatedModelPath)); monacoRef.current.editor.setTheme(theme); setIsEditorReady(true); }, [defaultValue, defaultLanguage, defaultPath, value, language, path, options, overrideServices, saveViewState, theme]); useEffect(() => { if (isEditorReady) { onMountRef.current(editorRef.current, monacoRef.current); } }, [isEditorReady]); useEffect(() => { !isMonacoMounting && !isEditorReady && createEditor(); }, [isMonacoMounting, isEditorReady, createEditor]); // subscription // to avoid unnecessary updates (attach - dispose listener) in subscription valueRef.current = value; useEffect(() => { if (isEditorReady && onChange) { var _subscriptionRef$curr, _editorRef$current; (_subscriptionRef$curr = subscriptionRef.current) === null || _subscriptionRef$curr === void 0 ? void 0 : _subscriptionRef$curr.dispose(); subscriptionRef.current = (_editorRef$current = editorRef.current) === null || _editorRef$current === void 0 ? void 0 : _editorRef$current.onDidChangeModelContent(event => { const editorValue = editorRef.current.getValue(); if (valueRef.current !== editorValue) { onChange(editorValue, event); } }); } }, [isEditorReady, onChange]); // onValidate useEffect(() => { if (isEditorReady) { const changeMarkersListener = monacoRef.current.editor.onDidChangeMarkers(uris => { var _editorRef$current$ge; const editorUri = (_editorRef$current$ge = editorRef.current.getModel()) === null || _editorRef$current$ge === void 0 ? void 0 : _editorRef$current$ge.uri; if (editorUri) { const currentEditorHasMarkerChanges = uris.find(uri => uri.path === editorUri.path); if (currentEditorHasMarkerChanges) { const markers = monacoRef.current.editor.getModelMarkers({ resource: editorUri }); onValidate === null || onValidate === void 0 ? void 0 : onValidate(markers); } } }); return () => { changeMarkersListener === null || changeMarkersListener === void 0 ? void 0 : changeMarkersListener.dispose(); }; } }, [isEditorReady, onValidate]); function disposeEditor() { var _subscriptionRef$curr2; (_subscriptionRef$curr2 = subscriptionRef.current) === null || _subscriptionRef$curr2 === void 0 ? void 0 : _subscriptionRef$curr2.dispose(); if (keepCurrentModel) { saveViewState && viewStates.set(path, editorRef.current.saveViewState()); } else { var _editorRef$current$ge2; (_editorRef$current$ge2 = editorRef.current.getModel()) === null || _editorRef$current$ge2 === void 0 ? void 0 : _editorRef$current$ge2.dispose(); } editorRef.current.dispose(); } return /*#__PURE__*/React.createElement(MonacoContainer, { width: width, height: height, isEditorReady: isEditorReady, loading: loading, _ref: containerRef, className: className, wrapperProps: wrapperProps }); } Editor.propTypes = { defaultValue: PropTypes.string, defaultPath: PropTypes.string, defaultLanguage: PropTypes.string, value: PropTypes.string, language: PropTypes.string, path: PropTypes.string, /* === */ theme: PropTypes.string, line: PropTypes.number, loading: PropTypes.oneOfType([PropTypes.element, PropTypes.string]), options: PropTypes.object, overrideServices: PropTypes.object, saveViewState: PropTypes.bool, keepCurrentModel: PropTypes.bool, /* === */ width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), className: PropTypes.string, wrapperProps: PropTypes.object, /* === */ beforeMount: PropTypes.func, onMount: PropTypes.func, onChange: PropTypes.func, onValidate: PropTypes.func }; Editor.defaultProps = { theme: 'light', loading: 'Loading...', options: {}, overrideServices: {}, saveViewState: true, keepCurrentModel: false, /* === */ width: '100%', height: '100%', wrapperProps: {}, /* === */ beforeMount: noop, onMount: noop, onValidate: noop }; export default Editor;