| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- import Editor, { Monaco } from "@monaco-editor/react"
- import useTheme from "hooks/useTheme"
- import prettier from "prettier/standalone"
- import parserTypeScript from "prettier/parser-typescript"
- import codeAsString from "./code-as-string"
- import React, { useCallback, useEffect, useRef } from "react"
- import styled from "styles"
- import { IMonaco, IMonacoEditor } from "types"
-
- interface Props {
- value: string
- error: { line: number }
- fontSize: number
- monacoRef?: React.MutableRefObject<IMonaco>
- editorRef?: React.MutableRefObject<IMonacoEditor>
- readOnly?: boolean
- onMount?: (value: string, editor: IMonacoEditor) => void
- onUnmount?: (editor: IMonacoEditor) => void
- onChange?: (value: string, editor: IMonacoEditor) => void
- onSave?: (value: string, editor: IMonacoEditor) => void
- onError?: (error: Error, line: number, col: number) => void
- onKey?: () => void
- }
-
- export default function CodeEditor({
- editorRef,
- monacoRef,
- fontSize,
- value,
- error,
- readOnly,
- onChange,
- onSave,
- onKey,
- }: Props) {
- const { theme } = useTheme()
- const rEditor = useRef<IMonacoEditor>(null)
- const rMonaco = useRef<IMonaco>(null)
-
- const handleBeforeMount = useCallback((monaco: Monaco) => {
- if (monacoRef) {
- monacoRef.current = monaco
- }
- rMonaco.current = monaco
-
- monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
- allowJs: true,
- checkJs: false,
- strict: false,
- noLib: true,
- lib: ["es6"],
- target: monaco.languages.typescript.ScriptTarget.ES2015,
- allowNonTsExtensions: true,
- })
-
- monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true)
-
- monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true)
-
- monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
- noSemanticValidation: true,
- noSyntaxValidation: true,
- })
-
- monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
- noSemanticValidation: true,
- noSyntaxValidation: true,
- })
-
- monaco.languages.typescript.javascriptDefaults.addExtraLib(codeAsString)
-
- monaco.languages.registerDocumentFormattingEditProvider("javascript", {
- async provideDocumentFormattingEdits(model) {
- const text = prettier.format(model.getValue(), {
- parser: "typescript",
- plugins: [parserTypeScript],
- singleQuote: true,
- trailingComma: "es5",
- semi: false,
- })
-
- return [
- {
- range: model.getFullModelRange(),
- text,
- },
- ]
- },
- })
- }, [])
-
- const handleMount = useCallback((editor: IMonacoEditor) => {
- if (editorRef) {
- editorRef.current = editor
- }
- rEditor.current = editor
-
- editor.updateOptions({
- fontSize,
- wordBasedSuggestions: false,
- minimap: { enabled: false },
- lightbulb: {
- enabled: false,
- },
- readOnly,
- })
- }, [])
-
- const handleChange = useCallback((code: string | undefined) => {
- onChange(code, rEditor.current)
- }, [])
-
- const handleKeydown = useCallback(
- (e: React.KeyboardEvent<HTMLDivElement>) => {
- onKey && onKey()
- e.stopPropagation()
- const metaKey = navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey
- if (e.key === "s" && metaKey) {
- const editor = rEditor.current
- if (!editor) return
- editor
- .getAction("editor.action.formatDocument")
- .run()
- .then(() =>
- onSave(rEditor.current?.getModel().getValue(), rEditor.current)
- )
-
- e.preventDefault()
- }
- if (e.key === "p" && metaKey) {
- e.preventDefault()
- }
- if (e.key === "d" && metaKey) {
- e.preventDefault()
- }
- },
- []
- )
-
- const handleKeyUp = useCallback(
- (e: React.KeyboardEvent<HTMLDivElement>) => e.stopPropagation(),
- []
- )
-
- const rDecorations = useRef<any>([])
-
- useEffect(() => {
- const monaco = rMonaco.current
- if (!monaco) return
- const editor = rEditor.current
- if (!editor) return
-
- if (!error) {
- rDecorations.current = editor.deltaDecorations(rDecorations.current, [])
- return
- }
-
- if (!error.line) return
-
- rDecorations.current = editor.deltaDecorations(rDecorations.current, [
- {
- range: new monaco.Range(
- Number(error.line) - 1,
- 0,
- Number(error.line) - 1,
- 0
- ),
- options: {
- isWholeLine: true,
- className: "editorLineError",
- },
- },
- ])
- }, [error])
-
- useEffect(() => {
- const monaco = rMonaco.current
- if (!monaco) return
- monaco.editor.setTheme(theme === "dark" ? "vs-dark" : "light")
- }, [theme])
-
- useEffect(() => {
- const editor = rEditor.current
- if (!editor) return
-
- editor.updateOptions({
- fontSize,
- })
- }, [fontSize])
-
- return (
- <EditorContainer onKeyDown={handleKeydown} onKeyUp={handleKeyUp}>
- <Editor
- height="100%"
- language="javascript"
- value={value}
- theme={theme === "dark" ? "vs-dark" : "light"}
- beforeMount={handleBeforeMount}
- onMount={handleMount}
- onChange={handleChange}
- />
- </EditorContainer>
- )
- }
-
- const EditorContainer = styled("div", {
- height: "100%",
- pointerEvents: "all",
- userSelect: "all",
-
- ".editorLineError": {
- backgroundColor: "$lineError",
- },
- })
|