Skip to content

Commit e99516e

Browse files
ThomasK33Test
andauthored
🤖 feat: add l/h keybinds to mark hunks as read/unread (#509)
Add vim-style keybindings for marking hunks in the code review panel: - `l`: mark selected hunk as read (reviewed) - `h`: mark selected hunk as unread (unreviewed) ## Implementation The keybindings follow vim conventions where `l` (right) moves forward/marks as done, and `h` (left) moves backward/unmarks. **Auto-navigation logic**: When `showReadHunks` is disabled and a user presses `l` to mark a hunk as read, the selection automatically advances to the next unread hunk. This maintains keyboard navigation flow and prevents the selection from becoming invalid. The logic reuses the same pattern as `handleToggleRead` for consistency. ## Testing Manual testing: - [x] Press `l` on unread hunk → marks as read, advances to next unread hunk - [x] Press `h` on read hunk → marks as unread - [x] Works correctly when `showReadHunks` is enabled (no auto-navigation) - [x] Works correctly when `showReadHunks` is disabled (auto-navigation) - [x] Typechecks pass --- _Generated with `cmux`_ --------- Signed-off-by: Test <test@example.com> Co-authored-by: Test <test@example.com>
1 parent 4a5ff11 commit e99516e

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

src/components/RightSidebar/CodeReview/ReviewPanel.tsx

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
167167
);
168168

169169
// Initialize review state hook
170-
const { isRead, toggleRead } = useReviewState(workspaceId);
170+
const { isRead, toggleRead, markAsRead, markAsUnread } = useReviewState(workspaceId);
171171

172172
const [filters, setFilters] = useState<ReviewFiltersType>({
173173
showReadHunks: showReadHunks,
@@ -419,6 +419,39 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
419419
[isRead, toggleRead, filters.showReadHunks, hunks, selectedHunkId]
420420
);
421421

422+
// Handle marking hunk as read with auto-navigation
423+
const handleMarkAsRead = useCallback(
424+
(hunkId: string) => {
425+
const wasRead = isRead(hunkId);
426+
markAsRead(hunkId);
427+
428+
// If marking the selected hunk as read and it will be filtered out, navigate
429+
if (hunkId === selectedHunkId && !wasRead && !filters.showReadHunks) {
430+
// Hunk will be filtered out - move to next visible hunk
431+
const currentFiltered = hunks.filter((h) => !isRead(h.id));
432+
const currentIndex = currentFiltered.findIndex((h) => h.id === hunkId);
433+
if (currentIndex !== -1) {
434+
if (currentIndex < currentFiltered.length - 1) {
435+
setSelectedHunkId(currentFiltered[currentIndex + 1].id);
436+
} else if (currentIndex > 0) {
437+
setSelectedHunkId(currentFiltered[currentIndex - 1].id);
438+
} else {
439+
setSelectedHunkId(null);
440+
}
441+
}
442+
}
443+
},
444+
[isRead, markAsRead, filters.showReadHunks, hunks, selectedHunkId]
445+
);
446+
447+
// Handle marking hunk as unread (no navigation needed - unread hunks are always visible)
448+
const handleMarkAsUnread = useCallback(
449+
(hunkId: string) => {
450+
markAsUnread(hunkId);
451+
},
452+
[markAsUnread]
453+
);
454+
422455
// Stable callbacks for HunkViewer (single callback shared across all hunks)
423456
const handleHunkClick = useCallback((e: React.MouseEvent<HTMLElement>) => {
424457
const hunkId = e.currentTarget.dataset.hunkId;
@@ -496,6 +529,14 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
496529
// Toggle read state of selected hunk
497530
e.preventDefault();
498531
handleToggleRead(selectedHunkId);
532+
} else if (matchesKeybind(e, KEYBINDS.MARK_HUNK_READ)) {
533+
// Mark selected hunk as read
534+
e.preventDefault();
535+
handleMarkAsRead(selectedHunkId);
536+
} else if (matchesKeybind(e, KEYBINDS.MARK_HUNK_UNREAD)) {
537+
// Mark selected hunk as unread
538+
e.preventDefault();
539+
handleMarkAsUnread(selectedHunkId);
499540
} else if (matchesKeybind(e, KEYBINDS.TOGGLE_HUNK_COLLAPSE)) {
500541
// Toggle expand/collapse state of selected hunk
501542
e.preventDefault();
@@ -508,7 +549,14 @@ export const ReviewPanel: React.FC<ReviewPanelProps> = ({
508549

509550
window.addEventListener("keydown", handleKeyDown);
510551
return () => window.removeEventListener("keydown", handleKeyDown);
511-
}, [isPanelFocused, selectedHunkId, filteredHunks, handleToggleRead]);
552+
}, [
553+
isPanelFocused,
554+
selectedHunkId,
555+
filteredHunks,
556+
handleToggleRead,
557+
handleMarkAsRead,
558+
handleMarkAsUnread,
559+
]);
512560

513561
// Global keyboard shortcuts (Ctrl+R / Cmd+R for refresh, Ctrl+F / Cmd+F for search)
514562
useEffect(() => {

src/utils/ui/keybinds.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,12 @@ export const KEYBINDS = {
270270
/** Mark selected hunk as read/unread in Code Review panel */
271271
TOGGLE_HUNK_READ: { key: "m" },
272272

273+
/** Mark selected hunk as read in Code Review panel */
274+
MARK_HUNK_READ: { key: "l" },
275+
276+
/** Mark selected hunk as unread in Code Review panel */
277+
MARK_HUNK_UNREAD: { key: "h" },
278+
273279
/** Toggle hunk expand/collapse in Code Review panel */
274280
TOGGLE_HUNK_COLLAPSE: { key: " " },
275281
} as const;

0 commit comments

Comments
 (0)