diff --git a/components/console/linenoise/linenoise.c b/components/console/linenoise/linenoise.c index f68b6ec54..5e39866ba 100644 --- a/components/console/linenoise/linenoise.c +++ b/components/console/linenoise/linenoise.c @@ -112,6 +112,7 @@ #include #include #include +#include #include #include "linenoise.h" @@ -123,6 +124,7 @@ static linenoiseHintsCallback *hintsCallback = NULL; static linenoiseFreeHintsCallback *freeHintsCallback = NULL; static int mlmode = 0; /* Multi line mode. Default is single line. */ +static int dumbmode = 0; /* Dumb mode where line editing is disabled. Off by default */ static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; static int history_len = 0; static char **history = NULL; @@ -194,6 +196,11 @@ void linenoiseSetMultiLine(int ml) { mlmode = ml; } +/* Set if terminal does not recognize escape sequences */ +void linenoiseSetDumbMode(int set) { + dumbmode = set; +} + /* Use the ESC [6n escape sequence to query the horizontal cursor position * and return it. On error -1 is returned, on success the position of the * cursor. */ @@ -204,6 +211,7 @@ static int getCursorPosition() { /* Report cursor location */ fprintf(stdout, "\x1b[6n"); + /* Read the response: ESC [ rows ; cols R */ while (i < sizeof(buf)-1) { if (fread(buf+i, 1, 1, stdin) != 1) break; @@ -875,6 +883,38 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) return l.len; } +int linenoiseProbe() { + /* Switch to non-blocking mode */ + int flags = fcntl(STDIN_FILENO, F_GETFL); + flags |= O_NONBLOCK; + int res = fcntl(STDIN_FILENO, F_SETFL, flags); + if (res != 0) { + return -1; + } + /* Device status request */ + fprintf(stdout, "\x1b[5n"); + + /* Try to read response */ + int timeout_ms = 200; + size_t read_bytes = 0; + while (timeout_ms > 0 && read_bytes < 4) { // response is ESC[0n or ESC[3n + usleep(1000); + char c; + int cb = fread(&c, 1, 1, stdin); + read_bytes += cb; + timeout_ms--; + } + /* Restore old mode */ + flags &= ~O_NONBLOCK; + res = fcntl(STDIN_FILENO, F_SETFL, flags); + if (res != 0) { + return -1; + } + if (read_bytes < 4) { + return -2; + } + return 0; +} static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { int count; @@ -885,18 +925,65 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { } count = linenoiseEdit(buf, buflen, prompt); - printf("\n"); + fputc('\n', stdout); return count; } +static int linenoiseDumb(char* buf, size_t buflen, const char* prompt) { + /* dumb terminal, fall back to fgets */ + fputs(prompt, stdout); + int count = 0; + while (count < buflen) { + int c = fgetc(stdin); + if (c == '\n') { + break; + } else if (c >= 0x1c && c <= 0x1f){ + continue; /* consume arrow keys */ + } else if (c == BACKSPACE || c == 0x8) { + if (count > 0) { + buf[count - 1] = 0; + count --; + } + fputs("\x08 ", stdout); /* Windows CMD: erase symbol under cursor */ + } else { + buf[count] = c; + ++count; + } + fputc(c, stdout); /* echo */ + } + fputc('\n', stdout); + return count; +} + +static void sanitize(char* src) { + char* dst = src; + for (int c = *src; c != 0; src++, c = *src) { + if (isprint(c)) { + *dst = c; + ++dst; + } + } + *dst = 0; +} + /* The high level function that is the main API of the linenoise library. */ char *linenoise(const char *prompt) { - char buf[LINENOISE_MAX_LINE]; - int count; - - count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); - if (count == -1) return NULL; - return strdup(buf); + char *buf = calloc(1, LINENOISE_MAX_LINE); + int count = 0; + if (!dumbmode) { + count = linenoiseRaw(buf, LINENOISE_MAX_LINE, prompt); + } else { + count = linenoiseDumb(buf, LINENOISE_MAX_LINE, prompt); + } + if (count > 0) { + sanitize(buf); + count = strlen(buf); + } + if (count <= 0) { + free(buf); + return NULL; + } + return buf; } /* This is just a wrapper the user may want to call in order to make sure diff --git a/components/console/linenoise/linenoise.h b/components/console/linenoise/linenoise.h index 31a7d0fb9..a82701f83 100644 --- a/components/console/linenoise/linenoise.h +++ b/components/console/linenoise/linenoise.h @@ -56,6 +56,7 @@ void linenoiseSetHintsCallback(linenoiseHintsCallback *); void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); +int linenoiseProbe(void); char *linenoise(const char *prompt); void linenoiseFree(void *ptr); int linenoiseHistoryAdd(const char *line); @@ -65,6 +66,7 @@ int linenoiseHistoryLoad(const char *filename); void linenoiseHistoryFree(); void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); +void linenoiseSetDumbMode(int set); void linenoisePrintKeyCodes(void); #ifdef __cplusplus diff --git a/examples/system/console/main/console_example_main.c b/examples/system/console/main/console_example_main.c index 90ccdcb67..c849d5494 100644 --- a/examples/system/console/main/console_example_main.c +++ b/examples/system/console/main/console_example_main.c @@ -105,41 +105,58 @@ void app_main() register_system(); register_wifi(); + /* Prompt to be printed before each line. + * This can be customized, made dynamic, etc. + */ + const char* prompt = LOG_COLOR_I "esp32> " LOG_RESET_COLOR; + printf("\n" "This is an example of ESP-IDF console component.\n" "Type 'help' to get the list of commands.\n" "Use UP/DOWN arrows to navigate through command history.\n" "Press TAB when typing command name to auto-complete.\n"); - /* Prompt to be printed before each line. - * This can be customized, made dynamic, etc. - */ - const char* prompt = LOG_COLOR_I "[esp32]> " LOG_RESET_COLOR; + /* Figure out if the terminal supports escape sequences */ + int probe_status = linenoiseProbe(); + if (probe_status) { /* zero indicates success */ + printf("\n" + "Your terminal application does not support escape sequences.\n" + "Line editing and history features are disabled.\n" + "On Windows, try using Putty instead.\n"); + linenoiseSetDumbMode(1); +#if CONFIG_LOG_COLORS + /* Since the terminal doesn't support escape sequences, + * don't use color codes in the prompt. + */ + prompt = "esp32> "; +#endif //CONFIG_LOG_COLORS + } /* Main loop */ - char *line; - /* Get a line using linenoise. - * The line is returned when ENTER is pressed. - */ - while((line = linenoise(prompt)) != NULL) { - if (strlen(line) > 0) { /* Ignore empty lines */ - /* Add the command to the history */ - linenoiseHistoryAdd(line); + while(true) { + /* Get a line using linenoise. + * The line is returned when ENTER is pressed. + */ + char* line = linenoise(prompt); + if (line == NULL) { /* Ignore empty lines */ + continue; + } + /* Add the command to the history */ + linenoiseHistoryAdd(line); #if CONFIG_STORE_HISTORY - /* Save command history to filesystem */ - linenoiseHistorySave(HISTORY_PATH); + /* Save command history to filesystem */ + linenoiseHistorySave(HISTORY_PATH); #endif - /* Try to run the command */ - int ret; - esp_err_t err = esp_console_run(line, &ret); - if (err == ESP_ERR_NOT_FOUND) { - printf("Unrecognized command\n"); - } else if (err == ESP_OK && ret != ESP_OK) { - printf("Command returned non-zero error code: 0x%x\n", ret); - } else if (err != ESP_OK) { - printf("Internal error: 0x%x\n", err); - } + /* Try to run the command */ + int ret; + esp_err_t err = esp_console_run(line, &ret); + if (err == ESP_ERR_NOT_FOUND) { + printf("Unrecognized command\n"); + } else if (err == ESP_OK && ret != ESP_OK) { + printf("Command returned non-zero error code: 0x%x\n", ret); + } else if (err != ESP_OK) { + printf("Internal error: 0x%x\n", err); } /* linenoise allocates line buffer on the heap, so need to free it */ linenoiseFree(line);