[FL-1555] Cli: update motd (#584)
* Cli: update motd * Cli: autocomplete and cursor. * Cli: one line history. * Cli: minor cleanup, remove double flush, remove prompt on empty autocomplete
This commit is contained in:
		
							parent
							
								
									30ae16c2e1
								
							
						
					
					
						commit
						fbb81483ae
					
				| @ -5,24 +5,27 @@ | |||||||
| 
 | 
 | ||||||
| Cli* cli_alloc() { | Cli* cli_alloc() { | ||||||
|     Cli* cli = furi_alloc(sizeof(Cli)); |     Cli* cli = furi_alloc(sizeof(Cli)); | ||||||
|  | 
 | ||||||
|     CliCommandTree_init(cli->commands); |     CliCommandTree_init(cli->commands); | ||||||
| 
 | 
 | ||||||
|  |     string_init(cli->last_line); | ||||||
|  |     string_init(cli->line); | ||||||
|  | 
 | ||||||
|     cli->mutex = osMutexNew(NULL); |     cli->mutex = osMutexNew(NULL); | ||||||
|     furi_check(cli->mutex); |     furi_check(cli->mutex); | ||||||
| 
 | 
 | ||||||
|     cli_reset_state(cli); |  | ||||||
| 
 |  | ||||||
|     return cli; |     return cli; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cli_free(Cli* cli) { | void cli_free(Cli* cli) { | ||||||
|     free(cli); |     furi_assert(cli); | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| void cli_reset_state(Cli* cli) { |     string_clear(cli->last_line); | ||||||
|     // Release allocated buffer, reset state
 |  | ||||||
|     string_clear(cli->line); |     string_clear(cli->line); | ||||||
|     string_init(cli->line); | 
 | ||||||
|  |     CliCommandTree_clear(cli->commands); | ||||||
|  | 
 | ||||||
|  |     free(cli); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cli_putc(char c) { | void cli_putc(char c) { | ||||||
| @ -33,7 +36,7 @@ char cli_getc(Cli* cli) { | |||||||
|     furi_assert(cli); |     furi_assert(cli); | ||||||
|     char c; |     char c; | ||||||
|     if(api_hal_vcp_rx((uint8_t*)&c, 1) == 0) { |     if(api_hal_vcp_rx((uint8_t*)&c, 1) == 0) { | ||||||
|         cli_reset_state(cli); |         cli_reset(cli); | ||||||
|     } |     } | ||||||
|     return c; |     return c; | ||||||
| } | } | ||||||
| @ -56,20 +59,6 @@ bool cli_cmd_interrupt_received(Cli* cli) { | |||||||
|     return c == CliSymbolAsciiETX; |     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) { | void cli_print_usage(const char* cmd, const char* usage, const char* arg) { | ||||||
|     furi_assert(cmd); |     furi_assert(cmd); | ||||||
|     furi_assert(arg); |     furi_assert(arg); | ||||||
| @ -79,94 +68,200 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cli_motd() { | void cli_motd() { | ||||||
|     printf("\r\n \
 |     printf("\r\n" | ||||||
|               _.-------.._                    -,\r\n \ |            "              _.-------.._                    -,\r\n" | ||||||
|           .-\"```\"--..,,_/ /`-,               -,  \\ \r\n \
 |            "          .-\"```\"--..,,_/ /`-,               -,  \\ \r\n" | ||||||
|        .:\"          /:/  /'\\  \\     ,_...,  `. |  |\r\n \
 |            "       .:\"          /:/  /'\\  \\     ,_...,  `. |  |\r\n" | ||||||
|       /       ,----/:/  /`\\ _\\~`_-\"`     _;\r\n \
 |            "      /       ,----/:/  /`\\ _\\~`_-\"`     _;\r\n" | ||||||
|      '      / /`\"\"\"'\\ \\ \\.~`_-'      ,-\"'/ \r\n \
 |            "     '      / /`\"\"\"'\\ \\ \\.~`_-'      ,-\"'/ \r\n" | ||||||
|     |      | |  0    | | .-'      ,/`  /\r\n \ |            "    |      | |  0    | | .-'      ,/`  /\r\n" | ||||||
|    |    ,..\\ \\     ,.-\"`       ,/`    /\r\n \
 |            "   |    ,..\\ \\     ,.-\"`       ,/`    /\r\n" | ||||||
|   ;    :    `/`\"\"\\`           ,/--==,/-----,\r\n \
 |            "  ;    :    `/`\"\"\\`           ,/--==,/-----,\r\n" | ||||||
|   |    `-...|        -.___-Z:_______J...---;\r\n \ |            "  |    `-...|        -.___-Z:_______J...---;\r\n" | ||||||
|   :         `                           _-'\r\n \ |            "  :         `                           _-'\r\n" | ||||||
|  _L_  _     ___  ___  ___  ___  ____--\"`___  _     ___\r\n \
 |            " _L_  _     ___  ___  ___  ___  ____--\"`___  _     ___\r\n" | ||||||
| | __|| |   |_ _|| _ \\| _ \\| __|| _ \\   / __|| |   |_ _|\r\n \ |            "| __|| |   |_ _|| _ \\| _ \\| __|| _ \\   / __|| |   |_ _|\r\n" | ||||||
| | _| | |__  | | |  _/|  _/| _| |   /  | (__ | |__  | |\r\n \ |            "| _| | |__  | | |  _/|  _/| _| |   /  | (__ | |__  | |\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"); |     const Version* firmware_version = api_hal_version_get_firmware_version(); | ||||||
| 
 |     if(firmware_version) { | ||||||
|     printf("Bootloader\r\n"); |         printf( | ||||||
|     cli_print_version(api_hal_version_get_boot_version()); |             "Firmware version: %s %s (%s built on %s)\r\n", | ||||||
| 
 |             version_get_gitbranch(firmware_version), | ||||||
|     printf("Firmware\r\n"); |             version_get_version(firmware_version), | ||||||
|     cli_print_version(api_hal_version_get_firmware_version()); |             version_get_githash(firmware_version), | ||||||
|  |             version_get_builddate(firmware_version)); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cli_nl() { | void cli_nl(Cli* cli) { | ||||||
|     printf("\r\n"); |     printf("\r\n"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cli_prompt() { | void cli_prompt(Cli* cli) { | ||||||
|     printf("\r\n>: "); |     printf("\r\n>: %s", string_get_cstr(cli->line)); | ||||||
|     fflush(stdout); |     fflush(stdout); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cli_backspace(Cli* cli) { | void cli_reset(Cli* cli) { | ||||||
|     size_t s = string_size(cli->line); |     string_move(cli->last_line, cli->line); | ||||||
|     if(s > 0) { |     string_init(cli->line); | ||||||
|         s--; |     cli->cursor_position = 0; | ||||||
|         string_left(cli->line, s); | } | ||||||
|         cli_putc(CliSymbolAsciiBackspace); | 
 | ||||||
|         cli_putc(CliSymbolAsciiSpace); | static void cli_handle_backspace(Cli* cli) { | ||||||
|         cli_putc(CliSymbolAsciiBackspace); |     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 { |     } else { | ||||||
|         cli_putc(CliSymbolAsciiBell); |         cli_putc(CliSymbolAsciiBell); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void cli_enter(Cli* cli) { | static void cli_normalize_line(Cli* cli) { | ||||||
|     // Normalize input
 |  | ||||||
|     string_strim(cli->line); |     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) { |     if(string_size(cli->line) == 0) { | ||||||
|         cli_prompt(); |         cli_prompt(cli); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Get first word as command name
 |     // Command and args container
 | ||||||
|     string_t command; |     string_t command; | ||||||
|     string_init(command); |     string_init(command); | ||||||
|  |     string_t args; | ||||||
|  |     string_init(args); | ||||||
|  | 
 | ||||||
|  |     // Split command and args
 | ||||||
|     size_t ws = string_search_char(cli->line, ' '); |     size_t ws = string_search_char(cli->line, ' '); | ||||||
|     if(ws == STRING_FAILURE) { |     if(ws == STRING_FAILURE) { | ||||||
|         string_set(command, cli->line); |         string_set(command, cli->line); | ||||||
|         string_clear(cli->line); |  | ||||||
|         string_init(cli->line); |  | ||||||
|     } else { |     } else { | ||||||
|         string_set_n(command, cli->line, 0, ws); |         string_set_n(command, cli->line, 0, ws); | ||||||
|         string_right(cli->line, ws); |         string_set_n(args, cli->line, ws, string_size(cli->line)); | ||||||
|         string_strim(cli->line); |         string_strim(args); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Search for command
 |     // Search for command
 | ||||||
|     furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK); |     furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK); | ||||||
|     CliCommand* cli_command = CliCommandTree_get(cli->commands, command); |     CliCommand* cli_command = CliCommandTree_get(cli->commands, command); | ||||||
|     furi_check(osMutexRelease(cli->mutex) == osOK); |  | ||||||
|     if(cli_command) { |     if(cli_command) { | ||||||
|         cli_nl(); |         cli_nl(cli); | ||||||
|         cli_command->callback(cli, cli->line, cli_command->context); |         // Execute command
 | ||||||
|         cli_prompt(); |         cli_command->callback(cli, args, cli_command->context); | ||||||
|  |         // Clear line
 | ||||||
|  |         cli_reset(cli); | ||||||
|     } else { |     } else { | ||||||
|         cli_nl(); |         cli_nl(cli); | ||||||
|         printf("Command not found: "); |         printf( | ||||||
|         printf(string_get_cstr(command)); |             "`%s` command not found, use `help` or `?` to list all available commands", | ||||||
|         cli_prompt(); |             string_get_cstr(command)); | ||||||
|         cli_putc(CliSymbolAsciiBell); |         cli_putc(CliSymbolAsciiBell); | ||||||
|     } |     } | ||||||
|  |     furi_check(osMutexRelease(cli->mutex) == osOK); | ||||||
| 
 | 
 | ||||||
|  |     cli_prompt(cli); | ||||||
|  | 
 | ||||||
|  |     // Cleanup command and args
 | ||||||
|     string_clear(command); |     string_clear(command); | ||||||
|     // Always finish with clean state
 |     string_clear(args); | ||||||
|     cli_reset_state(cli); | } | ||||||
|  | 
 | ||||||
|  | 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) { | void cli_process_input(Cli* cli) { | ||||||
| @ -174,26 +269,42 @@ void cli_process_input(Cli* cli) { | |||||||
|     size_t r; |     size_t r; | ||||||
| 
 | 
 | ||||||
|     if(c == CliSymbolAsciiTab) { |     if(c == CliSymbolAsciiTab) { | ||||||
|         cli_putc(CliSymbolAsciiBell); |         cli_handle_autocomplete(cli); | ||||||
|     } else if(c == CliSymbolAsciiSOH) { |     } else if(c == CliSymbolAsciiSOH) { | ||||||
|         cli_motd(); |         cli_motd(); | ||||||
|         cli_prompt(); |         cli_prompt(cli); | ||||||
|     } else if(c == CliSymbolAsciiEOT) { |     } else if(c == CliSymbolAsciiEOT) { | ||||||
|         cli_reset_state(cli); |         cli_reset(cli); | ||||||
|     } else if(c == CliSymbolAsciiEsc) { |     } else if(c == CliSymbolAsciiEsc) { | ||||||
|         r = api_hal_vcp_rx((uint8_t*)&c, 1); |         r = api_hal_vcp_rx((uint8_t*)&c, 1); | ||||||
|         if(r && c == '[') { |         if(r && c == '[') { | ||||||
|             api_hal_vcp_rx((uint8_t*)&c, 1); |             api_hal_vcp_rx((uint8_t*)&c, 1); | ||||||
|  |             cli_handle_escape(cli, c); | ||||||
|         } else { |         } else { | ||||||
|             cli_putc(CliSymbolAsciiBell); |             cli_putc(CliSymbolAsciiBell); | ||||||
|         } |         } | ||||||
|     } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) { |     } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) { | ||||||
|         cli_backspace(cli); |         cli_handle_backspace(cli); | ||||||
|     } else if(c == CliSymbolAsciiCR) { |     } else if(c == CliSymbolAsciiCR) { | ||||||
|         cli_enter(cli); |         cli_handle_enter(cli); | ||||||
|     } else if(c >= 0x20 && c < 0x7F) { |     } else if(c >= 0x20 && c < 0x7F) { | ||||||
|         string_push_back(cli->line, c); |         if(cli->cursor_position == string_size(cli->line)) { | ||||||
|         cli_putc(c); |             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 { |     } else { | ||||||
|         cli_putc(CliSymbolAsciiBell); |         cli_putc(CliSymbolAsciiBell); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -94,7 +94,6 @@ void cli_command_help(Cli* cli, string_t args, void* context) { | |||||||
|     (void)args; |     (void)args; | ||||||
|     printf("Commands we have:"); |     printf("Commands we have:"); | ||||||
| 
 | 
 | ||||||
|     furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK); |  | ||||||
|     // Get the middle element
 |     // Get the middle element
 | ||||||
|     CliCommandTree_it_t it_mid; |     CliCommandTree_it_t it_mid; | ||||||
|     uint8_t cmd_num = CliCommandTree_size(cli->commands); |     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); |         ref = CliCommandTree_ref(it_j); | ||||||
|         printf(string_get_cstr(ref->key_ptr[0])); |         printf(string_get_cstr(ref->key_ptr[0])); | ||||||
|     }; |     }; | ||||||
|     furi_check(osMutexRelease(cli->mutex) == osOK); |  | ||||||
| 
 | 
 | ||||||
|     if(string_size(args) > 0) { |     if(string_size(args) > 0) { | ||||||
|         cli_nl(); |         cli_nl(); | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ | |||||||
| 
 | 
 | ||||||
| #include <m-dict.h> | #include <m-dict.h> | ||||||
| #include <m-bptree.h> | #include <m-bptree.h> | ||||||
|  | #include <m-array.h> | ||||||
| 
 | 
 | ||||||
| #define CLI_LINE_SIZE_MAX | #define CLI_LINE_SIZE_MAX | ||||||
| #define CLI_COMMANDS_TREE_RANK 4 | #define CLI_COMMANDS_TREE_RANK 4 | ||||||
| @ -24,15 +25,23 @@ BPTREE_DEF2( | |||||||
|     CliCommand, |     CliCommand, | ||||||
|     M_POD_OPLIST) |     M_POD_OPLIST) | ||||||
| 
 | 
 | ||||||
|  | #define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST) | ||||||
|  | 
 | ||||||
| struct Cli { | struct Cli { | ||||||
|     CliCommandTree_t commands; |     CliCommandTree_t commands; | ||||||
|     osMutexId_t mutex; |     osMutexId_t mutex; | ||||||
|  |     string_t last_line; | ||||||
|     string_t line; |     string_t line; | ||||||
|  | 
 | ||||||
|  |     size_t cursor_position; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Cli* cli_alloc(); | Cli* cli_alloc(); | ||||||
|  | 
 | ||||||
| void cli_free(Cli* cli); | 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_putc(char c); | ||||||
|  | 
 | ||||||
| void cli_stdout_callback(void* _cookie, const char* data, size_t size); | void cli_stdout_callback(void* _cookie, const char* data, size_t size); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 あく
						あく