From 236eabe248c40d8055200a4388b5834e2587c556 Mon Sep 17 00:00:00 2001 From: Qiaochu Hu <110hqc@gmail.com> Date: Wed, 13 May 2026 14:25:42 +0800 Subject: [PATCH] fix: prevent SourceEditor recreation on every content change (#624) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mountEditor callback had content in its dependency array and was used as a React ref callback. Every content change (keystroke) gave mountEditor a new identity, causing React to destroy and recreate the entire CodeMirror editor — losing cursor position, undo history, and focus. Remove content from the dependency array and use a separate useEffect to push external content updates to the existing editor via dispatch(). The editor is now only recreated when filePath, language, or readOnly change. Co-authored-by: Test User Co-authored-by: Claude Opus 4.7 --- .../src/components/editor/SourceEditor.tsx | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/studio/src/components/editor/SourceEditor.tsx b/packages/studio/src/components/editor/SourceEditor.tsx index 098b93db8..d6c4975ea 100644 --- a/packages/studio/src/components/editor/SourceEditor.tsx +++ b/packages/studio/src/components/editor/SourceEditor.tsx @@ -1,4 +1,4 @@ -import { useRef, useCallback, memo } from "react"; +import { useRef, useCallback, useEffect, memo } from "react"; import { EditorView, keymap, @@ -69,6 +69,9 @@ export const SourceEditor = memo(function SourceEditor({ const onChangeRef = useRef(onChange); onChangeRef.current = onChange; + const contentRef = useRef(content); + contentRef.current = content; + const mountEditor = useCallback( (node: HTMLDivElement | null) => { if (editorRef.current) { @@ -87,7 +90,7 @@ export const SourceEditor = memo(function SourceEditor({ }); const state = EditorState.create({ - doc: content, + doc: contentRef.current, extensions: [ lineNumbers(), highlightActiveLine(), @@ -112,8 +115,22 @@ export const SourceEditor = memo(function SourceEditor({ editorRef.current = new EditorView({ state, parent: node }); }, - [content, filePath, language, readOnly], + [filePath, language, readOnly], ); + // Sync external content changes into the editor without recreating it. + // Only applies when the new content differs from the current document + // (e.g. file switch or server refresh), not on every keystroke. + useEffect(() => { + const view = editorRef.current; + if (!view) return; + const current = view.state.doc.toString(); + if (current !== content) { + view.dispatch({ + changes: { from: 0, to: current.length, insert: content }, + }); + } + }, [content]); + return
; });