The CommandProcessor is the interface to install a run-time menu into an embedded system.

Dependents:   A_CANAdapter USB2I2C

Revision:
14:7971c8bd3f11
Parent:
13:e1880be590c4
Child:
15:5f30da93e3e2
--- a/CommandProcessor.c	Wed Jun 15 12:50:56 2011 +0000
+++ b/CommandProcessor.c	Sat Oct 01 20:01:44 2011 +0000
@@ -8,7 +8,7 @@
 ///
 /// Even though it is a c interface, it is somewhat object oriented.
 ///
-/// @version 1.03
+/// @version 1.04
 ///
 /// @note Copyright &copr; 2011 by Smartware Computing, all rights reserved.
 ///     Individuals may use this application for evaluation or non-commercial
@@ -53,15 +53,17 @@
 static CMDLINK_T * head = NULL;
 
 static char *buffer;        // buffer space must be allocated based on the longest command
-
+static char *historyBuffer;		// keeps the history of commands for recall
+static int historyCount = 0;	// and the count of
+static int historyDepth = 0;
 static size_t longestCommand = 0;
 
 static struct {
-    CMD_T *SignOnBanner;
-    int showSignOnBanner;        // Shows the sign-on banner at startup
+	CMD_T *SignOnBanner;
+	int showSignOnBanner;		// Shows the sign-on banner at startup
     int caseinsensitive;    // FALSE=casesensitive, TRUE=insensitive
     int echo;               // TRUE=echo on, FALSE=echo off
-    int bufferSize;         // size of the buffer
+    int bufferSize;         // size of the command buffer
     int (*kbhit)(void);
     int (*getch)(void);
     int (*putch)(int ch);
@@ -70,8 +72,9 @@
 
 static INITRESULT_T CommandProcessor_Init(
     CMD_T *SignOnBanner,
-    CONFIG_T config,
+	CONFIG_T config,
     int maxCmdLen,
+	int historyCount,
     int (*kbhit)(void),
     int (*getch)(void),
     int (*putch)(int ch),
@@ -81,6 +84,8 @@
 static RUNRESULT_T CommandProcessor_Run(void);
 static RUNRESULT_T CommandProcessor_End(void);
 static RUNRESULT_T CommandProcessor_Echo(int echo);
+static void EraseChars(int keycount);
+static void EchoString(char * p);
 
 // helper functions
 static int myisprint(int c);
@@ -97,14 +102,15 @@
 };
 
 static RUNRESULT_T Help(char *p);
+static RUNRESULT_T History(char *p);
 static RUNRESULT_T Echo(char *p);
 static RUNRESULT_T Exit(char *p);
 //static RUNRESULT_T About(char *p);
 
 static CMD_T HelpMenu = {"Help", "Help or '?' shows this help, 'Help ?' shows more details.", Help, visible};
 static CMD_T QuestionMenu = {"?", "Shows this help, '? ?' shows more details.", Help, invisible};
+static CMD_T HistoryMenu = {"History", "Show command history", History, visible};
 static CMD_T EchoMenu = {"Echo", "Echo [1|on|0|off] turns echo on or off.", Echo, visible};
-//static CMD_T AboutMenu = {"About", "About this CommandProcessor", About, visible};
 static CMD_T ExitMenu = {"Exit", "Exits the program", Exit, visible};
 
 /// Gets a handle to the CommandProcessor
@@ -119,23 +125,24 @@
 }
 
 
-/// Information about this command processor
+/// History shows the command history
 ///
-/// @param p is a pointer to command line arguments, of which there is
-///        none for this function.
+/// @param p is a pointer to a string that is ignored
 /// @returns runok
 ///
-#if 0
-static RUNRESULT_T About(char *p) {
-    cfg.puts("\r\n About this CommandProcessor:\r\n"
-             "    This CommandProcessor provides an easy facility for creating a\r\n"
-             "      runtime interactive interpreter in an embedded system.\r\n"
-             "    Copyright (c) 2011 by Smartware Computing, all rights reserved.\r\n"
-             "    Author: David Smart, Smartware Computing\r\n"
-            );
-    return runok;
+static RUNRESULT_T History(char *p) {
+	int whereInHistory = 0;
+	char buf[100];
+
+	cfg.puts("");
+	for (whereInHistory = 0; whereInHistory < historyCount; whereInHistory++) {
+		sprintf(buf, "  %2i: %s", whereInHistory - historyCount, &historyBuffer[whereInHistory * cfg.bufferSize]);
+		cfg.puts(buf);
+	}
+	sprintf(buf, "  %2i: %s", 0, buffer);
+	cfg.puts(buf);
+	return runok;
 }
-#endif
 
 /// Turns command prompt echo on and off
 ///
@@ -197,15 +204,16 @@
                  "    * <esc> can be used to cancel a command.\r\n"
                  "    * <tab> can be used to complete the entry of a partial command.\r\n"
                  "");
