Skip to content

Commit accd0c6

Browse files
committed
Add code diff highlighting feature
Implements #154 - Add ability to highlight code differences within lines Features: - New diff mode toggle in controls panel - Automatic detection of + and - prefixed lines - Visual styling with green background for additions - Visual styling with red background for deletions - Diff markers (+ and -) displayed at line start - URL state persistence for diff mode - Works seamlessly with existing line highlighting Technical implementation: - Added diffModeAtom to store for state management - Extended Shiki transformer to detect and style diff lines - Created DiffModeControl component with Switch UI - Added CSS styling for diff-add and diff-remove classes - Integrated with existing code highlighting system
1 parent 531975d commit accd0c6

5 files changed

Lines changed: 70 additions & 2 deletions

File tree

app/(navigation)/(code)/components/Controls.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import LanguageControl from "./LanguageControl";
88
import PaddingControl from "./PaddingControl";
99
import ThemeControl from "./ThemeControl";
1010
import LineNumberControl from "./LineNumberControl";
11+
import { DiffModeControl } from "./DiffModeControl";
1112

1213
const Controls: React.FC = () => {
1314
return (
@@ -16,6 +17,7 @@ const Controls: React.FC = () => {
1617
<BackgroundControl />
1718
<DarkModeControl />
1819
<LineNumberControl />
20+
<DiffModeControl />
1921
<PaddingControl />
2022
<LanguageControl />
2123
</div>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"use client";
2+
3+
import { useAtom } from "jotai";
4+
import { diffModeAtom } from "../store";
5+
import { Switch } from "@/components/switch";
6+
7+
export function DiffModeControl() {
8+
const [diffMode, setDiffMode] = useAtom(diffModeAtom);
9+
10+
return (
11+
<div className="flex items-center gap-2">
12+
<label htmlFor="diff-mode" className="text-sm text-gray-11 cursor-default">
13+
Diff
14+
</label>
15+
<Switch id="diff-mode" checked={diffMode} onCheckedChange={setDiffMode} />
16+
</div>
17+
);
18+
}

app/(navigation)/(code)/components/Editor.module.css

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,32 @@
4848
content: "";
4949
}
5050
}
51+
52+
:global(.diff-add) {
53+
background-color: rgba(46, 160, 67, 0.15) !important;
54+
55+
&:before {
56+
position: absolute;
57+
left: 0;
58+
padding-left: 8px;
59+
color: rgba(46, 160, 67, 0.8);
60+
content: "+";
61+
font-weight: 600;
62+
}
63+
}
64+
65+
:global(.diff-remove) {
66+
background-color: rgba(248, 81, 73, 0.15) !important;
67+
68+
&:before {
69+
position: absolute;
70+
left: 0;
71+
padding-left: 8px;
72+
color: rgba(248, 81, 73, 0.8);
73+
content: "-";
74+
font-weight: 600;
75+
}
76+
}
5177
}
5278

5379
.isHighlightingLines {

app/(navigation)/(code)/components/HighlightedCode.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react";
33
import { Language, LANGUAGES } from "../util/languages";
44

55
import styles from "./Editor.module.css";
6-
import { highlightedLinesAtom, highlighterAtom, loadingLanguageAtom } from "../store";
6+
import { highlightedLinesAtom, highlighterAtom, loadingLanguageAtom, diffModeAtom } from "../store";
77
import { useAtom, useAtomValue, useSetAtom } from "jotai";
88
import { themeDarkModeAtom, themeAtom } from "../store/themes";
99

@@ -17,6 +17,7 @@ const HighlightedCode: React.FC<PropTypes> = ({ selectedLanguage, code }) => {
1717
const highlighter = useAtomValue(highlighterAtom);
1818
const setIsLoadingLanguage = useSetAtom(loadingLanguageAtom);
1919
const highlightedLines = useAtomValue(highlightedLinesAtom);
20+
const diffMode = useAtomValue(diffModeAtom);
2021
const darkMode = useAtomValue(themeDarkModeAtom);
2122
const theme = useAtomValue(themeAtom);
2223
const themeName = theme.id === "tailwind" ? (darkMode ? "tailwind-dark" : "tailwind-light") : "css-variables";
@@ -49,6 +50,16 @@ const HighlightedCode: React.FC<PropTypes> = ({ selectedLanguage, code }) => {
4950
line(node, line) {
5051
node.properties["data-line"] = line;
5152
if (highlightedLines.includes(line)) this.addClassToHast(node, "highlighted-line");
53+
54+
// Handle diff mode
55+
if (diffMode) {
56+
const lineContent = code.split("\n")[line - 1];
57+
if (lineContent?.startsWith("+")) {
58+
this.addClassToHast(node, "diff-add");
59+
} else if (lineContent?.startsWith("-")) {
60+
this.addClassToHast(node, "diff-remove");
61+
}
62+
}
5263
},
5364
},
5465
],
@@ -58,7 +69,16 @@ const HighlightedCode: React.FC<PropTypes> = ({ selectedLanguage, code }) => {
5869
generateHighlightedHtml().then((newHtml) => {
5970
setHighlightedHtml(newHtml);
6071
});
61-
}, [code, selectedLanguage, highlighter, setIsLoadingLanguage, setHighlightedHtml, highlightedLines, themeName]);
72+
}, [
73+
code,
74+
selectedLanguage,
75+
highlighter,
76+
setIsLoadingLanguage,
77+
setHighlightedHtml,
78+
highlightedLines,
79+
themeName,
80+
diffMode,
81+
]);
6282

6383
return (
6484
<div

app/(navigation)/(code)/store/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ export const highlightedLinesAtom = atomWithHash<number[]>("highlightedLines", [
2929
return str ? str.split(",").map(Number) : [];
3030
},
3131
});
32+
33+
export const diffModeAtom = atomWithHash<boolean>("diff", false);

0 commit comments

Comments
 (0)