Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command suggestions #6688

Merged
merged 14 commits into from
May 13, 2022
39 changes: 39 additions & 0 deletions src/formula/string_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,42 @@ std::string vngettext_impl(const char* domain,
const std::string msg = utils::interpolate_variables_into_string(orig, &symbols);
return msg;
}

int edit_distance_approx(const std::string &str_1, const std::string &str_2)
{
int edit_distance = 0;
if(str_1.length() == 0) {
return str_2.length();
}
else if(str_2.length() == 0) {
return str_1.length();
}
else {
int j = 0;
int len_max = std::max(str_1.length(), str_2.length());
for(int i = 0; i < len_max; i++) {
if(str_1[i] != str_2[j]) {
//SWAP
if(str_1[i+1] == str_2[j] && str_1[i] == str_2[j+1]) {
// No need to test the next letter
i++;j++;
}
//ADDITION
else if(str_1[i+1] == str_2[j]) {
j--;
}
//DELETION
else if(str_1[i] == str_2[j+1]) {
i--;
}
// CHANGE (no need to do anything, next letter MAY be successful).
edit_distance++;
if(edit_distance * 100 / std::min(str_1.length(), str_2.length()) > 33) {
break;
}
}
j++;
}
}
return edit_distance;
}
16 changes: 16 additions & 0 deletions src/formula/string_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,19 @@ std::string vngettext_impl(const char* domain,

#define VNGETTEXT(msgid, msgid_plural, count, ...) \
vngettext_impl(GETTEXT_DOMAIN, msgid, msgid_plural, count, __VA_ARGS__)

/**
* Approximately calculates the distance between two strings
*
* Inspired in the Levenshtein distance, but made simpler
* to avoid using recursion and wasting resources.
*
* The consequence is that the function gets "lost"
* after two consecutive differences.
*
* @param str_1 First string to compare
* @param str_2 Second string to compare
*/

int edit_distance_approx(const std::string &str_1, const std::string &str_2);

33 changes: 31 additions & 2 deletions src/map_command_handler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,39 @@ class map_command_handler
}
else if (help_on_unknown_) {
utils::string_map symbols;
std::string string_user = get_cmd();
int distance = 0;
// Minimum length of the two compared strings.
int len_min = 0;
bool has_command_proposal = false;
// Compare the input with every command (excluding alias).
for(const auto& [key, index] : command_map_) {
// No need to test commands that are not enabled.
if(is_enabled(index)) {
distance = edit_distance_approx(string_user, key);
len_min = std::min(string_user.length(), key.length());
// Maximum of a third of the letters are wrong. The ratio
// between the edit distance and the minimum length, multiplied
// by a hundred gives us the percentage of errors.
if(distance * 100 / len_min < 34) {
symbols["command_proposal"] = key;
has_command_proposal = true;
// If a good enough candidate is found, exit the loop.
break;
}
}
}
symbols["command"] = get_cmd();
symbols["help_command"] = cmd_prefix_ + "help";
print("help", VGETTEXT("Unknown command '$command', try $help_command "
"for a list of available commands.", symbols));
// If a proposal for a command is found, print it
if(has_command_proposal) {
print("help", VGETTEXT("Unknown command '$command', did you mean '$command_proposal'? try $help_command "
"for a list of available commands.", symbols));
}
else {
print("help", VGETTEXT("Unknown command '$command', try $help_command "
"for a list of available commands.", symbols));
}
}
}

Expand Down