1- import { memo , useMemo } from "react"
1+ import { memo , useMemo , useEffect , useState } from "react"
22import { parsePatch } from "diff"
3+ import { toJsxRuntime } from "hast-util-to-jsx-runtime"
4+ import { Fragment , jsx , jsxs } from "react/jsx-runtime"
5+ import { getHighlighter , normalizeLanguage } from "@src/utils/highlighter"
6+ import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
37
48interface DiffViewProps {
59 source : string
@@ -17,7 +21,62 @@ interface DiffLine {
1721 * DiffView component renders unified diffs with side-by-side line numbers
1822 * matching VSCode's diff editor style
1923 */
20- const DiffView = memo ( ( { source } : DiffViewProps ) => {
24+ const DiffView = memo ( ( { source, filePath } : DiffViewProps ) => {
25+ // Determine language from file path and prepare highlighter
26+ const normalizedLang = useMemo ( ( ) => normalizeLanguage ( getLanguageFromPath ( filePath || "" ) || "txt" ) , [ filePath ] )
27+ const [ highlighter , setHighlighter ] = useState < any > ( null )
28+ const isLightTheme = useMemo (
29+ ( ) => typeof document !== "undefined" && document . body . className . toLowerCase ( ) . includes ( "light" ) ,
30+ [ ] ,
31+ )
32+
33+ useEffect ( ( ) => {
34+ let mounted = true
35+ getHighlighter ( normalizedLang )
36+ . then ( ( h ) => {
37+ if ( mounted ) setHighlighter ( h )
38+ } )
39+ . catch ( ( ) => {
40+ // fall back to plain text if highlighting fails
41+ } )
42+ return ( ) => {
43+ mounted = false
44+ }
45+ } , [ normalizedLang ] )
46+
47+ const renderHighlighted = ( code : string ) : React . ReactNode => {
48+ if ( ! highlighter ) return code
49+ try {
50+ const hast : any = highlighter . codeToHast ( code , {
51+ lang : normalizedLang ,
52+ theme : isLightTheme ? "github-light" : "github-dark" ,
53+ transformers : [
54+ {
55+ pre ( node : any ) {
56+ node . properties . style = "padding:0;margin:0;background:none;"
57+ return node
58+ } ,
59+ code ( node : any ) {
60+ node . properties . class = `hljs language-${ normalizedLang } `
61+ return node
62+ } ,
63+ } ,
64+ ] ,
65+ } )
66+
67+ // Extract just the <code> children to render inline inside our table cell
68+ const codeEl = hast ?. children ?. [ 0 ] ?. children ?. [ 0 ]
69+ const inlineRoot =
70+ codeEl && codeEl . children
71+ ? { type : "element" , tagName : "span" , properties : { } , children : codeEl . children }
72+ : { type : "element" , tagName : "span" , properties : { } , children : hast . children || [ ] }
73+
74+ return toJsxRuntime ( inlineRoot as any , { Fragment, jsx, jsxs } )
75+ } catch {
76+ return code
77+ }
78+ }
79+
2180 // Parse diff and extract line information
2281 const diffLines = useMemo ( ( ) => {
2382 if ( ! source ) return [ ]
@@ -92,22 +151,29 @@ const DiffView = memo(({ source }: DiffViewProps) => {
92151 } } >
93152 < tbody >
94153 { diffLines . map ( ( line , idx ) => {
95- // Backgrounds: tint only the content and +/- gutter, not the line-number columns
96- const contentBg =
154+ // Use VSCode's built-in diff editor color variables with 50% opacity
155+ const gutterBg =
97156 line . type === "addition"
98157 ? "var(--vscode-diffEditor-insertedTextBackground)"
99158 : line . type === "deletion"
100159 ? "var(--vscode-diffEditor-removedTextBackground)"
101- : "transparent"
102- // Use same tint for the +/- gutter for a cohesive band effect
103- const gutterBg = contentBg
160+ : "var(--vscode-editorGroup-border)"
104161
105- const lineColor =
162+ const contentBgStyles =
106163 line . type === "addition"
107- ? "var(--vscode-gitDecoration-addedResourceForeground)"
164+ ? {
165+ backgroundColor :
166+ "color-mix(in srgb, var(--vscode-diffEditor-insertedTextBackground) 70%, transparent)" ,
167+ }
108168 : line . type === "deletion"
109- ? "var(--vscode-gitDecoration-deletedResourceForeground)"
110- : "var(--vscode-editorLineNumber-foreground)"
169+ ? {
170+ backgroundColor :
171+ "color-mix(in srgb, var(--vscode-diffEditor-removedTextBackground) 70%, transparent)" ,
172+ }
173+ : {
174+ backgroundColor :
175+ "color-mix(in srgb, var(--vscode-editorGroup-border) 100%, transparent)" ,
176+ }
111177
112178 return (
113179 < tr key = { idx } >
@@ -118,11 +184,10 @@ const DiffView = memo(({ source }: DiffViewProps) => {
118184 textAlign : "right" ,
119185 paddingRight : "12px" ,
120186 paddingLeft : "8px" ,
121- color : lineColor ,
122- opacity : 0.5 ,
123187 userSelect : "none" ,
124188 verticalAlign : "top" ,
125189 whiteSpace : "nowrap" ,
190+ backgroundColor : gutterBg ,
126191 } } >
127192 { line . oldLineNum || "" }
128193 </ td >
@@ -132,33 +197,22 @@ const DiffView = memo(({ source }: DiffViewProps) => {
132197 width : "45px" ,
133198 textAlign : "right" ,
134199 paddingRight : "12px" ,
135- color : lineColor ,
136- opacity : 0.5 ,
137200 userSelect : "none" ,
138201 verticalAlign : "top" ,
139202 whiteSpace : "nowrap" ,
203+ backgroundColor : gutterBg ,
140204 } } >
141205 { line . newLineNum || "" }
142206 </ td >
143- { /* +/- indicator */ }
207+ { /* Narrow colored gutter (no +/- glyph) */ }
144208 < td
145209 style = { {
146- width : "20px" ,
147- textAlign : "center" ,
210+ width : "12px" ,
148211 backgroundColor : gutterBg ,
149- color :
150- line . type === "addition"
151- ? "var(--vscode-gitDecoration-addedResourceForeground)"
152- : line . type === "deletion"
153- ? "var(--vscode-gitDecoration-deletedResourceForeground)"
154- : "transparent" ,
155- userSelect : "none" ,
156212 verticalAlign : "top" ,
157- paddingRight : "8px" ,
158- } } >
159- { line . type === "addition" ? "+" : line . type === "deletion" ? "−" : "" }
160- </ td >
161- { /* Code content */ }
213+ } }
214+ />
215+ { /* Code content (includes +/- prefix inside the code cell) */ }
162216 < td
163217 style = { {
164218 paddingLeft : "4px" ,
@@ -169,9 +223,16 @@ const DiffView = memo(({ source }: DiffViewProps) => {
169223 fontFamily : "var(--vscode-editor-font-family)" ,
170224 color : "var(--vscode-editor-foreground)" ,
171225 width : "100%" ,
172- backgroundColor : contentBg ,
226+ ... contentBgStyles ,
173227 } } >
174- { line . content }
228+ < span
229+ style = { {
230+ color : line . type === "context" ? "transparent" : "#ffffff" ,
231+ userSelect : "none" ,
232+ } } >
233+ { line . type === "addition" ? "+ " : line . type === "deletion" ? "- " : "" }
234+ </ span >
235+ { renderHighlighted ( line . content ) }
175236 </ td >
176237 </ tr >
177238 )
0 commit comments