66"""
77
88from pathlib import Path
9- from typing import Any , Dict , List , Optional
9+ from typing import Any , Dict , List , Optional , Tuple
1010
1111from .base_service import BaseService
1212from ..utils import FileFilter , ResponseFormatter , ValidationHelper
@@ -20,14 +20,16 @@ def __init__(self, ctx):
2020 super ().__init__ (ctx )
2121 self .file_filter = self ._create_file_filter ()
2222
23- def search_code ( # pylint: disable=too-many-arguments
23+ def search_code ( # pylint: disable=too-many-arguments, too-many-locals
2424 self ,
2525 pattern : str ,
2626 case_sensitive : bool = True ,
2727 context_lines : int = 0 ,
2828 file_pattern : Optional [str ] = None ,
2929 fuzzy : bool = False ,
30- regex : Optional [bool ] = None
30+ regex : Optional [bool ] = None ,
31+ start_index : int = 0 ,
32+ max_results : Optional [int ] = 10
3133 ) -> Dict [str , Any ]:
3234 """Search for code patterns in the project."""
3335 self ._require_project_setup ()
@@ -44,6 +46,10 @@ def search_code( # pylint: disable=too-many-arguments
4446 if error :
4547 raise ValueError (f"Invalid file pattern: { error } " )
4648
49+ pagination_error = ValidationHelper .validate_pagination (start_index , max_results )
50+ if pagination_error :
51+ raise ValueError (pagination_error )
52+
4753 if not self .settings :
4854 raise ValueError ("Settings not available" )
4955
@@ -64,7 +70,15 @@ def search_code( # pylint: disable=too-many-arguments
6470 regex = regex
6571 )
6672 filtered = self ._filter_results (results )
67- return ResponseFormatter .search_results_response (filtered )
73+ formatted_results , pagination = self ._paginate_results (
74+ filtered ,
75+ start_index = start_index ,
76+ max_results = max_results
77+ )
78+ return ResponseFormatter .search_results_response (
79+ formatted_results ,
80+ pagination
81+ )
6882 except Exception as exc :
6983 raise ValueError (f"Search failed using '{ strategy .name } ': { exc } " ) from exc
7084
@@ -168,3 +182,88 @@ def _filter_results(self, results: Dict[str, Any]) -> Dict[str, Any]:
168182 continue
169183
170184 return filtered
185+
186+ def _paginate_results (
187+ self ,
188+ results : Dict [str , Any ],
189+ start_index : int ,
190+ max_results : Optional [int ]
191+ ) -> Tuple [List [Dict [str , Any ]], Dict [str , Any ]]:
192+ """Apply pagination to search results and format them for responses."""
193+ total_matches = 0
194+ for matches in results .values ():
195+ if isinstance (matches , (list , tuple )):
196+ total_matches += len (matches )
197+
198+ effective_start = min (max (start_index , 0 ), total_matches )
199+
200+ if total_matches == 0 or effective_start >= total_matches :
201+ pagination = self ._build_pagination_metadata (
202+ total_matches = total_matches ,
203+ returned = 0 ,
204+ start_index = effective_start ,
205+ max_results = max_results
206+ )
207+ return [], pagination
208+
209+ collected : List [Dict [str , Any ]] = []
210+ current_index = 0
211+
212+ sorted_items = sorted (
213+ (
214+ (path , matches )
215+ for path , matches in results .items ()
216+ if isinstance (path , str ) and isinstance (matches , (list , tuple ))
217+ ),
218+ key = lambda item : item [0 ]
219+ )
220+
221+ for path , matches in sorted_items :
222+ sorted_matches = sorted (
223+ (match for match in matches if isinstance (match , (list , tuple )) and len (match ) >= 2 ),
224+ key = lambda pair : pair [0 ]
225+ )
226+
227+ for line_number , content , * _ in sorted_matches :
228+ if current_index >= effective_start :
229+ if max_results is None or len (collected ) < max_results :
230+ collected .append ({
231+ "file" : path ,
232+ "line" : line_number ,
233+ "text" : content
234+ })
235+ else :
236+ break
237+ current_index += 1
238+ if max_results is not None and len (collected ) >= max_results :
239+ break
240+
241+ pagination = self ._build_pagination_metadata (
242+ total_matches = total_matches ,
243+ returned = len (collected ),
244+ start_index = effective_start ,
245+ max_results = max_results
246+ )
247+ return collected , pagination
248+
249+ @staticmethod
250+ def _build_pagination_metadata (
251+ total_matches : int ,
252+ returned : int ,
253+ start_index : int ,
254+ max_results : Optional [int ]
255+ ) -> Dict [str , Any ]:
256+ """Construct pagination metadata for search responses."""
257+ end_index = start_index + returned
258+ metadata : Dict [str , Any ] = {
259+ "total_matches" : total_matches ,
260+ "returned" : returned ,
261+ "start_index" : start_index ,
262+ "has_more" : end_index < total_matches
263+ }
264+
265+ if max_results is not None :
266+ metadata ["max_results" ] = max_results
267+
268+ metadata ["end_index" ] = end_index
269+ return metadata
0 commit comments