Pārlūkot izejas kodu

[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
あく 4 gadi atpakaļ
vecāks
revīzija
fbb81483ae
3 mainītis faili ar 205 papildinājumiem un 87 dzēšanām
  1. 194 83
      applications/cli/cli.c
  2. 0 2
      applications/cli/cli_commands.c
  3. 11 2
      applications/cli/cli_i.h

+ 194 - 83
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("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());
-}
-
-void cli_nl() {
+    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");
+
+    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(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);
     }

+ 0 - 2
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();

+ 11 - 2
applications/cli/cli_i.h

@@ -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);