[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 = 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) { | ||||
|         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); | ||||
|     } | ||||
|  | ||||
| @ -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(); | ||||
|  | ||||
| @ -7,6 +7,7 @@ | ||||
| 
 | ||||
| #include <m-dict.h> | ||||
| #include <m-bptree.h> | ||||
| #include <m-array.h> | ||||
| 
 | ||||
| #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); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 あく
						あく