Command Line Editing and History

CLI history is a feature that lets you retrieve previously typed commands for reuse. CLI editing is the ability to modify a command line through some set of escape or control keystrokes. Combine CLI history and CLI editing, and you have a very handy mechanism for command-line recycling. Command-line recycling becomes very useful when you are doing a lot of long, repetitive commands that each differ slightly.

Command-line recycling is a wonderful tool that allows us geeky computer types to look like we can type really fast. In reality, were spending the majority of our time using the control or escape sequences in the command line editor to undo all our typing mistakes, instead of retyping the whole line. When you are doing a lot of commands like DM 0x14280040 0x40 followed by DM 0x14290040 0x40 , command-line recycling is a big help.

Like terminal-based file editors, command-line editors come in two major types: modal and modeless. Modal means that you type some escape character to enter a mode that redefines the use of some of the keystrokes; then you type some other character to terminate the mode and return to normal character entry. Modeless means that certain control sequences are dedicated to certain features of the editor, and these control sequences can be used without entering any alternate mode.

For this implementation, I chose to implement a modal CLI editor that is a subset of the vi -like editing capability provided by many other shells . When I wrote this editor years ago, I chose a vi -style interface because thats what I was familiar with on UNIX. Over time, I have incorporated this editor into a few different tools, and I have gotten some flack over not supporting a modeless emacs-like version. I stuck to the modal approach because it was more easily isolated from the rest of the character retrieval process.

As implemented here, the function that retrieves a command line only needs to look for one escape character. After receiving that character, it passes control to the line editor. The line-editing code is therefore isolated from the rest of the system, which makes it very easy to include or exclude line-editing from the package. The following code is the only necessary addition to the general character retrieval function [3] that passes a command line to docommand()

 #define ESC 0x1b ... #if INCLUDE_LINEEDIT     if (inchar == ESC) {         (void)line_edit(inbuf);         break;     }     else #endif 

Whenever inchar contains an escape character, control passes to the function line_edit(), which switches command line input into the editing mode. The line_edit() function takes a pointer to the current input buffer. The remainder of that lines content is built through interaction with the user and the editing mode features provided by line_edit() . The line_edit() function itself gets quite messy because of all of the character overwriting performed while editing a line. This complexity, however, is all isolated from the generic retrieval of a command line.

After accessing the line_edit() function, the monitor is in the editing mode. Within that function though, there are several sub-modes that must be handled while editing the line. The user may be inserting characters, deleting characters, replacing characters , moving to a different location on the line, or just stepping through the list of commands currently in the history buffer of the editor. Also, the function must always know where it is on the command line so that it can move the cursor right or left. Unfortunately, you cant take advantage of any convenient terminal modes without limiting the function to work only with those terminals. Although use of the arrow keys might seem like the obvious set of keystrokes for moving right/left across the line and up/down through the command list, using the arrows might not be practical because many terminals (or terminal emulators) do not map the arrow keys to serial port characters.

Listing 5.11 shows part of the code for linedit.c . (Please refer to the CD, file linedit.c for the complete implementation. A full discussion of the line editor code is beyond the scope of this text. Ill introduce the implementation but leave the nitty-gritty details for the reader to review in the CD source code.) The function lineeditor() is called by line_edit() with a type that lets it know that it is being asked to edit a command line. [4] At this point, the editor has just started and is in the command ( CMD ) mode. The function sets the current position pointer to the location where the escape character was just entered on the line. Some additional pointers and state variables are established, and the code enters a loop to process incoming characters. The first character processed is the escape character. Depending on the current mode, the escape character either terminates the line editing entirely, or it terminates one of the sub-modes and returns the editor to command mode. Any character other than an escape is processed based on the mode.

