1+ 'use client' ;
2+
3+ import { useExtensionWithDependency } from "@/hooks/useExtensionWithDependency" ;
4+ import { useSyntaxHighlightingExtension } from "@/hooks/useSyntaxHighlightingExtension" ;
5+ import { useThemeNormalized } from "@/hooks/useThemeNormalized" ;
6+ import { lineOffsetExtension } from "@/lib/extensions/lineOffsetExtension" ;
7+ import { SearchResultRange } from "@/lib/schemas" ;
8+ import CodeMirror , { Decoration , DecorationSet , EditorState , EditorView , ReactCodeMirrorRef , StateField , Transaction } from "@uiw/react-codemirror" ;
9+ import { useMemo , useRef } from "react" ;
10+
11+ const markDecoration = Decoration . mark ( {
12+ class : "cm-searchMatch"
13+ } ) ;
14+
15+ const cmTheme = EditorView . baseTheme ( {
16+ "&light .cm-searchMatch" : {
17+ border : "1px #6b7280ff" ,
18+ } ,
19+ "&dark .cm-searchMatch" : {
20+ border : "1px #d1d5dbff" ,
21+ } ,
22+ } ) ;
23+
24+ interface CodePreviewProps {
25+ content : string ,
26+ language : string ,
27+ ranges : SearchResultRange [ ] ,
28+ lineOffset : number ,
29+ }
30+
31+ export const CodePreview = ( {
32+ content,
33+ language,
34+ ranges,
35+ lineOffset,
36+ } : CodePreviewProps ) => {
37+ const editorRef = useRef < ReactCodeMirrorRef > ( null ) ;
38+ const { theme } = useThemeNormalized ( ) ;
39+
40+ const syntaxHighlighting = useSyntaxHighlightingExtension ( language , editorRef . current ?. view ) ;
41+
42+ const rangeHighlighting = useExtensionWithDependency ( editorRef . current ?. view ?? null , ( ) => {
43+ return [
44+ StateField . define < DecorationSet > ( {
45+ create ( editorState : EditorState ) {
46+ const document = editorState . doc ;
47+
48+ const decorations = ranges
49+ . sort ( ( a , b ) => {
50+ return a . Start . ByteOffset - b . Start . ByteOffset ;
51+ } )
52+ . filter ( ( { Start, End } ) => {
53+ const startLine = Start . LineNumber - lineOffset ;
54+ const endLine = End . LineNumber - lineOffset ;
55+
56+ if (
57+ startLine < 1 ||
58+ endLine < 1 ||
59+ startLine > document . lines ||
60+ endLine > document . lines
61+ ) {
62+ return false ;
63+ }
64+ return true ;
65+ } )
66+ . map ( ( { Start, End } ) => {
67+ const startLine = Start . LineNumber - lineOffset ;
68+ const endLine = End . LineNumber - lineOffset ;
69+
70+ const from = document . line ( startLine ) . from + Start . Column - 1 ;
71+ const to = document . line ( endLine ) . from + End . Column - 1 ;
72+ return markDecoration . range ( from , to ) ;
73+ } ) ;
74+
75+ return Decoration . set ( decorations ) ;
76+ } ,
77+ update ( highlights : DecorationSet , _transaction : Transaction ) {
78+ return highlights ;
79+ } ,
80+ provide : ( field ) => EditorView . decorations . from ( field ) ,
81+ } ) ,
82+ cmTheme
83+ ] ;
84+ } , [ ranges , lineOffset ] ) ;
85+
86+ const extensions = useMemo ( ( ) => {
87+ return [
88+ syntaxHighlighting ,
89+ lineOffsetExtension ( lineOffset ) ,
90+ rangeHighlighting ,
91+ ] ;
92+ } , [ syntaxHighlighting , lineOffset , rangeHighlighting ] ) ;
93+
94+ return (
95+ < CodeMirror
96+ ref = { editorRef }
97+ readOnly = { true }
98+ editable = { false }
99+ value = { content }
100+ theme = { theme === "dark" ? "dark" : "light" }
101+ basicSetup = { {
102+ lineNumbers : true ,
103+ syntaxHighlighting : true ,
104+
105+ // Disable all this other stuff...
106+ ... {
107+ foldGutter : false ,
108+ highlightActiveLineGutter : false ,
109+ highlightSpecialChars : false ,
110+ history : false ,
111+ drawSelection : false ,
112+ dropCursor : false ,
113+ allowMultipleSelections : false ,
114+ indentOnInput : false ,
115+ bracketMatching : false ,
116+ closeBrackets : false ,
117+ autocompletion : false ,
118+ rectangularSelection : false ,
119+ crosshairCursor : false ,
120+ highlightActiveLine : false ,
121+ highlightSelectionMatches : false ,
122+ closeBracketsKeymap : false ,
123+ defaultKeymap : false ,
124+ searchKeymap : false ,
125+ historyKeymap : false ,
126+ foldKeymap : false ,
127+ completionKeymap : false ,
128+ lintKeymap : false ,
129+ }
130+ } }
131+ extensions = { extensions }
132+ />
133+ )
134+ }
0 commit comments