# All files / src/components/docpane CodePreview.tsx

0% Statements 0/49
0% Branches 0/1
0% Functions 0/1
0% Lines 0/49

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" />;
    }