[U-Boot-Users] [PATCH 10/11] Add command history and editing

The patch below adds command history and editing. This patch was originally posted to the list by James MacAulay james.macaulay@amirix.com. I recall a poll on whether others found this patch useful or not but I do not remember any resolution to the question. I have found this feature extremely useful and I use this feature constantly, so I 'gitified' the patch and posted it here.
diff --git a/CHANGELOG b/CHANGELOG index c774dd0..a560990 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,10 @@ Changes since U-Boot 1.1.4: ======================================================================
+* Incorporate patch to support command history and command editing + posted by uboot-users by James MacAulay james.macaulay@amirix.com + Patch by James MacAulay + * Coding Style cleanup
* Write RTC seconds first to maintain settings integrity per diff --git a/CREDITS b/CREDITS index f91fa3e..7f65ed7 100644 --- a/CREDITS +++ b/CREDITS @@ -279,6 +279,12 @@ N: Raymond Lo E: lo@routefree.com D: Support for DOS partitions
+N: James MacAulay +E: james.macaulay@amirix.com +D: Support for command history. +D: Support for command buffer editing. +W: http://www.amirix.com + N: Dan Malek E: dan@embeddedalley.com D: FADSROM, the grandfather of all of this @@ -455,4 +461,5 @@ W: www.elinos.com N: James MacAulay E: james.macaulay@amirix.com D: Suppport for Amirix AP1000 +D: Support for command history and editing W: www.amirix.com diff --git a/README b/README index 3ffef62..3a2a5c7 100644 --- a/README +++ b/README @@ -2238,7 +2238,48 @@ Low Level (hardware related) configurati some other boot loader or by a debugger which performs these intializations itself.
- +- CONFIG_COMMAND_HISTORY + Adds basic command history to the shell. The command history + is accessed by using the up and down arrows. The size of the + command history defaults to 10, but can be changed by defining + CONFIG_COMMAND_HISTORY_SIZE to be another value, for example: + + #define CONFIG_COMMAND_HISTORY_SIZE 20 + + Because the current command in the buffer is considered part + of the history, (CONFIG_COMMAND_HISTORY_SIZE - 1) commands are + saved for retrieval. + + The command history code supports the 4 common escape sequences + implicitly (0x1b + 0x5b), (0x1b + 0x9b), (0x1b + 0x4f), and + (0x1b + 0x8f). + + Following the escape sequence, the assumed characters for the + up arrow is 0x41 and for the down arrow 0x42. If these values + differ for your terminal, the character value for each of the + arrows can be redefined like so: + + #define CONFIG_UP_ARROW PROPER_UP_ARROW_VALUE + #define CONFIG_DOWN_ARROW PROPER_DOWN_ARROW_VALUE + +- CONFIG_COMMAND_EDIT + Adds basic command buffer editing to the shell. The left and + right arrows are used to move the cursor through the current + command buffer. Ctrl-A moves the prompt to the start of the + command while Ctrl-E moves the prompt to the end of the command. + + The command history code supports the 4 common escape sequences + implicitly (0x1b + 0x5b), (0x1b + 0x9b), (0x1b + 0x4f), and + (0x1b + 0x8f). + + Following the escape sequence, the assumed characters for the + right arrow is 0x43 and for the left arrow 0x44. If these values + differ for your terminal, the character value for each of the + arrows can be redefined like so: + + #define CONFIG_RIGHT_ARROW PROPER_RIGHT_ARROW_VALUE + #define CONFIG_LEFT_ARROW PROPER_LEFT_ARROW_VALUE + Building the Software: ======================
diff --git a/common/main.c b/common/main.c index 758ef8d..01a370e 100644 --- a/common/main.c +++ b/common/main.c @@ -26,8 +26,8 @@ #include <common.h> #include <watchdog.h> #include <command.h> -#ifdef CONFIG_MODEM_SUPPORT -#include <malloc.h> /* for free() prototype */ +#if defined(CONFIG_MODEM_SUPPORT) || defined(CONFIG_COMMAND_HISTORY) +#include <malloc.h> /* for free() prototype (CONFIG_MODEM_SUPPORT) */ #endif
#ifdef CFG_HUSH_PARSER @@ -518,6 +518,271 @@ #endif
/****************************************************************************/
+#ifdef CONFIG_COMMAND_HISTORY + +/* Default to a history size of 10 if no other value specified */ +#ifndef CONFIG_COMMAND_HISTORY_SIZE +#define CONFIG_COMMAND_HISTORY_SIZE 10 +#endif + +#define NEXT_INDEX(index) ((index + 1) % CONFIG_COMMAND_HISTORY_SIZE) +#define PREV_INDEX(index) ((index + CONFIG_COMMAND_HISTORY_SIZE - 1) % CONFIG_COMMAND_HISTORY_SIZE) + +/* The index of the current command in the history ring buffer */ +static int cmd_buffer_index = 0; + +/* + * The original index of the current command line in the history ring buffer + * before the user begins scrolling through the history. + */ +static int cmd_buffer_start_index; + +/* How far through the history buffer the user has scrolled. */ +static int cmd_buffer_peek_offset; + +/* The command history ring buffer */ +static char* cmd_buffer[CONFIG_COMMAND_HISTORY_SIZE] = { NULL }; +#endif + +#if defined(CONFIG_COMMAND_HISTORY) || defined(CONFIG_COMMAND_EDIT) +#define BEEP 0x7 + +/* Redefine any necessary arrow keys */ +#ifdef CONFIG_UP_ARROW +#define UP_ARROW_CHAR CONFIG_UP_ARROW +#else +#define UP_ARROW_CHAR 0x41 +#endif + +#ifdef CONFIG_DOWN_ARROW +#define DOWN_ARROW_CHAR CONFIG_DOWN_ARROW +#else +#define DOWN_ARROW_CHAR 0x42 +#endif + +#ifdef CONFIG_RIGHT_ARROW +#define RIGHT_ARROW_CHAR CONFIG_RIGHT_ARROW +#else +#define RIGHT_ARROW_CHAR 0x43 +#endif + +#ifdef CONFIG_LEFT_ARROW +#define LEFT_ARROW_CHAR CONFIG_LEFT_ARROW +#else +#define LEFT_ARROW_CHAR 0x44 +#endif + +/* When a new prompt is displayed, clear out edit and/or history data. */ +static void cmd_start_new_line(void){ + console_buffer[0] = '\0'; +#ifdef CONFIG_COMMAND_HISTORY + cmd_buffer_start_index = cmd_buffer_index; + cmd_buffer_peek_offset = 0; +#endif +} +#endif + +#ifdef CONFIG_COMMAND_HISTORY +/* Re-print out the characters to get the cursor to the end of the line */ +static void cmd_cursor_to_EOL(char** pp, int n){ + while(*pp != (console_buffer + n)){ + putc(**pp); + (*pp)++; + } +} + +/* Save a command by freeing and allocating a new string in the ring buffer */ +static void save_cmd(char* cmd, int history_index){ + if(cmd_buffer[history_index] != NULL){ + free(cmd_buffer[history_index]); + } + + if((cmd_buffer[history_index] = malloc(strlen(cmd) + 1)) != NULL){ + strcpy(cmd_buffer[history_index], cmd); + } +} + +/* + * When a command is executed, save the current command buffer and advance the + * ring buffer. + */ +static void cmd_executed(char** p, int n){ + cmd_cursor_to_EOL(p, n); + console_buffer[n] = '\0'; + save_cmd(console_buffer, cmd_buffer_start_index); + cmd_buffer_index = NEXT_INDEX(cmd_buffer_start_index); +} + +/* + * Display the proper number of erase sequences. + * Assumes that the cursor is at the EOL. + */ +static void erase_line(char** pp, int* n){ + int ii; + + for(ii = 0; ii < *n;ii++){ + puts (erase_seq); + } + + *pp = console_buffer; + console_buffer[0] = '\0'; + *n = 0; +} + +/* + * Display the command history buffer that is currently selected. + * Erase the line, then reset pp, col, and n, cursor moves to EOL. + */ +static void cmd_history_display(int* col, int plen, char** pp, int* n){ + int cmd_length; + + erase_line(pp, n); + cmd_length = strlen(cmd_buffer[cmd_buffer_index]); + + puts(cmd_buffer[cmd_buffer_index]); + strcpy(console_buffer, cmd_buffer[cmd_buffer_index]); + + *pp = console_buffer + cmd_length; + *col = cmd_length + plen; + *n = cmd_length; +} + +/* Move up on in the history if possible. */ +static void cmd_history_up(int* col, int plen, char** pp, int* n){ + if((cmd_buffer_peek_offset >= (CONFIG_COMMAND_HISTORY_SIZE - 1)) || + (cmd_buffer[PREV_INDEX(cmd_buffer_index)] == NULL)) + { + putc(BEEP); + } + else{ + /* save users command line only */ + if(cmd_buffer_peek_offset == 0){ + save_cmd(console_buffer, cmd_buffer_index); + } + + cmd_buffer_peek_offset++; + cmd_cursor_to_EOL(pp, *n); + + cmd_buffer_index = PREV_INDEX(cmd_buffer_index); + cmd_history_display(col, plen, pp, n); + } +} + +/* Move down on in the history if possible. */ +static void cmd_history_down(int* col, int plen, char** pp, int* n){ + if(cmd_buffer_peek_offset == 0){ + putc(BEEP); + } + else{ + cmd_buffer_peek_offset--; + cmd_cursor_to_EOL(pp, *n); + cmd_buffer_index = NEXT_INDEX(cmd_buffer_index); + + cmd_history_display(col, plen, pp, n); + } +} +#endif + +#ifdef CONFIG_COMMAND_EDIT +/* Insert a character at the current cursor location and increment cursor. */ +static void cmd_edit_insert_char(char** pp, int n, char c){ + int chars_to_move; + int ii; + + /* move chars along +1 will include the '/0' */ + chars_to_move = n - (*pp - console_buffer) + 1; + + for(ii = 0;ii < chars_to_move;ii++){ + console_buffer[n - ii + 1] = console_buffer[n - ii]; + } + + /* insert new char */ + **pp = c; + (*pp)++; + + /* redisplay any moved chars, exclude '/0' */ + for(ii = 0;ii < (chars_to_move - 1);ii++){ + putc((*pp)[ii]); + } + + /* put cursor back where user left it */ + for(ii = 0;ii < (chars_to_move - 1);ii++){ + putc('\b'); + } +} + +/* Remove a character at the current cursor location. */ +static void cmd_edit_remove_char(char** pp, int n){ + int chars_to_move; + int cursor_pos; + int ii; + + cursor_pos = *pp - console_buffer; + + /* move chars, include the '/0' */ + chars_to_move = n - cursor_pos; + + for(ii = 0;ii < chars_to_move;ii++){ + (*pp)[ii] = (*pp)[ii + 1]; + } + + /* move cursor */ + putc('\b'); + + /* redisplay any moved chars, exclude '/0' */ + for(ii = 0;ii < (chars_to_move - 1);ii++){ + putc((*pp)[ii]); + } + + /* clear spot where char used to be */ + putc(' '); + + /* put cursor back where it user left it */ + for(ii = 0;ii < chars_to_move;ii++){ + putc('\b'); + } +} + +/* Move cursor left one character if possible. */ +static void cmd_edit_left_arrow(char** pp){ + if(*pp > console_buffer){ + putc('\b'); + (*pp)--; + } + else{ + putc(BEEP); + } +} + +/* Move cursor right one character if possible. */ +static void cmd_edit_right_arrow(char** pp, int n){ + if((*pp - console_buffer) < n){ + putc(**pp); + (*pp)++; + } + else{ + putc(BEEP); + } +} + +/* Move cursor to start of the command buffer. */ +static void cmd_edit_home(char** pp){ + while(*pp > console_buffer){ + putc('\b'); + (*pp)--; + } +} + +/* Move cursor to end of the command buffer. */ +static void cmd_edit_end(char** pp, int n){ + while((*pp - console_buffer) < n){ + putc(**pp); + (*pp)++; + } +} + +#endif + /* * Prompt for input and read a line. * If CONFIG_BOOT_RETRY_TIME is defined and retry_time >= 0, @@ -534,6 +799,10 @@ int readline (const char *const prompt) int col; /* output column cnt */ char c;
+#if defined(CONFIG_COMMAND_HISTORY) || defined(CONFIG_COMMAND_EDIT) + cmd_start_new_line(); +#endif + /* print prompt */ if (prompt) { plen = strlen (prompt); @@ -564,17 +833,40 @@ #endif switch (c) { case '\r': /* Enter */ case '\n': +#if !defined (CONFIG_COMMAND_HISTORY) && !defined(CONFIG_COMMAND_EDIT) *p = '\0'; +#endif + +#ifdef CONFIG_COMMAND_HISTORY + /* save command in history */ + if(n != 0){ + cmd_executed(&p, n); + } +#endif puts ("\r\n"); return (p - console_buffer);
case '\0': /* nul */ continue;
- case 0x03: /* ^C - break */ - console_buffer[0] = '\0'; /* discard input */ +#ifdef CONFIG_COMMAND_EDIT + case 0x01: /* ^A - home */ + cmd_edit_home(&p); + continue; +#endif + case 0x03: /* ^C - break */ +#ifdef CONFIG_COMMAND_HISTORY + cmd_buffer_index = cmd_buffer_start_index; +#else + console_buffer[0] = '\0'; /* discard input */ +#endif return (-1);
+#ifdef CONFIG_COMMAND_EDIT + case 0x05: /* ^E - end */ + cmd_edit_end(&p, n); + continue; +#endif case 0x15: /* ^U - erase line */ while (col > plen) { puts (erase_seq); @@ -593,9 +885,60 @@ #endif
case 0x08: /* ^H - backspace */ case 0x7F: /* DEL - backspace */ +#ifdef CONFIG_COMMAND_EDIT + if(p > console_buffer){ + /* remove character before cursor */ + p--; + cmd_edit_remove_char(&p, n); + n--; + } + else{ + putc(BEEP); + } +#else p=delete_char(console_buffer, p, &col, &n, plen); +#endif continue;
+#if defined(CONFIG_COMMAND_HISTORY) || defined(CONFIG_COMMAND_EDIT) + /* escape char used for special characters */ + case 0x1b: + c = getc(); + /* accept any of the 4 possible escape sequences */ + if((c == 0x5b) || (c == 0x9b) || (c == 0x4f) || (c == 0x8f)){ + c = getc(); + switch(c){ + case UP_ARROW_CHAR:{ +#ifdef CONFIG_COMMAND_HISTORY + cmd_history_up(&col, plen, &p, &n); +#endif + continue; + } + case DOWN_ARROW_CHAR:{ +#ifdef CONFIG_COMMAND_HISTORY + cmd_history_down(&col, plen, &p, &n); +#endif + continue; + } + case RIGHT_ARROW_CHAR:{ +#ifdef CONFIG_COMMAND_EDIT + cmd_edit_right_arrow(&p, n); +#endif + continue; + } + case LEFT_ARROW_CHAR:{ +#ifdef CONFIG_COMMAND_EDIT + cmd_edit_left_arrow(&p); +#endif + continue; + } + default:{ + continue; + } + } + } + continue; +#endif default: /* * Must be a normal character then @@ -610,13 +953,24 @@ #ifdef CONFIG_AUTO_COMPLETE continue; } #endif +#ifdef CONFIG_COMMAND_EDIT + /* treat tabs as space characters */ + c = ' '; + ++col; + putc(c); +#else puts (tab_seq+(col&07)); col += 8 - (col&07); +#endif } else { ++col; /* echo input */ putc (c); } +#ifdef CONFIG_COMMAND_EDIT + cmd_edit_insert_char(&p, n, c); +#else *p++ = c; +#endif ++n; } else { /* Buffer full */ putc ('\a');
participants (1)
-
Keith J Outwater