| 'use client'; |
|
|
| import { EditorView } from '@codemirror/view'; |
| import { EditorState, Transaction } from '@codemirror/state'; |
| import { python } from '@codemirror/lang-python'; |
| import { oneDark } from '@codemirror/theme-one-dark'; |
| import { basicSetup } from 'codemirror'; |
| import React, { memo, useEffect, useRef } from 'react'; |
| import { Suggestion } from '@/lib/db/schema'; |
|
|
| type EditorProps = { |
| content: string; |
| onSaveContent: (updatedContent: string, debounce: boolean) => void; |
| status: 'streaming' | 'idle'; |
| isCurrentVersion: boolean; |
| currentVersionIndex: number; |
| suggestions: Array<Suggestion>; |
| }; |
|
|
| function PureCodeEditor({ content, onSaveContent, status }: EditorProps) { |
| const containerRef = useRef<HTMLDivElement>(null); |
| const editorRef = useRef<EditorView | null>(null); |
|
|
| useEffect(() => { |
| if (containerRef.current && !editorRef.current) { |
| const startState = EditorState.create({ |
| doc: content, |
| extensions: [basicSetup, python(), oneDark], |
| }); |
|
|
| editorRef.current = new EditorView({ |
| state: startState, |
| parent: containerRef.current, |
| }); |
| } |
|
|
| return () => { |
| if (editorRef.current) { |
| editorRef.current.destroy(); |
| editorRef.current = null; |
| } |
| }; |
| |
| |
| }, []); |
|
|
| useEffect(() => { |
| if (editorRef.current) { |
| const updateListener = EditorView.updateListener.of((update) => { |
| if (update.docChanged) { |
| const transaction = update.transactions.find( |
| (tr) => !tr.annotation(Transaction.remote), |
| ); |
|
|
| if (transaction) { |
| const newContent = update.state.doc.toString(); |
| onSaveContent(newContent, true); |
| } |
| } |
| }); |
|
|
| const currentSelection = editorRef.current.state.selection; |
|
|
| const newState = EditorState.create({ |
| doc: editorRef.current.state.doc, |
| extensions: [basicSetup, python(), oneDark, updateListener], |
| selection: currentSelection, |
| }); |
|
|
| editorRef.current.setState(newState); |
| } |
| }, [onSaveContent]); |
|
|
| useEffect(() => { |
| if (editorRef.current && content) { |
| const currentContent = editorRef.current.state.doc.toString(); |
|
|
| if (status === 'streaming' || currentContent !== content) { |
| const transaction = editorRef.current.state.update({ |
| changes: { |
| from: 0, |
| to: currentContent.length, |
| insert: content, |
| }, |
| annotations: [Transaction.remote.of(true)], |
| }); |
|
|
| editorRef.current.dispatch(transaction); |
| } |
| } |
| }, [content, status]); |
|
|
| return ( |
| <div |
| className="relative not-prose w-full pb-[calc(80dvh)] text-sm" |
| ref={containerRef} |
| /> |
| ); |
| } |
|
|
| function areEqual(prevProps: EditorProps, nextProps: EditorProps) { |
| if (prevProps.suggestions !== nextProps.suggestions) return false; |
| if (prevProps.currentVersionIndex !== nextProps.currentVersionIndex) |
| return false; |
| if (prevProps.isCurrentVersion !== nextProps.isCurrentVersion) return false; |
| if (prevProps.status === 'streaming' && nextProps.status === 'streaming') |
| return false; |
| if (prevProps.content !== nextProps.content) return false; |
|
|
| return true; |
| } |
|
|
| export const CodeEditor = memo(PureCodeEditor, areEqual); |
|
|