-        cfg.puts("\r\n About this CommandProcessor:\r\n"
-                 "    This CommandProcessor provides an easy facility for creating an\r\n"
-                 "      interactive runtime interpreter in an embedded system.\r\n"
-                 "    Copyright (c) 2011 by Smartware Computing, all rights reserved.\r\n"
-                 "    Author: David Smart, Smartware Computing\r\n");
+		cfg.puts("\r\n About this CommandProcessor:\r\n"
+				 "    This CommandProcessor provides an easy facility for creating an\r\n"
+				 "      interactive runtime interpreter in an embedded system.\r\n"
+				 "    Copyright (c) 2011 by Smartware Computing, all rights reserved.\r\n"
+				 "    Author: David Smart, Smartware Computing\r\n");
     }
     return runok;
 }
 
+
 /// CommandMatches is the function that determines if the user is entering a valid
 /// command
 ///
@@ -227,6 +235,7 @@
     int compareLength;
     int foundCount = 0;
     CMDLINK_T *link = head;
+	char * alternateBuffer;
 
     if (strlen(buffer)) {  // simple sanity check
         // Try to process the buffer. A command could be "Help", or it could be "Test1 123 abc"
@@ -261,22 +270,17 @@
                 // If they type "He 1234 5678", we backup and rewrite as "Help 1234 5678"
                 int diff = strlen((*menu)->command) - compareLength;    // e.g. 5 - 3
 
-                if (diff > 0) {
-                    int back = 0;
-                    char *p;
-                    if (strlen(space))
-                        back = strlen(space) + 1;
-
-                    while (back--)
-                        cfg.putch(0x08);
-                    p = (*menu)->command + compareLength;
-                    while (*p)
-                        cfg.putch(*p++);
-                    cfg.putch(' ');
-                    p = space;
-                    while (*p)
-                        cfg.putch(*p++);
-                    //cfg.printf("%s %s", link->menu->command + compareLength, space);
+				// or if they entered it in a case that doesn't match the command exactly
+                if (diff > 0 || 0 != strncmp(buffer, (*menu)->command, compareLength)) {
+                    char *p = buffer;
+					alternateBuffer = (char *)malloc(cfg.bufferSize);
+					strcpy(alternateBuffer, (*menu)->command);
+					strcat(alternateBuffer, " ");
+					strcat(alternateBuffer, space);
+					EraseChars(strlen(buffer));
+					strcpy(buffer, alternateBuffer);
+					free(alternateBuffer);
+					EchoString(p);
                 }
             }
         }
@@ -284,6 +288,7 @@
     return foundCount;
 }
 
+
 /// Init is the first function to call to configure the CommandProcessor.
 ///
 /// This function has a number of parameters, which make the CommandProcessor 
@@ -309,27 +314,31 @@
     CMD_T (*SignOnBanner),
     CONFIG_T config,
     int maxCmdLen,
+	int numInHistory,
     int (*kbhit)(void),
     int (*getch)(void),
     int (*putch)(int ch),
     int (*puts)(const char * s)
 ) {
-    if (SignOnBanner) {
-        CommandProcessor.Add(SignOnBanner);
-        cfg.SignOnBanner = SignOnBanner;
-        cfg.showSignOnBanner = 1;
-    }
+	if (SignOnBanner) {
+		CommandProcessor.Add(SignOnBanner);
+		cfg.SignOnBanner = SignOnBanner;
+		cfg.showSignOnBanner = 1;
+	}
     if (maxCmdLen < 6)
         maxCmdLen = 6;
-    buffer = (char *)malloc(maxCmdLen+1);
+	buffer = (char *)malloc(maxCmdLen);			// users often error by one, so we'll be generous
+	historyDepth = numInHistory;
+	historyBuffer = (char *)malloc(historyDepth * maxCmdLen);
     cfg.bufferSize = maxCmdLen;
-    if (buffer) {
+    if (buffer && historyBuffer) {
         if (config & CFG_ENABLE_SYSTEM)
-        {
+		{
             CommandProcessor.Add(&QuestionMenu);
             CommandProcessor.Add(&HelpMenu);
+			CommandProcessor.Add(&HistoryMenu);
             CommandProcessor.Add(&EchoMenu);
-        }
+		}
         if (config & CFG_ENABLE_TERMINATE)
             CommandProcessor.Add(&ExitMenu);
         //if (addDefaultMenu & 0x0002)
@@ -345,6 +354,7 @@
         return initfailed;
 }
 
+
 /// Add a command to the CommandProcessor
 ///
 /// This adds a command to the CommandProcessor. A command has several components
@@ -381,17 +391,31 @@
         prev = ptr;
         ptr = ptr->next;
     }
-    if (prev == head) {
-        head = temp;
-        head->next = prev;
-    } else {
-        prev->next = temp;
-        prev = temp;
-        prev->next = ptr;
-    }
+	if (prev == head) {
+		head = temp;
+		head->next = prev;
+	} else {
+		prev->next = temp;
+		prev = temp;
+		prev->next = ptr;
+	}
     return addok;
 }
 
+static void EchoString(char *p) {
+	while (*p)
+		cfg.putch(*p++);
+}
+
+static void EraseChars(int keycount) {
+    while (keycount--) {
+        cfg.putch(0x08);    // <bs>
+        cfg.putch(' ');
+        cfg.putch(0x08);
+    }
+}
+
+
 /// Run the CommandProcessor
 ///
 /// This will peek to see if there is a keystroke ready. It will pull that into a