Listing 5.11: linedit.c.
image from book
 static int      stridx;         /* history storage index */ static int      shwidx;         /* history display index */ static int      srchidx; static int      lastsize;       /* size of last command */ static char     curChar;        /* latest input character */ static char     *curPos;        /* current position on command line */ static char     *startOfLine;   /* start of command line */ static int      lineLen;        /* length of line */ static int      lMode;          /* present mode of entry */ static char * lineeditor(char *line_to_edit,int type) {     lMode = CMD;     startOfLine = line_to_edit;     curPos = line_to_edit;     while(*curPos != ESC)          curPos++;     *curPos = 0;                /* Remove the escape char from the line */     lineLen = (ulong)curPos - (ulong)startOfLine;     if (lineLen > 0) {         curPos--;         pwrite(1," \b\b",3);     }     else         pwrite(1," \b",2);     lastsize = 0;     shwidx = stridx;     srchidx = stridx;     while(1) {         curChar = (char)getchar();         switch(curChar) {             case ESC:                 if (lMode != CMD) {                     lMode = CMD;                     continue;                 }                 else {                     putchar('\n');                     return((char *)0);                 }             case '\r':             case '\n':                 putchar('\n');                 if (lineLen == 0)                      return((char *)0);                 *(char *)(startOfLine + lineLen) = ' 
 static int stridx; /* history storage index */ static int shwidx; /* history display index */ static int srchidx; static int lastsize; /* size of last command */ static char curChar; /* latest input character */ static char *curPos; /* current position on command line */ static char *startOfLine; /* start of command line */ static int lineLen; /* length of line */ static int lMode; /* present mode of entry */ static char * lineeditor(char *line_to_edit,int type) { lMode = CMD; startOfLine = line_to_edit; curPos = line_to_edit; while(*curPos != ESC) curPos++; *curPos = 0; /* Remove the escape char from the line */ lineLen = (ulong)curPos - (ulong)startOfLine; if (lineLen > 0) { curPos--; pwrite(1," \b\b",3); } else pwrite(1," \b",2); lastsize = 0; shwidx = stridx; srchidx = stridx; while(1) { curChar = (char) getchar (); switch(curChar) { case ESC: if (lMode != CMD) { lMode = CMD; continue; } else { putchar('\n'); return((char *)0); } case '\r': case '\n': putchar ('\n'); if (lineLen == 0) return((char *)0); *(char *)(startOfLine + lineLen) = '\0'; return(startOfLine); } switch(lMode) { case CMD: lcmd(type); if (lMode == NEITHER) return((char *)0); break; case INSERT: linsert(); break; case EDIT1: case EDIT: ledit(); break; } if (lineLen >= MAXLINESIZE) { printf("line overflow\n"); return((char *)0); } } } 
'; return(startOfLine); } switch(lMode) { case CMD: lcmd(type); if (lMode == NEITHER) return((char *)0); break; case INSERT: linsert(); break; case EDIT1: case EDIT: ledit(); break; } if (lineLen >= MAXLINESIZE) { printf("line overflow\n"); return((char *)0); } } }
image from book
 

This command-line editor supports three different sub-modes: INSERT , EDIT , and CMD . INSERT mode means that the user is inserting characters somewhere into the current command line. Characters are entered into the command line using the linsert() function. EDIT mode means that the user is modifying characters that are already in the command line (the ledit() function). CMD mode expects incoming characters to perform some action on the command line, put the editor into one of the other modes, or both.

If the system is in CMD mode, the lcmd() function (see Listing 5.12) processes different characters as different commands based on a subset of the vi -like command-line editing rules. A zero tells the editor to put the cursor at the beginning of the line, an A tells the editor to enter INSERT mode with the cursor positioned just after the last character of the current line. The i , r , and R commands simply transition to either EDIT or INSERT mode. The l and h commands move the cursor to the right or left, and the j and k commands tell the editor to step up or down through the command-line history list.

Listing 5.12: lcmd().
image from book
 static void lcmd(int type) {     switch(curChar) {         case '0':             gotobegin();             return;         case 'A':             gotoend();             putchar(*curPos);             curPos++;             lMode = INSERT;             return;         case 'i':             lMode = INSERT;             return;         case 'x':             ldelete();             return;         case ' ':         case 'l':             if (curPos < startOfLine+lineLen-1) {                 putchar(*curPos);                 curPos++;             }             return;         case '\b':         case 'h':             if (curPos > startOfLine) {                 putchar('\b');                 curPos--;             }             return;         case 'r':             lMode = EDIT1;             return;         case 'R':             lMode = EDIT;             return;         case '/':             putchar('/');             historysearch();             return;         case '+':         case 'j':             shownext();             return;         case '-':         case 'k':             showprev();             return;     }     /* Beep to indicate an error. */     putchar(' 
 static void lcmd(int type) { switch(curChar) { case '0': gotobegin(); return; case 'A': gotoend(); putchar(*curPos); curPos++; lMode = INSERT; return; case 'i': lMode = INSERT; return; case 'x': ldelete(); return; case ' ': case 'l': if (curPos < startOfLine+lineLen-1) { putchar(*curPos); curPos++; } return; case '\b': case 'h': if (curPos > startOfLine) { putchar('\b'); curPos--; } return; case 'r': lMode = EDIT1; return; case 'R': lMode = EDIT; return; case '/': putchar('/'); historysearch(); return; case '+': case 'j': shownext(); return; case '-': case 'k': showprev(); return; } /* Beep to indicate an error. */ putchar('\007'); } 
7'); }
image from book
 

For further detail on command-line editing, refer to the lineedit.c source code on the CD.

[3] On the CD, this is in the function _getline() (part of chario.c).

[4] This line editor is also used by a flash file editor, and, for that case, there are a few different rules applied.



Embedded Systems Firmware Demystified
Embedded Systems Firmware Demystified (With CD-ROM)
ISBN: 1578200997
EAN: 2147483647
Year: 2002
Pages: 118
Authors: Ed Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net