import { useEffect, useRef } from "react"; import { Compartment, EditorState } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; import { getLanguageForFile } from "@getpaseo/highlight"; import type { WorkspaceFileLocation } from "@/workspace/file-open"; import type { EditorVisualTheme } from "../editor/extensions.web"; import { editorTheme } from "../editor/extensions.web"; import { selectSourcePresentation, type SourcePresentation } from "./presentation"; interface FileSourceViewProps { content: string; filename: string; location: WorkspaceFileLocation; navigationRevision: number; size: number; theme: EditorVisualTheme; tooLargeMessage: string; } const languageCompartment = new Compartment(); const themeCompartment = new Compartment(); export function FileSourceView({ content, filename, location, navigationRevision, size, theme, tooLargeMessage, }: FileSourceViewProps) { const presentation = selectSourcePresentation({ size, platform: "web" }); if (presentation === "unsupported") { return (
{tooLargeMessage}
); } return ( ); } function ReadonlyCodeMirror({ content, filename, location, navigationRevision, presentation, theme, }: Omit & { presentation: Exclude; }) { const hostRef = useRef(null); const viewRef = useRef(null); const initial = useRef({ content, filename, presentation, theme }); useEffect(() => { if (!hostRef.current) return; const values = initial.current; const view = new EditorView({ parent: hostRef.current, state: EditorState.create({ doc: values.content, extensions: [ EditorState.readOnly.of(true), EditorView.editable.of(false), languageCompartment.of( languageFor({ filename: values.filename, presentation: values.presentation }), ), themeCompartment.of(editorTheme(values.theme)), ], }), }); viewRef.current = view; return () => { view.destroy(); viewRef.current = null; }; }, []); useEffect(() => { const view = viewRef.current; if (!view || view.state.doc.toString() === content) return; view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: content } }); }, [content]); useEffect(() => { viewRef.current?.dispatch({ effects: [ languageCompartment.reconfigure(languageFor({ filename, presentation })), themeCompartment.reconfigure(editorTheme(theme)), ], }); }, [filename, presentation, theme]); useEffect(() => { const view = viewRef.current; if (!view || !location.lineStart) return; const line = Math.min(location.lineStart, view.state.doc.lines); const from = view.state.doc.line(line).from; view.dispatch({ effects: EditorView.scrollIntoView(from, { y: "center" }) }); }, [location.lineStart, navigationRevision]); return
; } function languageFor(input: { filename: string; presentation: Exclude; }) { return input.presentation === "highlighted" ? (getLanguageForFile(input.filename)?.extension ?? []) : []; } const HOST_STYLE = { flex: 1, minHeight: 0, overflow: "hidden" } as const; const UNSUPPORTED_STYLE = { alignItems: "center", display: "flex", flex: 1, justifyContent: "center", } as const;