diff --git a/os/various/shell/shell.c b/os/various/shell/shell.c index 269ba1818..2a61c70eb 100644 --- a/os/various/shell/shell.c +++ b/os/various/shell/shell.c @@ -88,11 +88,6 @@ static char *parse_arguments(char *str, char **saveptr) { return *p != '\0' ? p : NULL; } -static void usage(BaseSequentialStream *chp, char *p) { - - chprintf(chp, "Usage: %s\r\n", p); -} - static void list_commands(BaseSequentialStream *chp, const ShellCommand *scp) { while (scp->sc_name != NULL) { @@ -114,6 +109,212 @@ static bool cmdexec(const ShellCommand *scp, BaseSequentialStream *chp, return true; } +#if (SHELL_USE_HISTORY == TRUE) || defined(__DOXYGEN__) +static void del_histbuff_entry(ShellHistory *shp) { + int pos = shp->sh_beg + *(shp->sh_buffer + shp->sh_beg) + 1; + + if (pos >= shp->sh_size) + pos -= shp->sh_size; + + shp->sh_beg = pos; +} + +static bool is_histbuff_space(ShellHistory *shp, int length) { + + if (shp->sh_end >= shp->sh_beg) { + if (length < (shp->sh_size - (shp->sh_end - shp->sh_beg + 1))) + return true; + } + else { + if (length < (shp->sh_beg - shp->sh_end - 1)) + return true; + } + + return false; +} + +static void save_history(ShellHistory *shp, char *line, int length) { + + if (length > shp->sh_size - 2) + return; + + while ((*(line + length -1) == ' ') && (length > 0)) + length--; + + if (length <= 0) + return; + + while (!is_histbuff_space(shp, length)) + del_histbuff_entry(shp); + + if (length < shp->sh_size - shp->sh_end - 1) + memcpy(shp->sh_buffer + shp->sh_end + 1, line, length); + else { + /* + * Since there isn't enough room left at the end of the buffer, + * split the line to save up to the end of the buffer and then + * wrap back to the beginning of the buffer. + */ + int part_len = shp->sh_size - shp->sh_end - 1; + memcpy(shp->sh_buffer + shp->sh_end + 1, line, part_len); + memcpy(shp->sh_buffer, line + part_len, length - part_len); + } + + /* Save the length of the current line and move the buffer end pointer */ + *(shp->sh_buffer + shp->sh_end) = (char)length; + shp->sh_end += length + 1; + if (shp->sh_end >= shp->sh_size) + shp->sh_end -= shp->sh_size; + *(shp->sh_buffer + shp->sh_end) = 0; + shp->sh_cur = 0; +} + +static int get_history(ShellHistory *shp, char *line, int dir) { + int count=0; + + /* Count the number of lines saved in the buffer */ + int idx = shp->sh_beg; + while (idx != shp->sh_end) { + idx += *(shp->sh_buffer + idx) + 1; + if (idx >= shp->sh_size) + idx -= shp->sh_size; + count++; + } + + if (dir == SHELL_HIST_DIR_FW) { + if (shp->sh_cur > 0) + shp->sh_cur -= 2; + else + return 0; + } + + if (count >= shp->sh_cur) { + idx = shp->sh_beg; + int i = 0; + while (idx != shp->sh_end && shp->sh_cur != (count - i - 1)) { + idx += *(shp->sh_buffer + idx) + 1; + if (idx >= shp->sh_size) + idx -= shp->sh_size; + i++; + } + + int length = *(shp->sh_buffer + idx); + + if (length > 0) { + shp->sh_cur++; + + memset(line, 0, SHELL_MAX_LINE_LENGTH); + if ((idx + length) < shp->sh_size) { + memcpy(line, (shp->sh_buffer + idx + 1), length); + } + else { + /* + * Since the saved line was split at the end of the buffer, + * get the line in two parts. + */ + int part_len = shp->sh_size - idx - 1; + memcpy(line, shp->sh_buffer + idx + 1, part_len); + memcpy(line + part_len, shp->sh_buffer, length - part_len); + } + return length; + } + else if (dir == SHELL_HIST_DIR_FW) { + shp->sh_cur++; + return 0; + } + } + return -1; +} +#endif + +#if (SHELL_USE_COMPLETION == TRUE) +static void get_completions(ShellConfig *scfg, char *line) { + ShellCommand *lcp = shell_local_commands; + const ShellCommand *scp = scfg->sc_commands; + char **scmp = scfg->sc_completion; + char help_cmp[] = "help"; + + if (strstr(help_cmp, line) == help_cmp) { + *scmp++ = help_cmp; + } + while (lcp->sc_name != NULL) { + if (strstr(lcp->sc_name, line) == lcp->sc_name) { + *scmp++ = (char *)lcp->sc_name; + } + lcp++; + } + if (scp != NULL) { + while (scp->sc_name != NULL) { + if (strstr(scp->sc_name, line) == scp->sc_name) { + *scmp++ = (char *)scp->sc_name; + } + scp++; + } + } + + *scmp = NULL; +} + +static int process_completions(ShellConfig *scfg, char *line, int length, unsigned size) { + char **scmp = scfg->sc_completion; + char **cmp = scmp + 1; + char *c = line + length; + int clen = 0; + + if (*scmp != NULL) { + if (*cmp == NULL) { + clen = strlen(*scmp); + int i = length; + while ((c < line + clen) && (c < line + size - 1)) + *c++ = *(*scmp + i++); + if (c < line + size -1) { + *c = ' '; + clen++; + } + } + else { + while (*(*scmp + clen) != 0) { + while ((*(*scmp + clen) == *(*cmp + clen)) && + (*(*cmp + clen) != 0) && (*cmp != NULL)) { + cmp++; + } + if (*cmp == NULL) { + if ((c < line + size - 1) && (clen >= length)) + *c++ = *(*scmp + clen); + cmp = scmp + 1; + clen++; + } + else { + break; + } + } + } + + *(line + clen) = 0; + } + + return clen; +} + +static void write_completions(ShellConfig *scfg, char *line, int pos) { + BaseSequentialStream *chp = scfg->sc_channel; + char **scmp = scfg->sc_completion; + + if (*(scmp + 1) != NULL) { + chprintf(chp, SHELL_NEWLINE_STR); + while (*scmp != NULL) + chprintf(chp, " %s", *scmp++); + chprintf(chp, SHELL_NEWLINE_STR); + + chprintf(chp, SHELL_PROMPT_STR); + chprintf(chp, "%s", line); + } + else { + chprintf(chp, "%s", line + pos); + } +} +#endif + /*===========================================================================*/ /* Module exported functions. */ /*===========================================================================*/ @@ -125,17 +326,20 @@ static bool cmdexec(const ShellCommand *scp, BaseSequentialStream *chp, */ THD_FUNCTION(shellThread, p) { int n; + ShellConfig *scfg = p; BaseSequentialStream *chp = ((ShellConfig *)p)->sc_channel; const ShellCommand *scp = ((ShellConfig *)p)->sc_commands; char *lp, *cmd, *tokp, line[SHELL_MAX_LINE_LENGTH]; char *args[SHELL_MAX_ARGUMENTS + 1]; - chprintf(chp, "\r\nChibiOS/RT Shell\r\n"); + chprintf(chp, SHELL_NEWLINE_STR); + chprintf(chp, "ChibiOS/RT Shell"SHELL_NEWLINE_STR); while (true) { - chprintf(chp, "ch> "); - if (shellGetLine(chp, line, sizeof(line))) { + chprintf(chp, SHELL_PROMPT_STR); + if (shellGetLine(scfg, line, sizeof(line))) { #if (SHELL_CMD_EXIT_ENABLED == TRUE) && !defined(_CHIBIOS_NIL_) - chprintf(chp, "\r\nlogout"); + chprintf(chp, SHELL_NEWLINE_STR); + chprintf(chp, "logout"); break; #else /* Putting a delay in order to avoid an endless loop trying to read @@ -148,7 +352,7 @@ THD_FUNCTION(shellThread, p) { n = 0; while ((lp = parse_arguments(NULL, &tokp)) != NULL) { if (n >= SHELL_MAX_ARGUMENTS) { - chprintf(chp, "too many arguments\r\n"); + chprintf(chp, "too many arguments"SHELL_NEWLINE_STR); cmd = NULL; break; } @@ -158,19 +362,19 @@ THD_FUNCTION(shellThread, p) { if (cmd != NULL) { if (strcmp(cmd, "help") == 0) { if (n > 0) { - usage(chp, "help"); + shellUsage(chp, "help"); continue; } chprintf(chp, "Commands: help "); list_commands(chp, shell_local_commands); if (scp != NULL) list_commands(chp, scp); - chprintf(chp, "\r\n"); + chprintf(chp, SHELL_NEWLINE_STR); } else if (cmdexec(shell_local_commands, chp, cmd, n, args) && ((scp == NULL) || cmdexec(scp, chp, cmd, n, args))) { chprintf(chp, "%s", cmd); - chprintf(chp, " ?\r\n"); + chprintf(chp, " ?"SHELL_NEWLINE_STR); } } } @@ -217,7 +421,7 @@ void shellExit(msg_t msg) { * - Other values below 0x20 are not echoed. * . * - * @param[in] chp pointer to a @p BaseSequentialStream object + * @param[in] scfg pointer to a @p ShellConfig object * @param[in] line pointer to the line buffer * @param[in] size buffer maximum length * @return The operation status. @@ -226,14 +430,66 @@ void shellExit(msg_t msg) { * * @api */ -bool shellGetLine(BaseSequentialStream *chp, char *line, unsigned size) { +bool shellGetLine(ShellConfig *scfg, char *line, unsigned size) { char *p = line; + BaseSequentialStream *chp = scfg->sc_channel; + bool escape = false; + bool bracket = false; + +#if (SHELL_USE_HISTORY == TRUE) + ShellHistory *shp = scfg->sc_history; +#endif while (true) { char c; if (streamRead(chp, (uint8_t *)&c, 1) == 0) return true; +#if (SHELL_USE_ESC_SEQ == TRUE) + if (c == 27) { + escape = true; + continue; + } + if (escape) { + escape = false; + if (c == '[') { + escape = true; + bracket = true; + continue; + } + if (bracket) { + bracket = false; +#if (SHELL_USE_HISTORY == TRUE) + if (c == 'A') { + int len = get_history(shp, line, SHELL_HIST_DIR_BK); + + if (len > 0) { + _shell_reset_cur(chp); + _shell_clr_line(chp); + chprintf(chp, "%s", line); + p = line + len; + } + continue; + } + if (c == 'B') { + int len = get_history(shp, line, SHELL_HIST_DIR_FW); + + if (len == 0) + *line = 0; + + if (len >= 0) { + _shell_reset_cur(chp); + _shell_clr_line(chp); + chprintf(chp, "%s", line); + p = line + len; + } + continue; + } +#endif + } + continue; + } +#endif #if (SHELL_CMD_EXIT_ENABLED == TRUE) && !defined(_CHIBIOS_NIL_) if (c == 4) { chprintf(chp, "^D"); @@ -250,10 +506,57 @@ bool shellGetLine(BaseSequentialStream *chp, char *line, unsigned size) { continue; } if (c == '\r') { - chprintf(chp, "\r\n"); + chprintf(chp, SHELL_NEWLINE_STR); +#if (SHELL_USE_HISTORY == TRUE) + if (shp != NULL) { + save_history(shp, line, p - line); + } +#endif *p = 0; return false; } +#if (SHELL_USE_COMPLETION == TRUE) + if (c == '\t') { + if (p < line + size - 1) { + *p = 0; + + get_completions(scfg, line); + int len = process_completions(scfg, line, p - line, size); + if (len > 0) { + write_completions(scfg, line, p - line); + p = line + len; + } + } + continue; + } +#endif +#if (SHELL_USE_HISTORY == TRUE) + if (c == 14) { + int len = get_history(shp, line, SHELL_HIST_DIR_FW); + + if (len == 0) + *line = 0; + + if (len >= 0) { + _shell_reset_cur(chp); + _shell_clr_line(chp); + chprintf(chp, "%s", line); + p = line + len; + } + continue; + } + if (c == 16) { + int len = get_history(shp, line, SHELL_HIST_DIR_BK); + + if (len > 0) { + _shell_reset_cur(chp); + _shell_clr_line(chp); + chprintf(chp, "%s", line); + p = line + len; + } + continue; + } +#endif if (c < 0x20) continue; if (p < line + size - 1) { diff --git a/os/various/shell/shell.h b/os/various/shell/shell.h index 5d026dd05..cf52bce9a 100644 --- a/os/various/shell/shell.h +++ b/os/various/shell/shell.h @@ -29,6 +29,22 @@ /* Module constants. */ /*===========================================================================*/ +/** + * @brief Prompt string + */ +#define SHELL_PROMPT_STR "ch> " + +/** + * @brief Newline string + */ +#define SHELL_NEWLINE_STR "\r\n" + +/** + * @brief Shell History Constants + */ +#define SHELL_HIST_DIR_BK 0 +#define SHELL_HIST_DIR_FW 1 + /*===========================================================================*/ /* Module pre-compile time settings. */ /*===========================================================================*/ @@ -47,6 +63,41 @@ #define SHELL_MAX_ARGUMENTS 4 #endif +/** + * @brief Shell maximum command history. + */ +#if !defined(SHELL_MAX_HIST_BUFF) || defined(__DOXYGEN__) +#define SHELL_MAX_HIST_BUFF 8 * SHELL_MAX_LINE_LENGTH +#endif + +/** + * @brief Enable shell command history + */ +#if !defined(SHELL_USE_HISTORY) || defined(__DOXYGEN__) +#define SHELL_USE_HISTORY TRUE +#endif + +/** + * @brief Enable shell command completion + */ +#if !defined(SHELL_USE_COMPLETION) || defined(__DOXYGEN__) +#define SHELL_USE_COMPLETION TRUE +#endif + +/** + * @brief Shell Maximum Completions (Set to max commands with common prefix) + */ +#if !defined(SHELL_MAX_COMPLETIONS) || defined(__DOXYGEN__) +#define SHELL_MAX_COMPLETIONS 8 +#endif + +/** + * @brief Enable shell escape sequence processing + */ +#if !defined(SHELL_USE_ESC_SEQ) || defined(__DOXYGEN__) +#define SHELL_USE_ESC_SEQ TRUE +#endif + /*===========================================================================*/ /* Derived constants and error checks. */ /*===========================================================================*/ @@ -68,6 +119,22 @@ typedef struct { shellcmd_t sc_function; /**< @brief Command function. */ } ShellCommand; +/** + * @brief Shell history type. + */ +typedef struct { + char *sh_buffer; /**< @brief Buffer to store command + history. */ + const int sh_size; /**< @brief Shell history buffer + size. */ + int sh_beg; /**< @brief Beginning command index + in buffer. */ + int sh_end; /**< @brief Ending command index + in buffer. */ + int sh_cur; /**< @brief Currently selected + command in buffer. */ +} ShellHistory; + /** * @brief Shell descriptor type. */ @@ -76,12 +143,52 @@ typedef struct { to the shell. */ const ShellCommand *sc_commands; /**< @brief Shell extra commands table. */ +#if (SHELL_USE_HISTORY == TRUE) || defined(__DOXYGEN__) + ShellHistory *sc_history; /**< @brief Shell command history + buffer. */ +#endif +#if (SHELL_USE_COMPLETION == TRUE) || defined(__DOXYGEN__) + char **sc_completion; /**< @brief Shell command completion + buffer. */ +#endif } ShellConfig; /*===========================================================================*/ /* Module macros. */ /*===========================================================================*/ +/** + * @brief Send escape codes to move cursor to the beginning of the line + * + * @param[in] stream pointer to a @p BaseSequentialStream object + * + * @notapi + */ +#define _shell_reset_cur(stream) chprintf(stream, "\033[%dD\033[%dC", \ + SHELL_MAX_LINE_LENGTH + \ + strlen(SHELL_PROMPT_STR) + 2, \ + strlen(SHELL_PROMPT_STR)) + +/** + * @brief Send escape codes to clear the rest of the line + * + * @param[in] stream pointer to a @p BaseSequentialStream object + * + * @notapi + */ +#define _shell_clr_line(stream) chprintf(stream, "\033[K") + +/** + * @brief Prints out usage message + * + * @param[in] stream pointer to a @p BaseSequentialStream object + * @param[in] message pointer to message string + * + * @api + */ +#define shellUsage(stream, message) \ + chprintf(stream, "Usage: %s"SHELL_NEWLINE_STR, message) + /*===========================================================================*/ /* External declarations. */ /*===========================================================================*/ @@ -96,7 +203,7 @@ extern "C" { void shellInit(void); THD_FUNCTION(shellThread, p); void shellExit(msg_t msg); - bool shellGetLine(BaseSequentialStream *chp, char *line, unsigned size); + bool shellGetLine(ShellConfig *scfg, char *line, unsigned size); #ifdef __cplusplus } #endif diff --git a/os/various/shell/shell_cmd.c b/os/various/shell/shell_cmd.c index 7f31dc87f..3b40f1bb8 100644 --- a/os/various/shell/shell_cmd.c +++ b/os/various/shell/shell_cmd.c @@ -54,18 +54,13 @@ /* Module local functions. */ /*===========================================================================*/ -static void usage(BaseSequentialStream *chp, char *p) { - - chprintf(chp, "Usage: %s\r\n", p); -} - #if ((SHELL_CMD_EXIT_ENABLED == TRUE) && !defined(_CHIBIOS_NIL_)) || \ defined(__DOXYGEN__) static void cmd_exit(BaseSequentialStream *chp, int argc, char *argv[]) { (void)argv; if (argc > 0) { - usage(chp, "exit"); + shellUsage(chp, "exit"); return; } @@ -78,30 +73,30 @@ static void cmd_info(BaseSequentialStream *chp, int argc, char *argv[]) { (void)argv; if (argc > 0) { - usage(chp, "info"); + shellUsage(chp, "info"); return; } - chprintf(chp, "Kernel: %s\r\n", CH_KERNEL_VERSION); + chprintf(chp, "Kernel: %s"SHELL_NEWLINE_STR, CH_KERNEL_VERSION); #ifdef PORT_COMPILER_NAME - chprintf(chp, "Compiler: %s\r\n", PORT_COMPILER_NAME); + chprintf(chp, "Compiler: %s"SHELL_NEWLINE_STR, PORT_COMPILER_NAME); #endif - chprintf(chp, "Architecture: %s\r\n", PORT_ARCHITECTURE_NAME); + chprintf(chp, "Architecture: %s"SHELL_NEWLINE_STR, PORT_ARCHITECTURE_NAME); #ifdef PORT_CORE_VARIANT_NAME - chprintf(chp, "Core Variant: %s\r\n", PORT_CORE_VARIANT_NAME); + chprintf(chp, "Core Variant: %s"SHELL_NEWLINE_STR, PORT_CORE_VARIANT_NAME); #endif #ifdef PORT_INFO - chprintf(chp, "Port Info: %s\r\n", PORT_INFO); + chprintf(chp, "Port Info: %s"SHELL_NEWLINE_STR, PORT_INFO); #endif #ifdef PLATFORM_NAME - chprintf(chp, "Platform: %s\r\n", PLATFORM_NAME); + chprintf(chp, "Platform: %s"SHELL_NEWLINE_STR, PLATFORM_NAME); #endif #ifdef BOARD_NAME - chprintf(chp, "Board: %s\r\n", BOARD_NAME); + chprintf(chp, "Board: %s"SHELL_NEWLINE_STR, BOARD_NAME); #endif #ifdef __DATE__ #ifdef __TIME__ - chprintf(chp, "Build time: %s%s%s\r\n", __DATE__, " - ", __TIME__); + chprintf(chp, "Build time: %s%s%s"SHELL_NEWLINE_STR, __DATE__, " - ", __TIME__); #endif #endif } @@ -112,10 +107,10 @@ static void cmd_echo(BaseSequentialStream *chp, int argc, char *argv[]) { (void)argv; if (argc != 1) { - usage(chp, "echo \"message\""); + shellUsage(chp, "echo \"message\""); return; } - chprintf(chp, "%s\r\n", argv[0]); + chprintf(chp, "%s"SHELL_NEWLINE_STR, argv[0]); } #endif @@ -124,10 +119,10 @@ static void cmd_systime(BaseSequentialStream *chp, int argc, char *argv[]) { (void)argv; if (argc > 0) { - usage(chp, "systime"); + shellUsage(chp, "systime"); return; } - chprintf(chp, "%lu\r\n", (unsigned long)chVTGetSystemTime()); + chprintf(chp, "%lu"SHELL_NEWLINE_STR, (unsigned long)chVTGetSystemTime()); } #endif @@ -137,14 +132,14 @@ static void cmd_mem(BaseSequentialStream *chp, int argc, char *argv[]) { (void)argv; if (argc > 0) { - chprintf(chp, "Usage: mem\r\n"); + shellUsage(chp, "mem"); return; } n = chHeapStatus(NULL, &total, &largest); - chprintf(chp, "core free memory : %u bytes\r\n", chCoreGetStatusX()); - chprintf(chp, "heap fragments : %u\r\n", n); - chprintf(chp, "heap free total : %u bytes\r\n", total); - chprintf(chp, "heap free largest: %u bytes\r\n", largest); + chprintf(chp, "core free memory : %u bytes"SHELL_NEWLINE_STR, chCoreGetStatusX()); + chprintf(chp, "heap fragments : %u"SHELL_NEWLINE_STR, n); + chprintf(chp, "heap free total : %u bytes"SHELL_NEWLINE_STR, total); + chprintf(chp, "heap free largest: %u bytes"SHELL_NEWLINE_STR, largest); } #endif @@ -155,10 +150,10 @@ static void cmd_threads(BaseSequentialStream *chp, int argc, char *argv[]) { (void)argv; if (argc > 0) { - chprintf(chp, "Usage: threads\r\n"); + shellUsage(chp, "threads"); return; } - chprintf(chp, "stklimit stack addr refs prio state name\r\n"); + chprintf(chp, "stklimit stack addr refs prio state name\r\n"SHELL_NEWLINE_STR); tp = chRegFirstThread(); do { #if CH_DBG_ENABLE_STACK_CHECK == TRUE @@ -166,7 +161,7 @@ static void cmd_threads(BaseSequentialStream *chp, int argc, char *argv[]) { #else uint32_t stklimit = 0U; #endif - chprintf(chp, "%08lx %08lx %08lx %4lu %4lu %9s %12s\r\n", + chprintf(chp, "%08lx %08lx %08lx %4lu %4lu %9s %12s"SHELL_NEWLINE_STR, stklimit, (uint32_t)tp->ctx.sp, (uint32_t)tp, (uint32_t)tp->refs - 1, (uint32_t)tp->prio, states[tp->state], tp->name == NULL ? "" : tp->name); @@ -181,14 +176,14 @@ static void cmd_test(BaseSequentialStream *chp, int argc, char *argv[]) { (void)argv; if (argc > 0) { - chprintf(chp, "Usage: test\r\n"); + shellUsage(chp, "test"); return; } tp = chThdCreateFromHeap(NULL, SHELL_CMD_TEST_WA_SIZE, "test", chThdGetPriorityX(), (tfunc_t)test_execute, chp); if (tp == NULL) { - chprintf(chp, "out of memory\r\n"); + chprintf(chp, "out of memory"SHELL_NEWLINE_STR); return; } chThdWait(tp);