Skip to content

Commit 3212849

Browse files
Add cmd+click to open preview panel
1 parent 184fdd1 commit 3212849

File tree

6 files changed

+70
-42
lines changed

6 files changed

+70
-42
lines changed

packages/web/src/app/[domain]/search/components/codePreviewPanel/codePreview.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Cross1Icon, FileIcon } from "@radix-ui/react-icons";
1515
import { Scrollbar } from "@radix-ui/react-scroll-area";
1616
import CodeMirror, { ReactCodeMirrorRef, SelectionRange } from '@uiw/react-codemirror';
1717
import { ArrowDown, ArrowUp } from "lucide-react";
18-
import { useCallback, useEffect, useMemo, useState } from "react";
18+
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
1919
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
2020
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
2121
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
@@ -34,18 +34,21 @@ export interface CodePreviewFile {
3434
interface CodePreviewProps {
3535
file: CodePreviewFile;
3636
repoName: string;
37+
selectedMatchIndex: number;
38+
onSelectedMatchIndexChange: Dispatch<SetStateAction<number>>;
3739
onClose: () => void;
3840
}
3941

4042
export const CodePreview = ({
4143
file,
4244
repoName,
45+
selectedMatchIndex,
46+
onSelectedMatchIndexChange,
4347
onClose,
4448
}: CodePreviewProps) => {
4549
const [editorRef, setEditorRef] = useState<ReactCodeMirrorRef | null>(null);
4650
const { navigateToPath } = useBrowseNavigation();
4751
const hasCodeNavEntitlement = useHasEntitlement("code-nav");
48-
const [selectedMatchIndex, setSelectedMatchIndex] = useState(0);
4952

5053
const [gutterWidth, setGutterWidth] = useState(0);
5154
const theme = useCodeMirrorTheme();
@@ -101,12 +104,12 @@ export const CodePreview = ({
101104
}, [ranges, selectedMatchIndex, file, editorRef]);
102105

103106
const onUpClicked = useCallback(() => {
104-
setSelectedMatchIndex((prev) => prev - 1);
105-
}, []);
107+
onSelectedMatchIndexChange((prev) => prev - 1);
108+
}, [onSelectedMatchIndexChange]);
106109

107110
const onDownClicked = useCallback(() => {
108-
setSelectedMatchIndex((prev) => prev + 1);
109-
}, []);
111+
onSelectedMatchIndexChange((prev) => prev + 1);
112+
}, [onSelectedMatchIndexChange]);
110113

