@@ -96,6 +96,7 @@ namespace vix::commands
9696 {
9797 if (!entry.contains (" keywords" ) || !entry[" keywords" ].is_array ())
9898 return {};
99+
99100 std::string out;
100101 for (const auto &k : entry[" keywords" ])
101102 {
@@ -135,6 +136,102 @@ namespace vix::commands
135136 int score{};
136137 };
137138
139+ struct SearchOptions
140+ {
141+ std::string query;
142+ std::size_t page = 1 ;
143+ std::size_t limit = 5 ;
144+ };
145+
146+ bool parse_positive_size (const std::string &s, std::size_t &out)
147+ {
148+ if (s.empty ())
149+ return false ;
150+
151+ std::size_t value = 0 ;
152+ for (char c : s)
153+ {
154+ if (c < ' 0' || c > ' 9' )
155+ return false ;
156+
157+ value = (value * 10u ) + static_cast <std::size_t >(c - ' 0' );
158+ }
159+
160+ if (value == 0 )
161+ return false ;
162+
163+ out = value;
164+ return true ;
165+ }
166+
167+ bool parse_search_args (const std::vector<std::string> &args, SearchOptions &opt)
168+ {
169+ bool querySet = false ;
170+
171+ for (std::size_t i = 0 ; i < args.size (); ++i)
172+ {
173+ const std::string &arg = args[i];
174+
175+ if (arg == " --page" )
176+ {
177+ if (i + 1 >= args.size ())
178+ return false ;
179+
180+ std::size_t page = 0 ;
181+ if (!parse_positive_size (args[++i], page))
182+ return false ;
183+
184+ opt.page = page;
185+ continue ;
186+ }
187+
188+ if (arg == " --limit" )
189+ {
190+ if (i + 1 >= args.size ())
191+ return false ;
192+
193+ std::size_t limit = 0 ;
194+ if (!parse_positive_size (args[++i], limit))
195+ return false ;
196+
197+ opt.limit = std::clamp<std::size_t >(limit, 1 , 100 );
198+ continue ;
199+ }
200+
201+ if (arg.rfind (" --page=" , 0 ) == 0 )
202+ {
203+ std::size_t page = 0 ;
204+ if (!parse_positive_size (arg.substr (7 ), page))
205+ return false ;
206+
207+ opt.page = page;
208+ continue ;
209+ }
210+
211+ if (arg.rfind (" --limit=" , 0 ) == 0 )
212+ {
213+ std::size_t limit = 0 ;
214+ if (!parse_positive_size (arg.substr (8 ), limit))
215+ return false ;
216+
217+ opt.limit = std::clamp<std::size_t >(limit, 1 , 100 );
218+ continue ;
219+ }
220+
221+ if (!querySet)
222+ {
223+ opt.query = arg;
224+ querySet = true ;
225+ continue ;
226+ }
227+
228+ opt.query += " " ;
229+ opt.query += arg;
230+ }
231+
232+ return !opt.query .empty ();
233+ }
234+
138235 int score_entry (const json &e, const std::string &qLower)
139236 {
140237 const std::string ns = e.value (" namespace" , " " );
@@ -173,8 +270,18 @@ namespace vix::commands
173270 if (args.empty ())
174271 return help ();
175272
176- const std::string query = args[0 ];
177- vix::cli::util::kv (std::cout, " query" , vix::cli::util::quote (query));
273+ SearchOptions options;
274+ if (!parse_search_args (args, options))
275+ {
276+ error (" invalid search arguments" );
277+ hint (" Usage: vix search <query> [--page N] [--limit N]" );
278+ hint (" Example: vix search json --page 2 --limit 5" );
279+ return 1 ;
280+ }
281+
282+ vix::cli::util::kv (std::cout, " query" , vix::cli::util::quote (options.query ));
283+ vix::cli::util::kv (std::cout, " page" , std::to_string (options.page ));
284+ vix::cli::util::kv (std::cout, " limit" , std::to_string (options.limit ));
178285
179286 const fs::path repoDir = registry_repo_dir ();
180287 const fs::path idxDir = registry_index_dir ();
@@ -187,7 +294,7 @@ namespace vix::commands
187294 }
188295
189296 std::vector<Hit> hits;
190- const std::string qLower = to_lower (query);
297+ const std::string qLower = to_lower (options. query );
191298
192299 for (const auto &it : fs::directory_iterator (idxDir))
193300 {
@@ -231,28 +338,58 @@ namespace vix::commands
231338
232339 if (hits.empty ())
233340 {
234- error (std::string (" no results for " ) + vix::cli::util::quote (query));
341+ error (std::string (" no results for " ) + vix::cli::util::quote (options. query ));
235342 hint (" Tip: search by namespace, name, description, or keywords" );
236343 hint (" Example: vix search gaspardkirira" );
237344 return 0 ;
238345 }
239346
240- vix::cli::util::one_line_spacer (std::cout);
347+ const std::size_t total = hits.size ();
348+ const std::size_t totalPages =
349+ (total + options.limit - 1 ) / options.limit ;
241350
242- const std::size_t limit = 20 ;
243- const std::size_t n = std::min<std::size_t >(hits.size (), limit);
351+ if (options.page > totalPages)
352+ {
353+ error (" page out of range" );
354+ hint (" Total pages: " + std::to_string (totalPages));
355+ return 1 ;
356+ }
357+
358+ const std::size_t start = (options.page - 1 ) * options.limit ;
359+ const std::size_t end = std::min (start + options.limit , total);
360+
361+ vix::cli::util::one_line_spacer (std::cout);
244362
245- for (std::size_t i = 0 ; i < n ; ++i)
363+ for (std::size_t i = start ; i < end ; ++i)
246364 {
247365 const auto &h = hits[i];
248366 vix::cli::util::pkg_line (std::cout, h.id , h.latest , h.desc , h.repo );
249367 std::cout << " \n " ;
250368 }
251369
252- if (hits.size () > limit)
253- vix::cli::util::ok_line (std::cout, " Showing " + std::to_string (limit) + " of " + std::to_string (hits.size ()) + " result(s)." );
254- else
255- vix::cli::util::ok_line (std::cout, " Found " + std::to_string (hits.size ()) + " result(s)." );
370+ vix::cli::util::ok_line (
371+ std::cout,
372+ " Showing " + std::to_string (start + 1 ) +
373+ " -" + std::to_string (end) +
374+ " of " + std::to_string (total) +
375+ " result(s)." );
376+
377+ if (totalPages > 1 )
378+ {
379+ std::cout << " " << GRAY
380+ << " Page " << options.page << " /" << totalPages
381+ << RESET << " \n " ;
382+
383+ if (options.page < totalPages)
384+ {
385+ std::cout << " " << GRAY
386+ << " Next: vix search "
387+ << vix::cli::util::quote (options.query )
388+ << " --page " << (options.page + 1 )
389+ << " --limit " << options.limit
390+ << RESET << " \n " ;
391+ }
392+ }
256393
257394 return 0 ;
258395 }
@@ -261,13 +398,14 @@ namespace vix::commands
261398 {
262399 std::cout
263400 << " Usage:\n "
264- << " vix search <query>\n\n "
401+ << " vix search <query> [--page N] [--limit N] \n\n "
265402 << " Description:\n "
266403 << " Search packages in the local registry index (offline).\n\n "
267404 << " Examples:\n "
268405 << " vix registry sync\n "
269406 << " vix search tree\n "
270- << " vix search gaspardkirira\n " ;
407+ << " vix search json --page 2\n "
408+ << " vix search gaspardkirira --limit 50\n " ;
271409 return 0 ;
272410 }
273411}
0 commit comments