@@ -12,7 +12,7 @@ import { SymbolIcon } from "@radix-ui/react-icons";
1212import { useQuery } from "@tanstack/react-query" ;
1313import Image from "next/image" ;
1414import { useRouter } from "next/navigation" ;
15- import { useEffect , useMemo , useState } from "react" ;
15+ import { useCallback , useEffect , useMemo , useState } from "react" ;
1616import logoDark from "../../../public/sb_logo_dark.png" ;
1717import logoLight from "../../../public/sb_logo_light.png" ;
1818import { search } from "../api/(client)/client" ;
@@ -22,25 +22,33 @@ import useCaptureEvent from "@/hooks/useCaptureEvent";
2222import { CodePreviewPanel } from "./components/codePreviewPanel" ;
2323import { SearchResultsPanel } from "./components/searchResultsPanel" ;
2424import { SearchResultFile } from "@/lib/types" ;
25+ import { ScrollArea } from "@/components/ui/scroll-area" ;
26+ import { Scrollbar } from "@radix-ui/react-scroll-area" ;
27+
28+ const DEFAULT_MAX_MATCH_DISPLAY_COUNT = 200 ;
29+
30+ export enum SearchQueryParams {
31+ query = "query" ,
32+ maxMatchDisplayCount = "maxMatchDisplayCount" ,
33+ }
2534
26- const DEFAULT_NUM_RESULTS = 100 ;
2735
2836export default function SearchPage ( ) {
2937 const router = useRouter ( ) ;
30- const searchQuery = useNonEmptyQueryParam ( " query" ) ?? "" ;
31- const _numResults = parseInt ( useNonEmptyQueryParam ( "numResults" ) ?? `${ DEFAULT_NUM_RESULTS } ` ) ;
32- const numResults = isNaN ( _numResults ) ? DEFAULT_NUM_RESULTS : _numResults ;
38+ const searchQuery = useNonEmptyQueryParam ( SearchQueryParams . query ) ?? "" ;
39+ const _maxMatchDisplayCount = parseInt ( useNonEmptyQueryParam ( SearchQueryParams . maxMatchDisplayCount ) ?? `${ DEFAULT_MAX_MATCH_DISPLAY_COUNT } ` ) ;
40+ const maxMatchDisplayCount = isNaN ( _maxMatchDisplayCount ) ? DEFAULT_MAX_MATCH_DISPLAY_COUNT : _maxMatchDisplayCount ;
3341
3442 const [ selectedMatchIndex , setSelectedMatchIndex ] = useState ( 0 ) ;
3543 const [ selectedFile , setSelectedFile ] = useState < SearchResultFile | undefined > ( undefined ) ;
3644
3745 const captureEvent = useCaptureEvent ( ) ;
3846
3947 const { data : searchResponse , isLoading } = useQuery ( {
40- queryKey : [ "search" , searchQuery , numResults ] ,
48+ queryKey : [ "search" , searchQuery , maxMatchDisplayCount ] ,
4149 queryFn : ( ) => search ( {
4250 query : searchQuery ,
43- numResults ,
51+ maxMatchDisplayCount ,
4452 } ) ,
4553 enabled : searchQuery . length > 0 ,
4654 refetchOnWindowFocus : false ,
@@ -93,8 +101,28 @@ export default function SearchPage() {
93101 } , [ searchResponse ] ) ;
94102
95103 const isMoreResultsButtonVisible = useMemo ( ( ) => {
96- return searchResponse && searchResponse . Result . MatchCount > numResults ;
97- } , [ searchResponse , numResults ] ) ;
104+ return searchResponse && searchResponse . Result . MatchCount > maxMatchDisplayCount ;
105+ } , [ searchResponse , maxMatchDisplayCount ] ) ;
106+
107+ const numMatches = useMemo ( ( ) => {
108+ // Accumualtes the number of matches across all files
109+ return searchResponse ?. Result . Files ?. reduce (
110+ ( acc , file ) =>
111+ acc + file . ChunkMatches . reduce (
112+ ( acc , chunk ) => acc + chunk . Ranges . length ,
113+ 0 ,
114+ ) ,
115+ 0 ,
116+ ) ?? 0 ;
117+ } , [ searchResponse ] ) ;
118+
119+ const onLoadMoreResults = useCallback ( ( ) => {
120+ const url = createPathWithQueryParams ( '/search' ,
121+ [ SearchQueryParams . query , searchQuery ] ,
122+ [ SearchQueryParams . maxMatchDisplayCount , `${ maxMatchDisplayCount * 2 } ` ] ,
123+ )
124+ router . push ( url ) ;
125+ } , [ maxMatchDisplayCount , router , searchQuery ] ) ;
98126
99127 return (
100128 < div className = "flex flex-col h-screen overflow-clip" >
@@ -129,20 +157,22 @@ export default function SearchPage() {
129157 />
130158 </ div >
131159 < Separator />
132- < div className = "bg-accent py-1 px-2 flex flex-row items-center justify-between" >
133- < p className = "text-sm font-medium" > Results for: { fileMatches . length } files in { searchDurationMs } ms</ p >
134- { isMoreResultsButtonVisible && (
160+ < div className = "bg-accent py-1 px-2 flex flex-row items-center gap-4" >
161+ {
162+ isLoading ? (
163+ < p className = "text-sm font-medium" > Loading...</ p >
164+ ) : fileMatches . length > 0 ? (
165+ < p className = "text-sm font-medium" > { `[${ searchDurationMs } ms] Displaying ${ numMatches } matches in ${ fileMatches . length } ${ fileMatches . length > 1 ? 'files' : 'file' } ` } </ p >
166+ ) : (
167+ < p className = "text-sm font-medium" > No results</ p >
168+ )
169+ }
170+ { isMoreResultsButtonVisible && ! isLoading && (
135171 < div
136172 className = "cursor-pointer text-blue-500 text-sm hover:underline"
137- onClick = { ( ) => {
138- const url = createPathWithQueryParams ( '/search' ,
139- [ "query" , searchQuery ] ,
140- [ "numResults" , `${ numResults * 2 } ` ] ,
141- )
142- router . push ( url ) ;
143- } }
173+ onClick = { onLoadMoreResults }
144174 >
145- More results
175+ (load more)
146176 </ div >
147177 ) }
148178 </ div >
@@ -157,16 +187,35 @@ export default function SearchPage() {
157187 < SymbolIcon className = "h-6 w-6 animate-spin" />
158188 < p className = "font-semibold text-center" > Searching...</ p >
159189 </ div >
190+ ) : fileMatches . length > 0 ? (
191+ < ScrollArea
192+ className = "h-full"
193+ >
194+ < SearchResultsPanel
195+ fileMatches = { fileMatches }
196+ onOpenFileMatch = { ( fileMatch ) => {
197+ setSelectedFile ( fileMatch ) ;
198+ } }
199+ onMatchIndexChanged = { ( matchIndex ) => {
200+ setSelectedMatchIndex ( matchIndex ) ;
201+ } }
202+ />
203+ { isMoreResultsButtonVisible && (
204+ < div className = "p-3" >
205+ < span
206+ className = "cursor-pointer text-blue-500 hover:underline"
207+ onClick = { onLoadMoreResults }
208+ >
209+ Load more results
210+ </ span >
211+ </ div >
212+ ) }
213+ < Scrollbar orientation = "vertical" />
214+ </ ScrollArea >
160215 ) : (
161- < SearchResultsPanel
162- fileMatches = { fileMatches }
163- onOpenFileMatch = { ( fileMatch ) => {
164- setSelectedFile ( fileMatch ) ;
165- } }
166- onMatchIndexChanged = { ( matchIndex ) => {
167- setSelectedMatchIndex ( matchIndex ) ;
168- } }
169- />
216+ < div className = "flex flex-col items-center justify-center h-full" >
217+ < p className = "text-sm text-muted-foreground" > No results found</ p >
218+ </ div >
170219 ) }
171220 </ ResizablePanel >
172221 < ResizableHandle withHandle = { selectedFile !== undefined } />
0 commit comments