111114
const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => {
112115
if (symbolDefinitions.length === 0) {

packages/web/src/app/[domain]/search/components/codePreviewPanel/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,23 @@ import { CodePreview } from "./codePreview";
55
import { SearchResultFile } from "@/features/search/types";
66
import { useDomain } from "@/hooks/useDomain";
77
import { SymbolIcon } from "@radix-ui/react-icons";
8-
import { useMemo } from "react";
8+
import { SetStateAction, Dispatch, useMemo } from "react";
99
import { getFileSource } from "@/features/search/fileSourceApi";
1010
import { base64Decode } from "@/lib/utils";
1111
import { unwrapServiceError } from "@/lib/utils";
1212

1313
interface CodePreviewPanelProps {
1414
previewedFile: SearchResultFile;
15+
selectedMatchIndex: number;
1516
onClose: () => void;
17+
onSelectedMatchIndexChange: Dispatch<SetStateAction<number>>;
1618
}
1719

1820
export const CodePreviewPanel = ({
1921
previewedFile,
22+
selectedMatchIndex,
2023
onClose,
24+
onSelectedMatchIndexChange,
2125
}: CodePreviewPanelProps) => {
2226
const domain = useDomain();
2327

@@ -67,6 +71,8 @@ export const CodePreviewPanel = ({
6771
<CodePreview
6872
file={file}
6973
repoName={previewedFile.repository}
74+
selectedMatchIndex={selectedMatchIndex}
75+
onSelectedMatchIndexChange={onSelectedMatchIndexChange}
7076
onClose={onClose}
7177
/>
7278
)

packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatch.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,31 @@
33
import { useCallback, useMemo } from "react";
44
import { SearchResultFile, SearchResultChunk } from "@/features/search/types";
55
import { base64Decode } from "@/lib/utils";
6-
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
76
import { LightweightCodeHighlighter } from "@/app/[domain]/components/lightweightCodeHighlighter";
87

98

109
interface FileMatchProps {
1110
match: SearchResultChunk;
1211
file: SearchResultFile;
12+
onOpen: (startLineNumber: number, endLineNumber: number, isCtrlKeyPressed: boolean) => void;
1313
}
1414

1515
export const FileMatch = ({
1616
match,
1717
file,
18+
onOpen: _onOpen,
1819
}: FileMatchProps) => {
19-
const { navigateToPath } = useBrowseNavigation();
2020

2121
const content = useMemo(() => {
2222
return base64Decode(match.content);
2323
}, [match.content]);
2424

25-
const onOpen = useCallback(() => {
25+
const onOpen = useCallback((isCtrlKeyPressed: boolean) => {
2626
const startLineNumber = match.contentStart.lineNumber;
2727
const endLineNumber = content.trimEnd().split('\n').length + startLineNumber - 1;
2828

29-
navigateToPath({
30-
repoName: file.repository,
31-
revisionName: file.branches?.[0] ?? 'HEAD',
32-
path: file.fileName.text,
33-
pathType: 'blob',
34-
highlightRange: {
35-
start: {
36-
lineNumber: startLineNumber,
37-
},
38-
end: {
39-
lineNumber: endLineNumber,
40-
}
41-
}
42-
})
43-
}, []);
29+
_onOpen(startLineNumber, endLineNumber, isCtrlKeyPressed);
30+
}, [content, match.contentStart.lineNumber, _onOpen]);
4431

4532
// If it's just the title, don't show a code preview
4633
if (match.matchRanges.length === 0) {
@@ -55,9 +42,13 @@ export const FileMatch = ({
5542
if (e.key !== "Enter") {
5643
return;
5744
}
58-
onOpen();
45+
46+
onOpen(e.metaKey || e.ctrlKey);
47+
}}
48+
onClick={(e) => {
49+
onOpen(e.metaKey || e.ctrlKey);
5950
}}
60-
onClick={onOpen}
51+
title="Open file preview (cmd/ctrl + click)"
6152
>
6253
<LightweightCodeHighlighter
6354
language={file.language}

packages/web/src/app/[domain]/search/components/searchResultsPanel/fileMatchContainer.tsx

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import { useMemo } from "react";
77
import { FileMatch } from "./fileMatch";
88
import { RepositoryInfo, SearchResultFile } from "@/features/search/types";
99
import { Button } from "@/components/ui/button";
10+
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
1011

1112
export const MAX_MATCHES_TO_PREVIEW = 3;
1213

1314
interface FileMatchContainerProps {
1415
file: SearchResultFile;
15-
onOpenFilePreview: () => void;
16+
onOpenFilePreview: (matchIndex?: number) => void;
1617
showAllMatches: boolean;
1718
onShowAllMatchesButtonClicked: () => void;
1819
isBranchFilteringEnabled: boolean;
@@ -32,6 +33,7 @@ export const FileMatchContainer = ({
3233
const matchCount = useMemo(() => {
3334
return file.chunks.length;
3435
}, [file]);
36+
const { navigateToPath } = useBrowseNavigation();
3537

3638
const matches = useMemo(() => {
3739
const sortedMatches = file.chunks.sort((a, b) => {
@@ -81,7 +83,6 @@ export const FileMatchContainer = ({
8183
return repoInfo[file.repositoryId];
8284
}, [repoInfo, file.repositoryId]);
8385

84-
8586
return (
8687
<div>
8788
{/* Title */}
@@ -103,15 +104,15 @@ export const FileMatchContainer = ({
103104
branchDisplayName={branchDisplayName}
104105
branchDisplayTitle={branches.join(", ")}
105106
/>
106-
<Button
107-
variant="link"
108-
className="text-blue-500 h-5"
109-
onClick={() => {
110-
onOpenFilePreview();
111-
}}
112-
>
113-
Preview
114-
</Button>
107+
<Button
108+
variant="link"
109+
className="text-blue-500 h-5"
110+
onClick={() => {
111+
onOpenFilePreview();
112+
}}
113+
>
114+
Preview
115+
</Button>
115116
</div>
116117

117118
{/* Matches */}
@@ -122,6 +123,29 @@ export const FileMatchContainer = ({
122123
<FileMatch
123124
match={match}
124125
file={file}
126+
onOpen={(startLineNumber, endLineNumber, isCtrlKeyPressed) => {
127+
if (isCtrlKeyPressed) {
128+
const matchIndex = matches.slice(0, index).reduce((acc, match) => {
129+
return acc + match.matchRanges.length;
130+
}, 0);
131+
onOpenFilePreview(matchIndex);
132+
} else {
133+
navigateToPath({
134+
repoName: file.repository,
135+
revisionName: file.branches?.[0] ?? 'HEAD',
136+
path: file.fileName.text,
137+
pathType: 'blob',
138+
highlightRange: {
139+
start: {
140+
lineNumber: startLineNumber,
141+
},
142+
end: {
143+
lineNumber: endLineNumber,
144+
}
145+
}
146+
});
147+
}
148+
}}
125149
/>
126150
{(index !== matches.length - 1 || isMoreContentButtonVisible) && (
127151
<Separator className="bg-accent" />

packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useDebounce, usePrevious } from "@uidotdev/usehooks";
88

99
interface SearchResultsPanelProps {
1010
fileMatches: SearchResultFile[];
11-
onOpenFilePreview: (fileMatch: SearchResultFile) => void;
11+
onOpenFilePreview: (fileMatch: SearchResultFile, matchIndex?: number) => void;
1212
isLoadMoreButtonVisible: boolean;
1313
onLoadMoreButtonClicked: () => void;
1414
isBranchFilteringEnabled: boolean;
@@ -150,8 +150,8 @@ export const SearchResultsPanel = ({
150150
>
151151
<FileMatchContainer
152152
file={file}
153-
onOpenFilePreview={() => {
154-
onOpenFilePreview(file);
153+
onOpenFilePreview={(matchIndex) => {
154+
onOpenFilePreview(file, matchIndex);
155155
}}
156156
showAllMatches={showAllMatchesStates[virtualRow.index]}
157157
onShowAllMatchesButtonClicked={() => {

packages/web/src/app/[domain]/search/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ const PanelGroup = ({
222222
const [previewedFile, setPreviewedFile] = useState<SearchResultFile | undefined>(undefined);
223223
const filteredFileMatches = useFilteredMatches(fileMatches);
224224
const filterPanelRef = useRef<ImperativePanelHandle>(null);
225+
const [selectedMatchIndex, setSelectedMatchIndex] = useState(0);
225226

226227
const [isFilterPanelCollapsed, setIsFilterPanelCollapsed] = useLocalStorage('isFilterPanelCollapsed', false);
227228

@@ -313,7 +314,8 @@ const PanelGroup = ({
313314
{filteredFileMatches.length > 0 ? (
314315
<SearchResultsPanel
315316
fileMatches={filteredFileMatches}
316-
onOpenFilePreview={(fileMatch) => {
317+
onOpenFilePreview={(fileMatch, matchIndex) => {
318+
setSelectedMatchIndex(matchIndex ?? 0);
317319
setPreviewedFile(fileMatch);
318320
}}
319321
isLoadMoreButtonVisible={!!isMoreResultsButtonVisible}
@@ -342,6 +344,8 @@ const PanelGroup = ({
342344
<CodePreviewPanel
343345
previewedFile={previewedFile}
344346
onClose={() => setPreviewedFile(undefined)}
347+
selectedMatchIndex={selectedMatchIndex}
348+
onSelectedMatchIndexChange={setSelectedMatchIndex}
345349
/>
346350
</ResizablePanel>
347351
</>

0 commit comments

Comments
 (0)