From 62134a0419727a17d256d51ed2d89d1a5bed5733 Mon Sep 17 00:00:00 2001 From: mst Date: Sun, 5 Aug 2018 16:05:59 -0600 Subject: [PATCH] apps/system/cle: Usage improvements including command line history (taken from readline) and support for addition control characters. --- system/cle/Kconfig | 44 +++++++ system/cle/cle.c | 250 +++++++++++++++++++++++++++++++++++++++- system/readline/Kconfig | 6 +- 3 files changed, 294 insertions(+), 6 deletions(-) diff --git a/system/cle/Kconfig b/system/cle/Kconfig index 4abfc7eab..92054e8b2 100644 --- a/system/cle/Kconfig +++ b/system/cle/Kconfig @@ -21,6 +21,50 @@ menuconfig SYSTEM_CLE if SYSTEM_CLE +config SYSTEM_CLE_CMD_HISTORY + bool "Command line history" + default n + ---help--- + Build in support for Unix-style command history using up and down + arrow keys. This feature was originally provided by Nghia Ho. + + NOTE: Command line history is kept in an in-memory array and is + shared. In the FLAT or PROTECTED builds, this history is shared by + all threads; in the KERNEL build, the command line history is shared + by all threads in the process. This means that in a FLAT build, for + example, a built-in application started from NSH will have the same + history as does NSH if it also uses the CLE. This also means + that different NSH sessions on serial, USB, or Telnet will also + share the same history array. + + In a KERNEL build, each process will have a separately allocated + history array so the issue is lessened. History would still be + shared amount pthreads within the same process, however. + +if SYSTEM_CLE_CMD_HISTORY + +config SYSTEM_CLE_CMD_HISTORY_LINELEN + int "Command line history length" + default 64 if DEFAULT_SMALL + default 80 if !DEFAULT_SMALL + ---help--- + The maximum length of one command line in the in-memory array. The + total memory usage for the command line array will be + SYSTEM_CLE_CMD_HISTORY_LINELEN x SYSTEM_CLE_CMD_HISTORY_LEN. Default: + 64/80 + +config SYSTEM_CLE_CMD_HISTORY_LEN + int "Command line history records" + default 4 if DEFAULT_SMALL + default 16 if !DEFAULT_SMALL + ---help--- + The number of lines of history that will be buffered in the in- + memory array. The total memory usage for the command line array + will be SYSTEM_CLE_CMD_HISTORY_LINELEN x SYSTEM_CLE_CMD_HISTORY_LEN. + Default: 16 + +endif # SYSTEM_CLE_CMD_HISTORY + config SYSTEM_CLE_DEBUGLEVEL int "Debug level" default 0 diff --git a/system/cle/cle.c b/system/cle/cle.c index 8626093b7..d1871863b 100644 --- a/system/cle/cle.c +++ b/system/cle/cle.c @@ -1,7 +1,7 @@ /**************************************************************************** * apps/system/cle/cle.c * - * Copyright (C) 2014 Gregory Nutt. All rights reserved. + * Copyright (C) 2014, 2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -134,6 +134,30 @@ # endif #endif +#ifdef CONFIG_SYSTEM_CLE_CMD_HISTORY +/* Command history + * + * g_cmd_history[][] Circular buffer + * g_cmd_history_head Head of the circular buffer, most recent + * command + * g_cmd_history_steps_from_head Offset from head + * g_cmd_history_len Number of elements in the circular buffer + * + * REVISIT: These globals will *not* work in an environment where there + * are multiple copies if the NSH shell! Use of global variables is not + * thread safe! These settings should, at least, be semaphore protected so + * that the integrity of the data is assured, even though commands from + * different sessions may be intermixed. + */ + +static char g_cmd_history[CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN] + [CONFIG_SYSTEM_CLE_CMD_HISTORY_LINELEN]; +static int g_cmd_history_head = -1; +static int g_cmd_history_steps_from_head = 1; +static int g_cmd_history_len = 0; + +#endif /* CONFIG_SYSTEM_CLE_CMD_HISTORY */ + /**************************************************************************** * Private Types ****************************************************************************/ @@ -148,8 +172,10 @@ enum cle_key_e KEY_RIGHT = CTRL('F'), /* Move right one character */ KEY_DELLEFT = CTRL('H'), /* Delete character, left (backspace) */ KEY_DELEOL = CTRL('K'), /* Delete to the end of the line */ + KEY_DN = CTRL('N'), /* Cursor down */ + KEY_UP = CTRL('P'), /* Cursor up */ KEY_DELLINE = CTRL('U'), /* Delete the entire line */ - KEY_QUOTE = '\\', /* The next character is quote (use literal value) */ + KEY_QUOTE = '\\' /* The next character is quote (use literal value) */ }; /* This structure describes the overall state of the editor */ @@ -250,8 +276,6 @@ static void cle_write(FAR struct cle_s *priv, FAR const char *buffer, ssize_t nwritten; uint16_t nremaining = buflen; - //cleinfo("buffer=%p buflen=%d\n", buffer, (int)buflen); - /* Loop until all bytes have been successfully written (or until a * unrecoverable error is encountered) */ @@ -749,14 +773,202 @@ static int cle_editloop(FAR struct cle_s *priv) /* Get the next character from the input */ +#if 1 /* Perhaps here should be a config switch */ + /* Simple decode of some VT100/xterm codes: left/right, up/dn, + * home/end, del + */ + + { + char state = 0; + + /* loop till we have a ch */ + + for (; ; ) + { + ch = cle_getch(priv); + if (ch < 0) + { + return -EIO; + } + else if (state != 0) + { + if (state == (char)1) /* Got ESC */ + { + if (ch == '[' || ch == 'O') + { + state = ch; + } + else + { + break; /* break the for loop */ + } + } + + else if (state == '[') + { + /* Got ESC[ */ + + switch (ch) + { + case '3': /* ESC[3~ = DEL */ + { + state = ch; + } + continue; + + case 'A': + { + ch = KEY_UP; + } + break; + + case 'B': + { + ch = KEY_DN; + } + break; + + case 'C': + { + ch = KEY_RIGHT; + } + break; + + case 'D': + { + ch = KEY_LEFT; + } + break; + + case 'F': + { + ch = KEY_ENDLINE; + } + break; + + case 'H': + { + ch = KEY_BEGINLINE; + } + break; + + default: + break; + } + break; /* Break the 'for' loop */ + } + else if (state == 'O') + { + /* got ESCO */ + + if (ch=='F') + { + ch = KEY_ENDLINE; + } + break; /* Break the 'for' loop */ + } + else if (state == '3') + { + if (ch == '~') + { + ch = KEY_DEL; + } + break; /* Break the 'for' loop */ + } + else + { + break; /* Break the 'for' loop */ + } + } + else if (ch == ASCII_ESC) + { + ++state; + } + else + { + break; /* Break the 'for' loop, use the char */ + } + } + } + +#else ch = cle_getch(priv); if (ch < 0) { return -EIO; } +#endif + /* Then handle the character. */ +#ifdef CONFIG_SYSTEM_CLE_CMD_HISTORY + if (g_cmd_history_len > 0) + { + int i = 1; + + switch (ch) + { + case KEY_UP: + /* Go to the past command in history */ + + g_cmd_history_steps_from_head--; + + if (-g_cmd_history_steps_from_head >= g_cmd_history_len) + { + g_cmd_history_steps_from_head = -(g_cmd_history_len - 1); + } + break; + + case KEY_DN: + /* Go to the recent command in history */ + + g_cmd_history_steps_from_head++; + + if (g_cmd_history_steps_from_head > 1) + { + g_cmd_history_steps_from_head = 1; + } + break; + + default: + i = 0; + break; + } + + if (i != 0) + { + priv->nchars = 0; + priv->curpos = 0; + + if (g_cmd_history_steps_from_head != 1) + { + int idx = g_cmd_history_head + + g_cmd_history_steps_from_head; + + /* Circular buffer wrap around */ + + if (idx < 0) + { + idx = idx + CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN; + } + else if (idx >= CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN) + { + idx = idx - CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN; + } + + for (i = 0; g_cmd_history[idx][i] != '\0'; i++) + { + cle_insertch(priv, g_cmd_history[idx][i]); + } + + priv->curpos = priv->nchars; + } + continue; + } + } +#endif /* CONFIG_SYSTEM_CLE_CMD_HISTORY */ + switch (ch) { case KEY_BEGINLINE: /* Move cursor to start of current line */ @@ -962,5 +1174,35 @@ int cle(FAR char *line, uint16_t linelen, FILE *instream, FILE *outstream) /* Make sure that the line is NUL terminated */ line[priv.nchars] = '\0'; + +#ifdef CONFIG_SYSTEM_CLE_CMD_HISTORY + /* Save history of command, only if there was something typed besides + * return character. + */ + + if (priv.nchars > 1) + { + int i; + + g_cmd_history_head = + (g_cmd_history_head + 1) % CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN; + + for (i = 0; + (i < priv.nchars - 1) && i < (CONFIG_SYSTEM_CLE_CMD_HISTORY_LINELEN - 1); + i++) + { + g_cmd_history[g_cmd_history_head][i] = line[i]; + } + + g_cmd_history[g_cmd_history_head][i] = '\0'; + g_cmd_history_steps_from_head = 1; + + if (g_cmd_history_len < CONFIG_SYSTEM_CLE_CMD_HISTORY_LEN) + { + g_cmd_history_len++; + } + } +#endif /* CONFIG_SYSTEM_CLE_CMD_HISTORY */ + return ret; } diff --git a/system/readline/Kconfig b/system/readline/Kconfig index d382a7b67..d2d4bf59a 100644 --- a/system/readline/Kconfig +++ b/system/readline/Kconfig @@ -71,7 +71,8 @@ config READLINE_CMD_HISTORY share the same history array. In a KERNEL build, each process will have a separately allocated - history array so the issue is lessened. + history array so the issue is lessened. History would still be + shared amount pthreads within the same process, however. if READLINE_CMD_HISTORY @@ -82,7 +83,8 @@ config READLINE_CMD_HISTORY_LINELEN ---help--- The maximum length of one command line in the in-memory array. The total memory usage for the command line array will be - READLINE_CMD_HISTORY_LINELEN x READLINE_CMD_HISTORY_LEN. Default: 64/80 + READLINE_CMD_HISTORY_LINELEN x READLINE_CMD_HISTORY_LEN. Default: + 64/80 config READLINE_CMD_HISTORY_LEN int "Command line history records"