Repository navigation
Add double-click to open file in editor - #1021
Conversation
📝 WalkthroughWalkthroughA single file modification introduces single-click versus double-click differentiation to the FileItem component. A 300ms timeout mechanism debounces single-click actions while double-click events immediately trigger open-in-editor functionality, with proper cleanup on component unmount. Changes
Estimated Code review effort🎯 2 (Simple) | ⏱️ ~15 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileItem/FileItem.tsx`:
- Around line 122-165: The single-click handler uses a hardcoded 300ms timeout
so slower double-clicks can still trigger the single-click action; extract the
timeout into a named constant (e.g. DEFAULT_DOUBLE_CLICK_DELAY) and make it
configurable (prop or config) and update handleClick/handleDoubleClick to use
that constant. Also add a lastClickTimeRef (or similar) and compare Date.now()
against it in handleClick/handleDoubleClick to detect double-clicks reliably (if
now - lastClickTimeRef.current < DELAY treat as double, cancel single click) and
ensure clickTimeoutRef is cleared in all paths; update references in
handleClick, handleDoubleClick, and the unmount effect accordingly.
| const handleOpenInEditor = useCallback(() => { | ||
| if (absolutePath && worktreePath) { | ||
| openInEditorMutation.mutate({ path: absolutePath, cwd: worktreePath }); | ||
| } | ||
| }; | ||
| }, [absolutePath, worktreePath, openInEditorMutation]); | ||
|
|
||
| const handleClick = useCallback(() => { | ||
| // Clear any pending single-click timeout | ||
| if (clickTimeoutRef.current) { | ||
| clearTimeout(clickTimeoutRef.current); | ||
| clickTimeoutRef.current = null; | ||
| } | ||
|
|
||
| // Set a timeout for single-click action | ||
| clickTimeoutRef.current = setTimeout(() => { | ||
| clickTimeoutRef.current = null; | ||
| onClick(); | ||
| }, 300); | ||
| }, [onClick]); | ||
|
|
||
| const handleDoubleClick = useCallback( | ||
| (e: React.MouseEvent) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
|
|
||
| if (clickTimeoutRef.current) { | ||
| clearTimeout(clickTimeoutRef.current); | ||
| clickTimeoutRef.current = null; | ||
| } | ||
|
|
||
| // Execute double-click action (open in editor) | ||
| handleOpenInEditor(); | ||
| }, | ||
| [handleOpenInEditor], | ||
| ); | ||
|
|
||
| // Cleanup timeout on unmount | ||
| useEffect(() => { | ||
| return () => { | ||
| if (clickTimeoutRef.current) { | ||
| clearTimeout(clickTimeoutRef.current); | ||
| } | ||
| }; | ||
| }, []); |
There was a problem hiding this comment.
Double-click can still trigger single-click for slower double-clicks.
With a fixed 300 ms delay, users who double-click within the browser’s double‑click interval but slower than 300 ms will get both actions. Consider using a longer/configurable detection window and extract the delay into a named constant.
🛠️ Suggested fix
+const DOUBLE_CLICK_DETECTION_DELAY_MS = 500;
+
const handleClick = useCallback(() => {
// Clear any pending single-click timeout
if (clickTimeoutRef.current) {
clearTimeout(clickTimeoutRef.current);
clickTimeoutRef.current = null;
}
// Set a timeout for single-click action
clickTimeoutRef.current = setTimeout(() => {
clickTimeoutRef.current = null;
onClick();
- }, 300);
+ }, DOUBLE_CLICK_DETECTION_DELAY_MS);
}, [onClick]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleOpenInEditor = useCallback(() => { | |
| if (absolutePath && worktreePath) { | |
| openInEditorMutation.mutate({ path: absolutePath, cwd: worktreePath }); | |
| } | |
| }; | |
| }, [absolutePath, worktreePath, openInEditorMutation]); | |
| const handleClick = useCallback(() => { | |
| // Clear any pending single-click timeout | |
| if (clickTimeoutRef.current) { | |
| clearTimeout(clickTimeoutRef.current); | |
| clickTimeoutRef.current = null; | |
| } | |
| // Set a timeout for single-click action | |
| clickTimeoutRef.current = setTimeout(() => { | |
| clickTimeoutRef.current = null; | |
| onClick(); | |
| }, 300); | |
| }, [onClick]); | |
| const handleDoubleClick = useCallback( | |
| (e: React.MouseEvent) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| if (clickTimeoutRef.current) { | |
| clearTimeout(clickTimeoutRef.current); | |
| clickTimeoutRef.current = null; | |
| } | |
| // Execute double-click action (open in editor) | |
| handleOpenInEditor(); | |
| }, | |
| [handleOpenInEditor], | |
| ); | |
| // Cleanup timeout on unmount | |
| useEffect(() => { | |
| return () => { | |
| if (clickTimeoutRef.current) { | |
| clearTimeout(clickTimeoutRef.current); | |
| } | |
| }; | |
| }, []); | |
| const DOUBLE_CLICK_DETECTION_DELAY_MS = 500; | |
| const handleOpenInEditor = useCallback(() => { | |
| if (absolutePath && worktreePath) { | |
| openInEditorMutation.mutate({ path: absolutePath, cwd: worktreePath }); | |
| } | |
| }, [absolutePath, worktreePath, openInEditorMutation]); | |
| const handleClick = useCallback(() => { | |
| // Clear any pending single-click timeout | |
| if (clickTimeoutRef.current) { | |
| clearTimeout(clickTimeoutRef.current); | |
| clickTimeoutRef.current = null; | |
| } | |
| // Set a timeout for single-click action | |
| clickTimeoutRef.current = setTimeout(() => { | |
| clickTimeoutRef.current = null; | |
| onClick(); | |
| }, DOUBLE_CLICK_DETECTION_DELAY_MS); | |
| }, [onClick]); | |
| const handleDoubleClick = useCallback( | |
| (e: React.MouseEvent) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| if (clickTimeoutRef.current) { | |
| clearTimeout(clickTimeoutRef.current); | |
| clickTimeoutRef.current = null; | |
| } | |
| // Execute double-click action (open in editor) | |
| handleOpenInEditor(); | |
| }, | |
| [handleOpenInEditor], | |
| ); | |
| // Cleanup timeout on unmount | |
| useEffect(() => { | |
| return () => { | |
| if (clickTimeoutRef.current) { | |
| clearTimeout(clickTimeoutRef.current); | |
| } | |
| }; | |
| }, []); |
🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileItem/FileItem.tsx`
around lines 122 - 165, The single-click handler uses a hardcoded 300ms timeout
so slower double-clicks can still trigger the single-click action; extract the
timeout into a named constant (e.g. DEFAULT_DOUBLE_CLICK_DELAY) and make it
configurable (prop or config) and update handleClick/handleDoubleClick to use
that constant. Also add a lastClickTimeRef (or similar) and compare Date.now()
against it in handleClick/handleDoubleClick to detect double-clicks reliably (if
now - lastClickTimeRef.current < DELAY treat as double, cancel single click) and
ensure clickTimeoutRef is cleared in all paths; update references in
handleClick, handleDoubleClick, and the unmount effect accordingly.
Kitenite
left a comment
There was a problem hiding this comment.
Amazing! I was thinking about adding this myself. And thanks for handling the cooldown edgecase
Description
Added double-click functionality to file items in the Changes View sidebar. Users can now double-click a file to open it directly in the editor, while single-click continues to work as before (selecting/highlighting the file).
The implementation uses a timeout-based approach to distinguish between single and double clicks:
Related Issues
#1020
Type of Change
Testing
Screenshots (if applicable)
Screen.Recording.2026-01-27.at.9.41.41.PM.mov
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.