'use client'; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useExtensionWithDependency } from "@/hooks/useExtensionWithDependency"; import { useKeymapType } from "@/hooks/useKeymapType"; import { useSyntaxHighlightingExtension } from "@/hooks/useSyntaxHighlightingExtension"; import { useThemeNormalized } from "@/hooks/useThemeNormalized"; import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension"; import { highlightRanges, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension"; import { SearchResultFileMatch } from "@/lib/schemas"; import { defaultKeymap } from "@codemirror/commands"; import { search } from "@codemirror/search"; import { EditorView, keymap } from "@codemirror/view"; import { Cross1Icon, FileIcon } from "@radix-ui/react-icons"; import { Scrollbar } from "@radix-ui/react-scroll-area"; import { vim } from "@replit/codemirror-vim"; import CodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror'; import clsx from "clsx"; import { ArrowDown, ArrowUp } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; export interface CodePreviewFile { content: string; filepath: string; link?: string; matches: SearchResultFileMatch[]; language: string; } interface CodePreviewPanelProps { file?: CodePreviewFile; selectedMatchIndex: number; onSelectedMatchIndexChange: (index: number) => void; onClose: () => void; } export const CodePreviewPanel = ({ file, selectedMatchIndex, onSelectedMatchIndexChange, onClose, }: CodePreviewPanelProps) => { const editorRef = useRef(null); const [ keymapType ] = useKeymapType(); const { theme } = useThemeNormalized(); const [gutterWidth, setGutterWidth] = useState(0); const keymapExtension = useExtensionWithDependency( editorRef.current?.view ?? null, () => { switch (keymapType) { case "default": return keymap.of(defaultKeymap); case "vim": return vim(); } }, [keymapType] ); const syntaxHighlighting = useSyntaxHighlightingExtension(file?.language ?? '', editorRef.current?.view); const extensions = useMemo(() => { return [ keymapExtension, gutterWidthExtension, syntaxHighlighting, searchResultHighlightExtension(), search({ top: true, }), EditorView.updateListener.of(update => { const width = update.view.plugin(gutterWidthExtension)?.width; if (width) { setGutterWidth(width); } }), ]; }, [keymapExtension, syntaxHighlighting]); const ranges = useMemo(() => { if (!file || !file.matches.length) { return []; } return file.matches.flatMap((match) => { return match.Ranges; }) }, [file]); useEffect(() => { if (!file || !editorRef.current?.view) { return; } highlightRanges(selectedMatchIndex, ranges, editorRef.current.view); }, [ranges, selectedMatchIndex, file]); const onUpClicked = useCallback(() => { onSelectedMatchIndexChange(selectedMatchIndex - 1); }, [onSelectedMatchIndexChange, selectedMatchIndex]); const onDownClicked = useCallback(() => { onSelectedMatchIndexChange(selectedMatchIndex + 1); }, [onSelectedMatchIndexChange, selectedMatchIndex]); return (
{ if (file?.link) { window.open(file.link, "_blank"); } }} > {file?.filepath}
{file && file.matches.length > 0 && ( <>

{`${selectedMatchIndex + 1} of ${ranges.length}`}

)}
) }