From dbbecd9e1fbf4449af56f1eec0c69979e7b8538f Mon Sep 17 00:00:00 2001 From: dexv <193105427+dexvleads@users.noreply.github.com> Date: Mon, 13 Jan 2025 03:44:09 +0100 Subject: [PATCH] CLI: Add 'clear' command and command suggestion (#342) * Add 'clear' command and improve command suggestion in CLI - Introduced a new CLI command 'clear' (alias 'cls') to clear the terminal screen. - Enhanced command not found feedback by suggesting similar commands based on user input. - Added a function to calculate string distance for better command matching. * Review changes * Update changelog --------- Co-authored-by: dexv <89728480+DXVVAY@users.noreply.github.com> Co-authored-by: Willy-JL <49810075+Willy-JL@users.noreply.github.com> --- CHANGELOG.md | 1 + applications/services/cli/cli.c | 49 ++++++++++++++++++++++-- applications/services/cli/cli_commands.c | 11 ++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82904b866..990835185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Apps: - Games: Pinball0 (by @rdefeo) - NFC: Metroflip (by @luu176) +- CLI: Add `clear` and `cls` commands, add `did you mean ...?` command suggestion (#342 by @dexvleads) - BadKB: Added german Mac keyboard Layout (#325 by @Cloudy261) - UL: Sub-GHz: Jolly Motors support with add manually (by @pkooiman & @xMasterX) - OFW: Desktop: Add winter animations (by @Astrrra) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index d4903f2c5..73e734073 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -8,6 +8,7 @@ #include #include #include +#include #define TAG "CliSrv" @@ -208,6 +209,36 @@ static void cli_execute_command(Cli* cli, CliCommand* command, FuriString* args) } } +static size_t cli_string_distance(const char* s1, const char* s2) { + size_t distance = 0; + + while(*s1 && *s2) { + if(*s1++ != *s2++) distance++; + } + while(*s1++) + distance++; + while(*s2++) + distance++; + + return distance; +} + +static void cli_find_similar_command(Cli* cli, const char* input, FuriString* suggestion) { + size_t min_distance = (size_t)-1; + size_t max_allowed = (strlen(input) + 1) / 2; + furi_string_reset(suggestion); + + CliCommandTree_it_t it; + for(CliCommandTree_it(it, cli->commands); !CliCommandTree_end_p(it); CliCommandTree_next(it)) { + const char* cmd_name = furi_string_get_cstr(*CliCommandTree_ref(it)->key_ptr); + size_t distance = cli_string_distance(input, cmd_name); + if(distance < min_distance && distance <= max_allowed) { + min_distance = distance; + furi_string_set(suggestion, cmd_name); + } + } +} + static void cli_handle_enter(Cli* cli) { cli_normalize_line(cli); @@ -245,9 +276,21 @@ static void cli_handle_enter(Cli* cli) { } else { furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); cli_nl(cli); - printf( - "`%s` command not found, use `help` or `?` to list all available commands", - furi_string_get_cstr(command)); + FuriString* suggestion = furi_string_alloc(); + cli_find_similar_command(cli, furi_string_get_cstr(command), suggestion); + + if(furi_string_empty(suggestion)) { + printf( + "`%s` command not found, use `help` or `?` to list all available commands", + furi_string_get_cstr(command)); + } else { + printf( + "`%s` command not found, did you mean `%s`? Use `help` or `?` to list all available commands", + furi_string_get_cstr(command), + furi_string_get_cstr(suggestion)); + } + + furi_string_free(suggestion); cli_putc(cli, CliKeyBell); } diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 9dd4e4265..e53744d80 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -682,6 +682,13 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) { furi_hal_i2c_release(&furi_hal_i2c_handle_external); } +void cli_command_clear(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); + UNUSED(args); + UNUSED(context); + printf("\e[2J\e[H"); +} + CLI_PLUGIN_WRAPPER("info", cli_command_info) CLI_PLUGIN_WRAPPER("src", cli_command_src) CLI_PLUGIN_WRAPPER("neofetch", cli_command_neofetch) @@ -693,6 +700,7 @@ CLI_PLUGIN_WRAPPER("vibro", cli_command_vibro) CLI_PLUGIN_WRAPPER("led", cli_command_led) CLI_PLUGIN_WRAPPER("gpio", cli_command_gpio) CLI_PLUGIN_WRAPPER("i2c", cli_command_i2c) +CLI_PLUGIN_WRAPPER("clear", cli_command_clear) void cli_commands_init(Cli* cli) { cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info_wrapper, (void*)true); @@ -724,4 +732,7 @@ void cli_commands_init(Cli* cli) { cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led_wrapper, NULL); cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio_wrapper, NULL); cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c_wrapper, NULL); + + cli_add_command(cli, "clear", CliCommandFlagParallelSafe, cli_command_clear, NULL); + cli_add_command(cli, "cls", CliCommandFlagParallelSafe, cli_command_clear, NULL); }