Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | import { useEffect, useRef, type JSX } from "react"; import { EditorState, type Extension } from "@codemirror/state"; import { EditorView, lineNumbers } from "@codemirror/view"; import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language"; import { oneDark } from "@codemirror/theme-one-dark"; import { languageExtensionForUri } from "./languages.js"; import styles from "./DocPane.module.scss"; /** Props for {@link CodePreview}. */ export interface CodePreviewProps { /** The file's text content. */ content: string; /** The file `file://` URI (drives language detection). */ uri: string; /** Whether the app is in a dark theme (selects the CodeMirror highlight). */ dark: boolean; } /** * Read-only CodeMirror 6 preview of a text/code file (#1396). * * A thin manual `EditorView` wrapper (no React binding lib, so no extra React * peer surface). The editor is created once per `(uri, dark)`; external content * changes (the agent rewrote the file → bridge re-read) are pushed in via * `view.dispatch` so the view refreshes without losing the instance. This is * also the editable seam for v1 (#1397). * * Lazy-loaded by {@link DocPane} so CodeMirror stays in its own async chunk. */ export function CodePreview({ content, uri, dark }: CodePreviewProps): JSX.Element { const hostRef = useRef<HTMLDivElement | null>(null); const viewRef = useRef<EditorView | null>(null); // Latest content, read at editor-creation time without forcing a recreate on // every content change (refs are stable, so they don't belong in effect deps). const contentRef = useRef<string>(content); contentRef.current = content; // Create (and recreate on language/theme change) the editor. useEffect(() => { const host = hostRef.current; if (host === null) { return; } const language = languageExtensionForUri(uri); const extensions: Extension[] = [ lineNumbers(), EditorView.editable.of(false), EditorState.readOnly.of(true), EditorView.lineWrapping, syntaxHighlighting(defaultHighlightStyle, { fallback: true }), ...(dark ? [oneDark] : []), ...(language ? [language] : []), ]; const view = new EditorView({ state: EditorState.create({ doc: contentRef.current, extensions }), parent: host, }); viewRef.current = view; return () => { view.destroy(); viewRef.current = null; }; }, [uri, dark]); // Reflect external content changes (live refresh) into the existing editor. useEffect(() => { const view = viewRef.current; if (view === null) { return; } const current = view.state.doc.toString(); if (current !== content) { view.dispatch({ changes: { from: 0, to: current.length, insert: content } }); } }, [content]); return <div ref={hostRef} className={styles.codeHost} data-testid="doc-code" />; } |