@@ -409,14 +433,17 @@
 RUNRESULT_T CommandProcessor_Run(void) {
     static int showPrompt = TRUE;
     static int keycount = 0;    // how full?
+	static int leadinChar = 0;
+	static int whereInHistory = 0;		// navigates history
+    int foundCount = 0;
     RUNRESULT_T val = runok;            // return true when happy, false to exit the prog
     CMD_T *cbk = NULL;
     char * params = NULL;
 
-    if (cfg.showSignOnBanner) {
-        cfg.SignOnBanner->callback("");
-        cfg.showSignOnBanner = 0;
-    }
+	if (cfg.showSignOnBanner) {
+		cfg.SignOnBanner->callback("");
+		cfg.showSignOnBanner = 0;
+	}
     if (showPrompt && cfg.echo) {
         cfg.putch('>');
         showPrompt = FALSE;
@@ -424,7 +451,55 @@
     if (cfg.kbhit()) {
         int c = cfg.getch();
 
+		if (leadinChar) {
         switch (c) {
+			case 0x50:	// down arrow - toward the newest (forward in time)
+				// if there is anything in the history, copy it out
+				if (historyCount && whereInHistory < historyCount) {
+					char *p;
+
+					EraseChars(keycount);
+					p = strcpy(buffer, &historyBuffer[whereInHistory * cfg.bufferSize]);
+					EchoString(p);
+					keycount = strlen(buffer);
+					whereInHistory++;
+				}
+				c = 0;
+				break;
+			case 0x48:	// up arrow - from newest to oldest (backward in time)
+				// same as escape
+				if (historyCount && --whereInHistory >= 0) {
+					char *p;
+
+					EraseChars(keycount);
+					p = strcpy(buffer, &historyBuffer[whereInHistory * cfg.bufferSize]);
+					EchoString(p);
+					keycount = strlen(buffer);
+					c = 0;
+				} else {
+					whereInHistory = 0;
+					c = 0x1B;
+				}
+				break;
+			default:
+				// ignore this char
+				c = 0;
+				break;
+			}
+			leadinChar = 0;
+		}
+		switch (c) {
+			case 0:
+				// null - do nothing
+				break;
+			case 0xE0:
+				// Lead-in char
+				// 0xE0 0x48 is up arrow
+				// 0xE0 0x50 is down arrow
+				// 0xE0 0x4B is left arrow
+				// 0xE0 0x4D is right arrow
+				leadinChar = 1;
+				break;
             case 0x09:    // <TAB> to request command completion
                 if (1 == CommandMatches(buffer, FALSE, &cbk, &params)) {
                     size_t n;
@@ -437,38 +512,41 @@
                         p = cbk->command + strlen(buffer);
                         mystrcat(buffer, p);
                         keycount = strlen(buffer);
-                        while (*p)
-                            cfg.putch(*p++);
-                        //cfg.printf("%s", p);
+                        EchoString(p);
+						//cfg.printf("%s", p);
                     }
                 }
                 break;
             case 0x1b:    // <ESC> to empty the command buffer
-                while (keycount--) {
-                    cfg.putch(0x08);    // <bs>
-                    cfg.putch(' ');
-                    cfg.putch(0x08);
-                }
+				EraseChars(keycount);
                 keycount = 0;
                 buffer[keycount] = '\0';
                 break;
             case '\x08':    // <bs>
                 if (keycount) {
                     buffer[--keycount] = '\0';
-                    cfg.putch(0x08);
-                    cfg.putch(' ');
-                    cfg.putch(0x08);
+                    EraseChars(1);
                 } else
                     cfg.putch(0x07);    // bell
                 break;
             case '\r':
-            case '\n': {
-                int foundCount = 0;
-
+            case '\n': 
                 if (strlen(buffer)) {
                     foundCount = CommandMatches(buffer, TRUE, &cbk, &params);
                     if (foundCount == 1) {
                         val = (*cbk->callback)(params);        // Execute the command
+						if (mystrnicmp(buffer, (const char *)&historyBuffer[(historyCount-1) * cfg.bufferSize], strlen(&historyBuffer[(historyCount-1) * cfg.bufferSize])) != 0) {
+							// not repeating the last command, so enter into the history
+							if (historyCount == historyDepth) {
+								int i;
+								historyCount--;
+								for (i=0; i<historyCount; i++)
+									strcpy(&historyBuffer[i * cfg.bufferSize], &historyBuffer[(i+1) * cfg.bufferSize]);
+							}
+							strcpy(&historyBuffer[historyCount * cfg.bufferSize], buffer);
+							whereInHistory = historyCount;
+							historyCount++;
+						}
                     } else if (foundCount > 1)
                         cfg.puts(" *** non-unique command ignored      try 'Help' ***");
                     else if (foundCount == 0)
@@ -478,7 +556,6 @@
                 keycount = 0;
                 buffer[keycount] = '\0';
                 showPrompt = TRUE;        // forces the prompt
-            }
             break;
             default:
                 if (myisprint(c) && keycount < cfg.bufferSize) {