"use client"; import { useEffect, useRef } from "react"; import Editor from "@monaco-editor/react"; import { useTheme } from "next-themes"; import { Button } from "@kandev/ui/button"; import { IconMessagePlus } from "@tabler/icons-react"; import { getMonacoLanguage } from "@/lib/editor/language-map"; import { EDITOR_FONT_FAMILY, EDITOR_FONT_SIZE } from "@/lib/theme/editor-theme"; import { CommentForm } from "@/components/diff/comment-form"; import { CommentDisplay } from "@/components/diff/comment-display"; import { useEditorViewZoneComments } from "@/hooks/use-editor-view-zone-comments"; import { MonacoEditorToolbar } from "./monaco-editor-toolbar"; import { useMonacoEditorComments } from "./use-monaco-editor-state"; import { useMonacoEditorLsp, useMonacoDiffDecorations } from "./use-monaco-editor-lsp"; import { initMonacoThemes } from "./monaco-init"; initMonacoThemes(); type MonacoCodeEditorProps = { path: string; content: string; originalContent: string; isDirty: boolean; hasRemoteUpdate?: boolean; vcsDiff?: string; isSaving: boolean; sessionId?: string; worktreePath?: string; enableComments?: boolean; onToggleMarkdownPreview?: () => void; onChange: (newContent: string) => void; onSave: () => void; onReloadFromAgent?: () => void; onDelete?: () => void; }; function getMonacoTheme(resolvedTheme: string | undefined): string { return resolvedTheme === "dark" ? "kandev-dark" : "kandev-light"; } const EDITOR_OPTIONS = { fontSize: EDITOR_FONT_SIZE, fontFamily: EDITOR_FONT_FAMILY, lineHeight: 18, minimap: { enabled: false }, scrollBeyondLastLine: false, smoothScrolling: true, cursorSmoothCaretAnimation: "on" as const, glyphMargin: false, lineDecorationsWidth: 10, folding: true, lineNumbers: "on" as const, renderLineHighlight: "line" as const, automaticLayout: true, scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 }, padding: { top: 4 }, "semanticHighlighting.enabled": true, }; type EditorState = ReturnType; function buildCommentZones( state: EditorState, addZone: (line: number, height: number, node: React.ReactNode) => void, ) { for (const comment of state.comments) { const isEditing = state.editingCommentId === comment.id; const node = isEditing ? (
state.handleCommentUpdateRef.current(comment.id, c)} onCancel={() => state.setEditingComment(null)} isEditing />
) : (
state.handleCommentDeleteRef.current(comment.id)} onEdit={() => state.setEditingComment(comment.id)} onRun={() => state.handleCommentRunRef.current(comment)} showCode={false} compact />
); addZone(comment.endLine, isEditing ? 120 : 32, node); } if (state.formZoneRange) { addZone( state.formZoneRange.endLine, 120,
state.handleCommentSubmitRef.current(c)} onSubmitAndRun={(c) => state.handleCommentSubmitAndRunRef.current(c)} onCancel={() => { state.setFormZoneRange(null); state.clearGutterSelection(); }} />
, ); } } function useMonacoCodeEditorSetup(props: MonacoCodeEditorProps) { const { path, content, originalContent, isDirty, vcsDiff, sessionId, worktreePath, enableComments = false, onChange, onSave, } = props; const contentRef = useRef(content); const wrapperRef = useRef(null); const language = getMonacoLanguage(path); const state = useMonacoEditorComments({ path, enableComments, sessionId, wrapperRef, onChange, onSave, contentRef, }); const lsp = useMonacoEditorLsp({ sessionId, worktreePath, language, path, contentRef, editorRef: state.editorRef, }); const { diffStats } = useMonacoDiffDecorations({ originalContent, isDirty, showDiffIndicators: state.showDiffIndicators, vcsDiff, editorReady: state.editorInstance, contentRef, editorRef: state.editorRef, diffDecorationsRef: state.diffDecorationsRef, }); useEffect(() => { contentRef.current = content; }, [content]); useEditorViewZoneComments( state.editorInstance, [state.comments, state.formZoneRange, state.editingCommentId], (addZone) => buildCommentZones(state, addZone), ); const options = { ...EDITOR_OPTIONS, wordWrap: state.wrapEnabled ? ("on" as const) : ("off" as const), }; return { contentRef, wrapperRef, language, state, lsp, diffStats, options }; } export function MonacoCodeEditor(props: MonacoCodeEditorProps) { const { path, content, isDirty, hasRemoteUpdate = false, vcsDiff, isSaving, sessionId, worktreePath, enableComments = false, onToggleMarkdownPreview, onSave, onReloadFromAgent, onDelete, } = props; const { resolvedTheme } = useTheme(); const { wrapperRef, language, state, lsp, diffStats, options } = useMonacoCodeEditorSetup(props); return (
state.setWrapEnabled(!state.wrapEnabled)} onToggleDiffIndicators={() => state.setShowDiffIndicators(!state.showDiffIndicators)} onSave={onSave} onReloadFromAgent={onReloadFromAgent} onDelete={onDelete} onToggleMarkdownPreview={onToggleMarkdownPreview} />
Loading editor...
} /> {state.floatingButtonPos && !state.formZoneRange && ( )}
); }