import { Component, createRef, type JSX } from "react"; import { AtomIcon } from "../../../../../app/renderer/components/icons/AtomIcon"; import { translate } from "../../../../../i18n/index"; import { AgentPathTailLabel } from "../AgentPathTailLabel"; import { loadMonaco, type MonacoModule } from "./loadMonaco"; interface MonacoDiffResources { monaco: MonacoModule; editor: import("monaco-editor").editor.IStandaloneDiffEditor; originalModel: import("monaco-editor").editor.ITextModel; modifiedModel: import("monaco-editor").editor.ITextModel; } interface MonacoDiffContent { oldValue: string; newValue: string; language: string; } type MonacoDiffEditorSurfaceProps = MonacoDiffContent; interface MonacoDiffEditorSurfaceState { loadError: Error | null; loading: boolean; } export function AgentMonacoDiffViewer({ path, oldValue, newValue, flat = false, showHeader = true }: { path?: string | null; oldValue: string; newValue: string; flat?: boolean; showHeader?: boolean; }): JSX.Element { "use memo"; return (
{showHeader ? (
) : null}
); } // Monaco's diff editor and its two text models form one imperative resource // lifetime. A class component makes the required mount/update/unmount ordering // explicit without coordinating multiple React effects. class MonacoDiffEditorSurface extends Component< MonacoDiffEditorSurfaceProps, MonacoDiffEditorSurfaceState > { private readonly containerRef = createRef(); private resources: MonacoDiffResources | null = null; private loadGeneration = 0; state: MonacoDiffEditorSurfaceState = { loadError: null, loading: true }; componentDidMount(): void { const loadGeneration = ++this.loadGeneration; void loadMonaco() .then((monaco) => { const container = this.containerRef.current; if (loadGeneration !== this.loadGeneration || !container) { return; } this.resources = createMonacoDiffResources( monaco, container, this.props ); this.setState({ loading: false }); }) .catch((error: unknown) => { if (loadGeneration === this.loadGeneration) { this.setState({ loadError: error instanceof Error ? error : new Error(String(error)) }); } }); } componentDidUpdate(): void { if (this.resources) { updateMonacoDiffResources(this.resources, this.props); } } componentWillUnmount(): void { this.loadGeneration += 1; const resources = this.resources; this.resources = null; if (resources) { disposeMonacoDiffResources(resources); } } render(): JSX.Element { if (this.state.loadError) { throw this.state.loadError; } return (
{this.state.loading ? (
) : null}
); } } function createMonacoDiffResources( monaco: MonacoModule, container: HTMLDivElement, content: MonacoDiffContent ): MonacoDiffResources { const originalModel = monaco.editor.createModel( content.oldValue, content.language ); const modifiedModel = monaco.editor.createModel( content.newValue, content.language ); let editor: import("monaco-editor").editor.IStandaloneDiffEditor | null = null; try { monaco.editor.setTheme("light"); editor = monaco.editor.createDiffEditor(container, { readOnly: true, renderSideBySide: true, minimap: { enabled: false }, scrollBeyondLastLine: false, wordWrap: "on", automaticLayout: true }); editor.setModel({ original: originalModel, modified: modifiedModel }); return { monaco, editor, originalModel, modifiedModel }; } catch (error) { if (editor) { try { editor.setModel(null); } finally { try { editor.dispose(); } finally { disposeMonacoModels(originalModel, modifiedModel); } } } else { disposeMonacoModels(originalModel, modifiedModel); } throw error; } } function updateMonacoDiffResources( resources: MonacoDiffResources, content: MonacoDiffContent ): void { if (resources.originalModel.getValue() !== content.oldValue) { resources.originalModel.setValue(content.oldValue); } if (resources.modifiedModel.getValue() !== content.newValue) { resources.modifiedModel.setValue(content.newValue); } const currentLanguage = resources.originalModel.getLanguageId(); if (currentLanguage !== content.language) { resources.monaco.editor.setModelLanguage( resources.originalModel, content.language ); resources.monaco.editor.setModelLanguage( resources.modifiedModel, content.language ); } } function disposeMonacoDiffResources(resources: MonacoDiffResources): void { try { resources.editor.setModel(null); } finally { try { resources.editor.dispose(); } finally { disposeMonacoModels(resources.originalModel, resources.modifiedModel); } } } function disposeMonacoModels( originalModel: import("monaco-editor").editor.ITextModel, modifiedModel: import("monaco-editor").editor.ITextModel ): void { try { originalModel.dispose(); } finally { modifiedModel.dispose(); } } function languageForPath(path: string | null | undefined): string { const extension = path?.split(".").pop()?.toLowerCase(); switch (extension) { case "ts": case "tsx": return "typescript"; case "js": case "jsx": return "javascript"; case "go": return "go"; case "md": return "markdown"; case "json": return "json"; default: return "plaintext"; } }