Skip to content

Commit 24238a4

Browse files
committed
feat(diff): enhance DiffView with syntax highlighting and improved styling
- Integrate highlighter for code syntax based on file language - Adjust background colors for additions and deletions to match VSCode styles - Refactor rendering logic to include inline highlighting and gutter styles
1 parent a777769 commit 24238a4

File tree

1 file changed

+93
-32
lines changed

1 file changed

+93
-32
lines changed

webview-ui/src/components/common/DiffView.tsx

Lines changed: 93 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import { memo, useMemo } from "react"
1+
import { memo, useMemo, useEffect, useState } from "react"
22
import { 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

48
interface 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

Comments
 (0)