diff --git a/applications/cli/cli.c b/applications/cli/cli.c index 948f7ac9..cf61cff0 100644 --- a/applications/cli/cli.c +++ b/applications/cli/cli.c @@ -5,24 +5,27 @@ Cli* cli_alloc() { Cli* cli = furi_alloc(sizeof(Cli)); + CliCommandTree_init(cli->commands); + string_init(cli->last_line); + string_init(cli->line); + cli->mutex = osMutexNew(NULL); furi_check(cli->mutex); - cli_reset_state(cli); - return cli; } void cli_free(Cli* cli) { - free(cli); -} + furi_assert(cli); -void cli_reset_state(Cli* cli) { - // Release allocated buffer, reset state + string_clear(cli->last_line); string_clear(cli->line); - string_init(cli->line); + + CliCommandTree_clear(cli->commands); + + free(cli); } void cli_putc(char c) { @@ -33,7 +36,7 @@ char cli_getc(Cli* cli) { furi_assert(cli); char c; if(api_hal_vcp_rx((uint8_t*)&c, 1) == 0) { - cli_reset_state(cli); + cli_reset(cli); } return c; } @@ -56,20 +59,6 @@ bool cli_cmd_interrupt_received(Cli* cli) { return c == CliSymbolAsciiETX; } -void cli_print_version(const Version* version) { - if(version) { - printf("\tVersion:\t%s\r\n", version_get_version(version)); - printf("\tBuild date:\t%s\r\n", version_get_builddate(version)); - printf( - "\tGit Commit:\t%s (%s)\r\n", - version_get_githash(version), - version_get_gitbranchnum(version)); - printf("\tGit Branch:\t%s\r\n", version_get_gitbranch(version)); - } else { - printf("\tNo build info\r\n"); - } -} - void cli_print_usage(const char* cmd, const char* usage, const char* arg) { furi_assert(cmd); furi_assert(arg); @@ -79,94 +68,200 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) { } void cli_motd() { - printf("\r\n \ - _.-------.._ -,\r\n \ - .-\"```\"--..,,_/ /`-, -, \\ \r\n \ - .:\" /:/ /'\\ \\ ,_..., `. | |\r\n \ - / ,----/:/ /`\\ _\\~`_-\"` _;\r\n \ - ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n \ - | | | 0 | | .-' ,/` /\r\n \ - | ,..\\ \\ ,.-\"` ,/` /\r\n \ - ; : `/`\"\"\\` ,/--==,/-----,\r\n \ - | `-...| -.___-Z:_______J...---;\r\n \ - : ` _-'\r\n \ - _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n \ -| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n \ -| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n \ -|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n\r\n"); + printf("\r\n" + " _.-------.._ -,\r\n" + " .-\"```\"--..,,_/ /`-, -, \\ \r\n" + " .:\" /:/ /'\\ \\ ,_..., `. | |\r\n" + " / ,----/:/ /`\\ _\\~`_-\"` _;\r\n" + " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n" + " | | | 0 | | .-' ,/` /\r\n" + " | ,..\\ \\ ,.-\"` ,/` /\r\n" + " ; : `/`\"\"\\` ,/--==,/-----,\r\n" + " | `-...| -.___-Z:_______J...---;\r\n" + " : ` _-'\r\n" + " _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n" + "| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n" + "| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n" + "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" + "\r\n" + "Welcome to Flipper Zero Command Line Interface!\r\n" + "Read Manual https://docs.flipperzero.one\r\n" + "\r\n"); - printf("You are now connected to Flipper Command Line Interface.\r\n\r\n"); - - printf("Bootloader\r\n"); - cli_print_version(api_hal_version_get_boot_version()); - - printf("Firmware\r\n"); - cli_print_version(api_hal_version_get_firmware_version()); + const Version* firmware_version = api_hal_version_get_firmware_version(); + if(firmware_version) { + printf( + "Firmware version: %s %s (%s built on %s)\r\n", + version_get_gitbranch(firmware_version), + version_get_version(firmware_version), + version_get_githash(firmware_version), + version_get_builddate(firmware_version)); + } } -void cli_nl() { +void cli_nl(Cli* cli) { printf("\r\n"); } -void cli_prompt() { - printf("\r\n>: "); +void cli_prompt(Cli* cli) { + printf("\r\n>: %s", string_get_cstr(cli->line)); fflush(stdout); } -void cli_backspace(Cli* cli) { - size_t s = string_size(cli->line); - if(s > 0) { - s--; - string_left(cli->line, s); - cli_putc(CliSymbolAsciiBackspace); - cli_putc(CliSymbolAsciiSpace); - cli_putc(CliSymbolAsciiBackspace); +void cli_reset(Cli* cli) { + string_move(cli->last_line, cli->line); + string_init(cli->line); + cli->cursor_position = 0; +} + +static void cli_handle_backspace(Cli* cli) { + if(string_size(cli->line) > 0) { + // Other side + printf("\e[D\e[1P"); + fflush(stdout); + // Our side + string_t temp; + string_init(temp); + string_reserve(temp, string_size(cli->line) - 1); + string_set_strn(temp, string_get_cstr(cli->line), cli->cursor_position - 1); + string_cat_str(temp, string_get_cstr(cli->line) + cli->cursor_position); + string_move(cli->line, temp); + cli->cursor_position--; } else { cli_putc(CliSymbolAsciiBell); } } -void cli_enter(Cli* cli) { - // Normalize input +static void cli_normalize_line(Cli* cli) { string_strim(cli->line); + cli->cursor_position = string_size(cli->line); +} + +static void cli_handle_enter(Cli* cli) { + cli_normalize_line(cli); + if(string_size(cli->line) == 0) { - cli_prompt(); + cli_prompt(cli); return; } - // Get first word as command name + // Command and args container string_t command; string_init(command); + string_t args; + string_init(args); + + // Split command and args size_t ws = string_search_char(cli->line, ' '); if(ws == STRING_FAILURE) { string_set(command, cli->line); - string_clear(cli->line); - string_init(cli->line); } else { string_set_n(command, cli->line, 0, ws); - string_right(cli->line, ws); - string_strim(cli->line); + string_set_n(args, cli->line, ws, string_size(cli->line)); + string_strim(args); } // Search for command furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK); CliCommand* cli_command = CliCommandTree_get(cli->commands, command); - furi_check(osMutexRelease(cli->mutex) == osOK); if(cli_command) { - cli_nl(); - cli_command->callback(cli, cli->line, cli_command->context); - cli_prompt(); + cli_nl(cli); + // Execute command + cli_command->callback(cli, args, cli_command->context); + // Clear line + cli_reset(cli); } else { - cli_nl(); - printf("Command not found: "); - printf(string_get_cstr(command)); - cli_prompt(); + cli_nl(cli); + printf( + "`%s` command not found, use `help` or `?` to list all available commands", + string_get_cstr(command)); cli_putc(CliSymbolAsciiBell); } + furi_check(osMutexRelease(cli->mutex) == osOK); + cli_prompt(cli); + + // Cleanup command and args string_clear(command); - // Always finish with clean state - cli_reset_state(cli); + string_clear(args); +} + +static void cli_handle_autocomplete(Cli* cli) { + cli_normalize_line(cli); + + if(string_size(cli->line) == 0) { + return; + } + + cli_nl(cli); + + // Prepare common base for autocomplete + string_t common; + string_init(common); + // Iterate throw commands + for + M_EACH(cli_command, cli->commands, CliCommandTree_t) { + // Process only if starts with line buffer + if(string_start_with_string_p(*cli_command->key_ptr, cli->line)) { + // Show autocomplete option + printf("%s\r\n", string_get_cstr(*cli_command->key_ptr)); + // Process common base for autocomplete + if(string_size(common) > 0) { + // Choose shortest string + const size_t key_size = string_size(*cli_command->key_ptr); + const size_t common_size = string_size(common); + const size_t min_size = key_size > common_size ? common_size : key_size; + size_t i = 0; + while(i < min_size) { + // Stop when do not match + if(string_get_char(*cli_command->key_ptr, i) != + string_get_char(common, i)) { + break; + } + i++; + } + // Cut right part if any + string_left(common, i); + } else { + // Start with something + string_set(common, *cli_command->key_ptr); + } + } + } + // Replace line buffer if autocomplete better + if(string_size(common) > string_size(cli->line)) { + string_set(cli->line, common); + cli->cursor_position = string_size(cli->line); + } + // Cleanup + string_clean(common); + // Show prompt + cli_prompt(cli); +} + +static void cli_handle_escape(Cli* cli, char c) { + if(c == 'A') { + // Use previous command if line buffer is empty + if(string_size(cli->line) == 0 && string_cmp(cli->line, cli->last_line) != 0) { + // Set line buffer and cursor position + string_set(cli->line, cli->last_line); + cli->cursor_position = string_size(cli->line); + // Show new line to user + printf(string_get_cstr(cli->line)); + } + } else if(c == 'B') { + } else if(c == 'C') { + if(cli->cursor_position < string_size(cli->line)) { + cli->cursor_position++; + printf("\e[C"); + } + } else if(c == 'D') { + if(cli->cursor_position > 0) { + cli->cursor_position--; + printf("\e[D"); + } + } + fflush(stdout); } void cli_process_input(Cli* cli) { @@ -174,26 +269,42 @@ void cli_process_input(Cli* cli) { size_t r; if(c == CliSymbolAsciiTab) { - cli_putc(CliSymbolAsciiBell); + cli_handle_autocomplete(cli); } else if(c == CliSymbolAsciiSOH) { cli_motd(); - cli_prompt(); + cli_prompt(cli); } else if(c == CliSymbolAsciiEOT) { - cli_reset_state(cli); + cli_reset(cli); } else if(c == CliSymbolAsciiEsc) { r = api_hal_vcp_rx((uint8_t*)&c, 1); if(r && c == '[') { api_hal_vcp_rx((uint8_t*)&c, 1); + cli_handle_escape(cli, c); } else { cli_putc(CliSymbolAsciiBell); } } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) { - cli_backspace(cli); + cli_handle_backspace(cli); } else if(c == CliSymbolAsciiCR) { - cli_enter(cli); + cli_handle_enter(cli); } else if(c >= 0x20 && c < 0x7F) { - string_push_back(cli->line, c); - cli_putc(c); + if(cli->cursor_position == string_size(cli->line)) { + string_push_back(cli->line, c); + cli_putc(c); + } else { + // ToDo: better way? + string_t temp; + string_init(temp); + string_reserve(temp, string_size(cli->line) + 1); + string_set_strn(temp, string_get_cstr(cli->line), cli->cursor_position); + string_push_back(temp, c); + string_cat_str(temp, string_get_cstr(cli->line) + cli->cursor_position); + string_move(cli->line, temp); + // Print character in replace mode + printf("\e[4h%c\e[4l", c); + fflush(stdout); + } + cli->cursor_position++; } else { cli_putc(CliSymbolAsciiBell); } diff --git a/applications/cli/cli_commands.c b/applications/cli/cli_commands.c index 2b7c70d0..90d5995d 100644 --- a/applications/cli/cli_commands.c +++ b/applications/cli/cli_commands.c @@ -94,7 +94,6 @@ void cli_command_help(Cli* cli, string_t args, void* context) { (void)args; printf("Commands we have:"); - furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK); // Get the middle element CliCommandTree_it_t it_mid; uint8_t cmd_num = CliCommandTree_size(cli->commands); @@ -113,7 +112,6 @@ void cli_command_help(Cli* cli, string_t args, void* context) { ref = CliCommandTree_ref(it_j); printf(string_get_cstr(ref->key_ptr[0])); }; - furi_check(osMutexRelease(cli->mutex) == osOK); if(string_size(args) > 0) { cli_nl(); diff --git a/applications/cli/cli_i.h b/applications/cli/cli_i.h index 66ffdda6..943d6304 100755 --- a/applications/cli/cli_i.h +++ b/applications/cli/cli_i.h @@ -7,6 +7,7 @@ #include #include +#include #define CLI_LINE_SIZE_MAX #define CLI_COMMANDS_TREE_RANK 4 @@ -24,15 +25,23 @@ BPTREE_DEF2( CliCommand, M_POD_OPLIST) +#define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST) + struct Cli { CliCommandTree_t commands; osMutexId_t mutex; + string_t last_line; string_t line; + + size_t cursor_position; }; Cli* cli_alloc(); + void cli_free(Cli* cli); -void cli_reset_state(Cli* cli); -void cli_print_version(const Version* version); + +void cli_reset(Cli* cli); + void cli_putc(char c); + void cli_stdout_callback(void* _cookie, const char* data, size_t size);