feat: syntax highlighting in diff preview + fix Allow All button style
- DiffViewer: add syntaxHighlight prop — maps diff lines to highlighted HTML via highlightLines(), passed to DiffLineRow - ToolApprovalDiffPreview: enable syntaxHighlight for file diffs - Allow All button: proper border style matching Deny button sizing
This commit is contained in:
parent
d92ab08416
commit
1f9f0b6390
3 changed files with 47 additions and 11 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
CODE_BG,
|
||||
|
|
@ -19,6 +19,7 @@ import {
|
|||
TAG_TEXT,
|
||||
} from '@renderer/constants/cssVariables';
|
||||
import { getBaseName } from '@renderer/utils/pathUtils';
|
||||
import { highlightLines } from '@renderer/utils/syntaxHighlighter';
|
||||
import { formatTokens } from '@shared/utils/tokenFormatting';
|
||||
import { diffLines as semanticDiffLines } from 'diff';
|
||||
import { Pencil } from 'lucide-react';
|
||||
|
|
@ -33,6 +34,7 @@ interface DiffViewerProps {
|
|||
newString: string; // The new text
|
||||
maxHeight?: string; // CSS max-height class (default: "max-h-96")
|
||||
tokenCount?: number; // Optional token count to display in header
|
||||
syntaxHighlight?: boolean; // Enable syntax highlighting via highlight.js
|
||||
}
|
||||
|
||||
interface DiffLine {
|
||||
|
|
@ -259,9 +261,10 @@ function inferLanguage(fileName: string): string {
|
|||
|
||||
interface DiffLineRowProps {
|
||||
line: DiffLine;
|
||||
highlightedHtml?: string;
|
||||
}
|
||||
|
||||
const DiffLineRow: React.FC<DiffLineRowProps> = ({ line }): React.JSX.Element => {
|
||||
const DiffLineRow: React.FC<DiffLineRowProps> = ({ line, highlightedHtml }): React.JSX.Element => {
|
||||
// Theme-aware styles using CSS variables
|
||||
const getStyles = (
|
||||
type: DiffLine['type']
|
||||
|
|
@ -312,10 +315,18 @@ const DiffLineRow: React.FC<DiffLineRowProps> = ({ line }): React.JSX.Element =>
|
|||
<span className="w-6 shrink-0 select-none" style={{ color: style.text }}>
|
||||
{style.prefix}
|
||||
</span>
|
||||
{/* Content */}
|
||||
<span className="flex-1 whitespace-pre" style={{ color: style.text }}>
|
||||
{line.content ?? ' '}
|
||||
</span>
|
||||
{/* Content — optionally syntax-highlighted via hljs (HTML-escaped, safe) */}
|
||||
{highlightedHtml !== undefined ? (
|
||||
<span
|
||||
className="flex-1 whitespace-pre"
|
||||
style={{ color: style.text }}
|
||||
dangerouslySetInnerHTML={{ __html: highlightedHtml || ' ' }}
|
||||
/>
|
||||
) : (
|
||||
<span className="flex-1 whitespace-pre" style={{ color: style.text }}>
|
||||
{line.content ?? ' '}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -330,6 +341,7 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
|||
newString,
|
||||
maxHeight = 'max-h-96',
|
||||
tokenCount,
|
||||
syntaxHighlight = false,
|
||||
}): React.JSX.Element => {
|
||||
// Compute diff
|
||||
const oldLines = oldString.split(/\r?\n/);
|
||||
|
|
@ -337,6 +349,29 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
|||
const diffLines = generateDiff(oldLines, newLines);
|
||||
const stats = computeStats(diffLines);
|
||||
|
||||
// Syntax highlighting: build a map from content line → highlighted HTML
|
||||
const highlightMap = useMemo(() => {
|
||||
if (!syntaxHighlight) return null;
|
||||
const oldHtml = highlightLines(oldString, fileName);
|
||||
const newHtml = highlightLines(newString, fileName);
|
||||
// Map each diff line to its highlighted HTML by tracking old/new line indices
|
||||
const map = new Map<number, string>();
|
||||
let oldIdx = 0;
|
||||
let newIdx = 0;
|
||||
for (let i = 0; i < diffLines.length; i++) {
|
||||
const line = diffLines[i];
|
||||
if (line.type === 'removed') {
|
||||
map.set(i, oldHtml[oldIdx++] ?? '');
|
||||
} else if (line.type === 'added') {
|
||||
map.set(i, newHtml[newIdx++] ?? '');
|
||||
} else {
|
||||
map.set(i, oldHtml[oldIdx++] ?? '');
|
||||
newIdx++;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}, [syntaxHighlight, oldString, newString, fileName, diffLines]);
|
||||
|
||||
// Infer language from file extension
|
||||
const detectedLanguage = inferLanguage(fileName);
|
||||
|
||||
|
|
@ -396,7 +431,7 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
|
|||
<div className={`overflow-auto font-mono text-xs ${maxHeight}`}>
|
||||
<div className="inline-block min-w-full">
|
||||
{diffLines.map((line, index) => (
|
||||
<DiffLineRow key={index} line={line} />
|
||||
<DiffLineRow key={index} line={line} highlightedHtml={highlightMap?.get(index)} />
|
||||
))}
|
||||
{diffLines.length === 0 && (
|
||||
<div className="px-3 py-2 italic" style={{ color: COLOR_TEXT_MUTED }}>
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ export const ToolApprovalDiffPreview: React.FC<ToolApprovalDiffPreviewProps> = (
|
|||
oldString={diff.oldString}
|
||||
newString={diff.newString}
|
||||
maxHeight="max-h-[300px]"
|
||||
syntaxHighlight
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -288,21 +288,21 @@ export const ToolApprovalSheet: React.FC = () => {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => void updateToolApprovalSettings({ autoAllowAll: true })}
|
||||
className="rounded-md px-2.5 py-1.5 text-[10px] transition-colors"
|
||||
className="rounded-md border px-3.5 py-1.5 text-xs font-medium transition-colors"
|
||||
style={{
|
||||
color: 'var(--color-text-muted)',
|
||||
backgroundColor: 'var(--color-surface-raised)',
|
||||
borderColor: 'var(--color-border-emphasis)',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
Object.assign(e.currentTarget.style, {
|
||||
color: 'var(--color-text-secondary)',
|
||||
backgroundColor: 'var(--color-border-emphasis)',
|
||||
backgroundColor: 'var(--color-surface-raised)',
|
||||
});
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
Object.assign(e.currentTarget.style, {
|
||||
color: 'var(--color-text-muted)',
|
||||
backgroundColor: 'var(--color-surface-raised)',
|
||||
backgroundColor: 'transparent',
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
Loading…
Reference in a new issue