History

Open diff files in editor from diff header clicks

- Add click handling on diff file headers (`data-title`) to open files via native `openInEditor` - Resolve file paths against active thread/project cwd before launching editor - Add header hover affordance and tighten diff panel spacing

Julius Marminge committed 8 months ago

Commit 9a30a501 parent 206eb6a1 file · +44 −3Browse files

Unified · Split

apps/web/src/components/DiffPanel.tsx+44−3
@@ -7,6 +7,8 @@ import { ChevronLeftIcon, ChevronRightIcon, Columns2Icon, Rows3Icon } from "luci · 6 unmodified lines
7import { useCallback, useEffect, useMemo, useRef, useState } from "react";7import { useCallback, useEffect, useMemo, useRef, useState } from "react";
8import { checkpointDiffQueryOptions } from "~/lib/providerReactQuery";8import { checkpointDiffQueryOptions } from "~/lib/providerReactQuery";
9import { cn } from "~/lib/utils";9import { cn } from "~/lib/utils";
10import { readNativeApi } from "../nativeApi";
11import { preferredTerminalEditor, resolvePathLinkTarget } from "../terminal-links";
10import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch";12import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch";
11import { isElectron } from "../env";13import { isElectron } from "../env";
12import { useTheme } from "../hooks/useTheme";14import { useTheme } from "../hooks/useTheme";
@@ -58,6 +60,21 @@ const DIFF_PANEL_UNSAFE_CSS = ` · 45 unmodified lines
58 border-block-color: var(--border) !important;60 border-block-color: var(--border) !important;
59 color: var(--foreground) !important;61 color: var(--foreground) !important;
60}62}
63
64[data-title] {
65 cursor: pointer;
66 transition:
67 color 120ms ease,
68 text-decoration-color 120ms ease;
69 text-decoration: underline;
70 text-decoration-color: transparent;
71 text-underline-offset: 2px;
72}
73
74[data-title]:hover {
75 color: color-mix(in srgb, var(--foreground) 84%, var(--primary)) !important;
76 text-decoration-color: currentColor;
77}
61`;78`;
62 79
63type RenderablePatch =80type RenderablePatch =
@@ -144,6 +161,8 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { · 80 unmodified lines
144 const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) });161 const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) });
145 const activeThreadId = routeThreadId;162 const activeThreadId = routeThreadId;
146 const activeThread = state.threads.find((thread) => thread.id === activeThreadId);163 const activeThread = state.threads.find((thread) => thread.id === activeThreadId);
164 const activeProject = state.projects.find((project) => project.id === activeThread?.projectId);
165 const activeCwd = activeThread?.worktreePath ?? activeProject?.cwd;
147 const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } =166 const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } =
148 useTurnDiffSummaries(activeThread);167 useTurnDiffSummaries(activeThread);
149 const orderedTurnDiffSummaries = useMemo(168 const orderedTurnDiffSummaries = useMemo(
@@ -264,6 +283,18 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { · 114 unmodified lines
264 target?.scrollIntoView({ block: "nearest" });283 target?.scrollIntoView({ block: "nearest" });
265 }, [selectedFilePath, renderableFiles]);284 }, [selectedFilePath, renderableFiles]);
266 285
286 const openDiffFileInEditor = useCallback(
287 (filePath: string) => {
288 const api = readNativeApi();
289 if (!api) return;
290 const targetPath = activeCwd ? resolvePathLinkTarget(filePath, activeCwd) : filePath;
291 void api.shell.openInEditor(targetPath, preferredTerminalEditor()).catch((error) => {
292 console.warn("Failed to open diff file in editor.", error);
293 });
294 },
295 [activeCwd],
296 );
297
267 const selectTurn = (turnId: TurnId) => {298 const selectTurn = (turnId: TurnId) => {
268 if (!activeThread) return;299 if (!activeThread) return;
269 void navigate({300 void navigate({
@@ -508,7 +539,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { · 238 unmodified lines
508 </div>539 </div>
509 ) : renderablePatch.kind === "files" ? (540 ) : renderablePatch.kind === "files" ? (
510 <Virtualizer541 <Virtualizer
511 className="diff-render-surface h-full min-h-0 overflow-auto px-3 py-2"542 className="diff-render-surface h-full min-h-0 overflow-auto p-2 "
512 config={{543 config={{
513 overscrollSize: 600,544 overscrollSize: 600,
514 intersectionObserverMargin: 1200,545 intersectionObserverMargin: 1200,
@@ -522,7 +553,17 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { · 7 unmodified lines
522 <div553 <div
523 key={themedFileKey}554 key={themedFileKey}
524 data-diff-file-path={filePath}555 data-diff-file-path={filePath}
525 className="diff-render-file rounded-md"556 className="diff-render-file mb-2 rounded-md last:mb-0"
557 onClickCapture={(event) => {
558 const nativeEvent = event.nativeEvent as MouseEvent;
559 const composedPath = nativeEvent.composedPath?.() ?? [];
560 const clickedHeader = composedPath.some((node) => {
561 if (!(node instanceof Element)) return false;
562 return node.hasAttribute("data-title");
563 });
564 if (!clickedHeader) return;
565 openDiffFileInEditor(filePath);
566 }}
526 >567 >
527 <FileDiff568 <FileDiff
528 fileDiff={fileDiff}569 fileDiff={fileDiff}
@@ -539,7 +580,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { · 10 unmodified lines
539 })}580 })}
540 </Virtualizer>581 </Virtualizer>
541 ) : (582 ) : (
542 <div className="h-full overflow-auto px-3 py-2">583 <div className="h-full overflow-auto p-2">
543 <div className="space-y-2">584 <div className="space-y-2">
544 <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p>585 <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p>
545 <pre className="max-h-[72vh] overflow-auto rounded-md border border-border/70 bg-background/70 p-3 font-mono text-[11px] leading-relaxed text-muted-foreground/90">586 <pre className="max-h-[72vh] overflow-auto rounded-md border border-border/70 bg-background/70 p-3 font-mono text-[11px] leading-relaxed text-muted-foreground/90">

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t