diff --git a/src/renderer/components/editor/CodeMirrorEditor.tsx b/src/renderer/components/editor/CodeMirrorEditor.tsx new file mode 100644 index 0000000..b32d58f --- /dev/null +++ b/src/renderer/components/editor/CodeMirrorEditor.tsx @@ -0,0 +1,80 @@ +import { useEffect, useRef } from 'react'; +import { EditorState, Compartment } from '@codemirror/state'; +import { EditorView, keymap, lineNumbers, highlightActiveLine, drawSelection } from '@codemirror/view'; +import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands'; +import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; +import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; +import { autocompletion, completionKeymap } from '@codemirror/autocomplete'; +import { oneDark } from '@codemirror/theme-one-dark'; +import { useTheme } from 'next-themes'; +import { lightTheme, lightHighlight } from './themes/light'; +import { useEditorStore } from '@/stores/editor-store'; + +interface Props { + bufferId: string; + initialContent: string; + onChange?: (content: string) => void; + onCursorChange?: (line: number, column: number) => void; +} + +export function CodeMirrorEditor({ bufferId, initialContent, onChange, onCursorChange }: Props) { + const ref = useRef(null); + const viewRef = useRef(null); + const themeCompartment = useRef(new Compartment()); + const { resolvedTheme } = useTheme(); + const updateContent = useEditorStore((s) => s.updateContent); + const setCursor = useEditorStore((s) => s.setCursor); + + useEffect(() => { + if (!ref.current) return; + const state = EditorState.create({ + doc: initialContent, + extensions: [ + lineNumbers(), + highlightActiveLine(), + highlightSelectionMatches(), + history(), + drawSelection(), + markdown({ base: markdownLanguage, codeLanguages: [] }), + autocompletion(), + keymap.of([...defaultKeymap, ...historyKeymap, ...searchKeymap, ...completionKeymap, indentWithTab]), + themeCompartment.current.of(resolvedTheme === 'dark' ? [oneDark] : [lightTheme, lightHighlight]), + EditorView.lineWrapping, + EditorView.updateListener.of((v) => { + if (v.docChanged) { + const content = v.state.doc.toString(); + updateContent(bufferId, content); + onChange?.(content); + } + if (v.selectionSet || v.docChanged) { + const head = v.state.selection.main.head; + const line = v.state.doc.lineAt(head); + const lineNo = line.number; + const col = head - line.from + 1; + setCursor(bufferId, lineNo, col); + onCursorChange?.(lineNo, col); + } + }), + ], + }); + const view = new EditorView({ state, parent: ref.current }); + viewRef.current = view; + return () => { + view.destroy(); + viewRef.current = null; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bufferId]); + + useEffect(() => { + const view = viewRef.current; + if (!view) return; + view.dispatch({ + effects: themeCompartment.current.reconfigure( + resolvedTheme === 'dark' ? [oneDark] : [lightTheme, lightHighlight] + ), + }); + }, [resolvedTheme]); + + return
; +} \ No newline at end of file