5656#include < algorithm>
5757#include < cctype>
5858#include < filesystem>
59+ #include < functional>
60+ #include < climits>
61+ #include < functional>
62+ #include < climits>
5963
6064namespace vix
6165{
@@ -92,6 +96,57 @@ namespace vix
9296 return std::nullopt ;
9397 }
9498
99+ static int levenshtein_distance (const std::string &a, const std::string &b)
100+ {
101+ const size_t m = a.size ();
102+ const size_t n = b.size ();
103+
104+ std::vector<int > prev (n + 1 ), curr (n + 1 );
105+
106+ for (size_t j = 0 ; j <= n; ++j)
107+ prev[j] = j;
108+
109+ for (size_t i = 1 ; i <= m; ++i)
110+ {
111+ curr[0 ] = i;
112+ for (size_t j = 1 ; j <= n; ++j)
113+ {
114+ int cost = (a[i - 1 ] == b[j - 1 ]) ? 0 : 1 ;
115+
116+ curr[j] = std::min ({prev[j] + 1 ,
117+ curr[j - 1 ] + 1 ,
118+ prev[j - 1 ] + cost});
119+ }
120+ prev = curr;
121+ }
122+
123+ return prev[n];
124+ }
125+
126+ static std::optional<std::string> find_closest_command (
127+ const std::string &input,
128+ const std::unordered_map<std::string, vix::cli::dispatch::Entry> &entries)
129+ {
130+ int bestScore = INT_MAX;
131+ std::string best;
132+
133+ for (const auto &[name, _] : entries)
134+ {
135+ int d = levenshtein_distance (input, name);
136+
137+ if (d < bestScore)
138+ {
139+ bestScore = d;
140+ best = name;
141+ }
142+ }
143+
144+ if (bestScore <= 3 )
145+ return best;
146+
147+ return std::nullopt ;
148+ }
149+
95150 void apply_log_level_from_env (Logger &logger)
96151 {
97152 if (const char *env = vix::utils::vix_getenv (" VIX_LOG_LEVEL" ))
@@ -290,8 +345,17 @@ namespace vix
290345 {
291346 if (!dispatcher.has (cmd))
292347 {
293- std::cerr << " vix: unknown command '" << cmd << " '\n\n " ;
294- return help ({});
348+ std::cerr << " error: unrecognized subcommand\n\n " ;
349+
350+ auto suggestion = find_closest_command (cmd, dispatcher.entries ());
351+
352+ if (suggestion.has_value ())
353+ {
354+ std::cerr << " tip: a similar command exists: '"
355+ << suggestion.value () << " '\n " ;
356+ }
357+
358+ return 1 ;
295359 }
296360 return dispatcher.help (cmd);
297361 }
@@ -307,8 +371,16 @@ namespace vix
307371
308372 if (!dispatcher.has (cmd))
309373 {
310- std::cerr << " vix: unknown command '" << cmd << " '\n\n " ;
311- help ({});
374+ std::cerr << " error: unrecognized subcommand\n\n " ;
375+
376+ auto suggestion = find_closest_command (cmd, dispatcher.entries ());
377+
378+ if (suggestion.has_value ())
379+ {
380+ std::cerr << " tip: a similar command exists: '"
381+ << suggestion.value () << " '\n " ;
382+ }
383+
312384 return 1 ;
313385 }
314386
0 commit comments