Polish diff view hunk controls

This commit is contained in:
Bohdan Triapitsyn
2026-06-14 00:03:02 +03:00
parent 876738229f
commit 8b318d2776
6 changed files with 122 additions and 20 deletions
@@ -119,7 +119,7 @@ const TOOL_DIFF_METRICS = {
lineHeight: 24,
diffHeaderHeight: 44,
hunkSeparatorHeight: 24,
fileGap: 0,
spacing: 0,
};
const usePierreThemeConfig = (): PierreThemeConfig => {
@@ -1388,7 +1388,7 @@ const TOOL_DIFF_METRICS = {
lineHeight: 24,
diffHeaderHeight: 44,
hunkSeparatorHeight: 24,
fileGap: 0,
spacing: 0,
};
const TOOL_COLLAPSED_CUSTOM_STYLE: React.CSSProperties = {
@@ -599,15 +599,15 @@ const MultiFileDiffEntry = React.memo<MultiFileDiffEntryProps>(({
return (
<div ref={setSectionRef} className="scroll-mt-9 border-b border-[var(--interactive-border)]/40 last:border-b-0">
<div className="sticky top-0 z-10 bg-[var(--surface-muted)]">
<div className="sticky top-0 z-10 border-b border-[var(--interactive-border)]/35 bg-[var(--surface-elevated)]/90 backdrop-blur-md supports-[backdrop-filter]:bg-[var(--surface-elevated)]/80">
<button
type="button"
onClick={handleToggle}
className={cn(
'group/header relative flex w-full items-center gap-2 px-3 py-1.5 overflow-hidden',
'group/header relative flex min-h-9 w-full items-center gap-2 overflow-hidden px-3 py-2',
'bg-transparent',
'text-muted-foreground hover:text-foreground',
isSelected ? 'bg-[var(--interactive-selection)]/40' : null
isSelected ? 'bg-[var(--interactive-selection)]/35' : null
)}
>
<div className="absolute inset-0 pointer-events-none group-hover/header:bg-[var(--interactive-hover)]/50" />
@@ -91,6 +91,104 @@ const WEBKIT_SCROLL_FIX_CSS = `
height: 24px !important;
}
}
[data-separator="line-info-basic"] {
height: 24px !important;
background: var(--diffs-bg) !important;
position: relative;
}
[data-diff-type="single"] [data-gutter],
[data-diff-type="split"] [data-deletions] [data-gutter] {
[data-separator-wrapper] {
position: absolute;
left: 100%;
display: flex;
align-items: center;
gap: unset;
width: max-content;
background: transparent;
color: var(--diffs-fg-number);
font-family: var(--diffs-header-font-family, var(--font-sans));
font-size: 0.75rem;
line-height: 1;
margin-left: calc(-2ch - 2px);
}
[data-separator-wrapper][data-separator-multi-button] {
margin-left: calc(-3ch - 2px);
}
[data-expand-button],
[data-separator-content] {
display: block;
align-self: unset;
min-width: unset;
min-height: unset;
padding: 0;
flex-shrink: 0;
grid-column: unset;
border: none;
width: auto;
height: auto;
background-color: transparent;
color: inherit;
font: inherit;
}
[data-expand-button]:not([data-expand-all-button]) {
&[data-expand-down]::before {
content: '\\2191';
}
&[data-expand-up]::before {
content: '\\2193';
}
&[data-expand-both]::before {
content: '\\2195';
}
svg {
display: none;
}
}
[data-separator-content] {
background: transparent;
margin-left: calc(2px + 1ch);
}
[data-expand-all-button] {
position: relative;
margin-left: 14px;
text-transform: lowercase;
}
[data-expand-all-button]::before {
content: '';
display: block;
position: absolute;
top: 50%;
left: -8px;
margin-top: -1px;
width: 3px;
height: 3px;
border-radius: 2px;
background-color: var(--diffs-fg-number);
pointer-events: none;
}
[data-separator-content]:hover,
[data-expand-button]:hover,
[data-expand-all-button]:hover {
color: var(--diffs-fg);
}
[data-expand-all-button]:hover {
text-decoration: underline;
}
}
`;
// Fast cache key - use length + samples instead of full hash
@@ -267,7 +365,7 @@ const virtualizerCache = new WeakMap<Document | HTMLElement, VirtualizerEntry>()
const VIRTUAL_METRICS: Partial<VirtualFileMetrics> = {
lineHeight: 24,
hunkSeparatorHeight: 24,
fileGap: 0,
spacing: 0,
};
function resolveVirtualizerTarget(container: HTMLElement): VirtualizerTarget {
@@ -324,7 +422,7 @@ function acquireSharedVirtualizer(container: HTMLElement): SharedVirtualizer | n
}
const wakeVirtualizer = (
instance: PierreFileDiff<unknown>,
instance: PierreFileDiff<PierreAnnotationData>,
sharedVirtualizer: SharedVirtualizer | null,
forceUpdate: () => void,
): (() => void) => {
@@ -545,11 +643,11 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
const diffRootRef = useRef<HTMLDivElement | null>(null);
const diffContainerRef = useRef<HTMLDivElement | null>(null);
const diffInstanceRef = useRef<PierreFileDiff<unknown> | null>(null);
const diffInstanceRef = useRef<PierreFileDiff<PierreAnnotationData> | null>(null);
const sharedVirtualizerRef = useRef<SharedVirtualizer | null>(null);
const instanceVirtualizerRef = useRef<Virtualizer | null>(null);
const instanceWorkerPoolRef = useRef<unknown>(null);
const instanceVirtualHunkSeparatorsRef = useRef<FileDiffOptions<unknown>['hunkSeparators'] | undefined>(undefined);
const instanceVirtualHunkSeparatorsRef = useRef<FileDiffOptions<PierreAnnotationData>['hunkSeparators'] | undefined>(undefined);
const instanceFileDiffRef = useRef<FileDiffMetadata | undefined>(undefined);
const instanceOldFileRef = useRef<FileContents | undefined>(undefined);
const instanceNewFileRef = useRef<FileContents | undefined>(undefined);
@@ -755,17 +853,17 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
: false;
if (!instance) {
instance = sharedVirtualizer
? new VirtualizedFileDiff(
options as unknown as FileDiffOptions<unknown>,
? new VirtualizedFileDiff<PierreAnnotationData>(
options as FileDiffOptions<PierreAnnotationData>,
sharedVirtualizer.virtualizer,
VIRTUAL_METRICS,
workerPool,
)
: new PierreFileDiff(options as unknown as FileDiffOptions<unknown>, workerPool);
: new PierreFileDiff(options as FileDiffOptions<PierreAnnotationData>, workerPool);
diffInstanceRef.current = instance;
lastAppliedSelectionRef.current = null;
} else {
instance.setOptions(options as unknown as FileDiffOptions<unknown>);
instance.setOptions(options as FileDiffOptions<PierreAnnotationData>);
}
instanceVirtualizerRef.current = virtualizer;