@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
JavaScript
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;