Listing 1

/*
 * Get input string, with VMS-style input line editing
 * and previous-command scrolling.
 *
 * Written for Turbo C 2.0 / Borland C++ 2.0
 * Bob Bybee, 2/91
 */
#include <stdio.h>
#include <string.h>

/* ASCII key definitions */
#define ESC_KEY                 0x1b
#define DELETE_KEY              0x7f
#define BACKSPACE_KEY           0x08
#define RETURN_KEY              0x0d
#define CTRL(x) ((x) &          0x1f)

/* Arbitrary values for tracking cursor key entry.
 * These happen to match PC BIOS scan codes, but any
 * unique values would work.
 */
#define UP_ARROW    0x4800
#define DOWN_ARROW  0x5000
#define RIGHT_ARROW 0x4d00
#define LEFT_ARROW  0x4b00

/* MAX_RECALL is two greater than the number of lines
 * we want in the "lastlines" recall buffer.
 */
#define MAX_RECALL 22
#define RECSUB(x) (((x) + MAX_RECALL) % MAX_RECALL)
#define RECALL_LEN 100

static char lastlines[MAX_RECALL][RECALL_LEN];
static int num_got;                     /* # chars in input buffer */
static int cursor_pos;          /* cursor position on line */
static int last_ptr = 0;        /* ptr to last line entered */
static char erase_one[] = "\b \b";      /* erase one character */
static char *str_ptr;           /* ptr to current input string */


/* prototypes for this file */
static void clear_line( void );
static int cursor_right( void );
static int cursor_left( void );
static int get_char_esc( void );
static void put_str( char *str );

/* external functions (see listing2.c)  */
void sys_putchar( char ch );
int sys_getchar( void );



/*
 * get_str() is called by main() to get a line of input.
 * The input line is placed in "str" and will be no
 * more than "len" characters.
 */
void get_str( char *str, int len )
      {
      int i, c, curptr, insert_mode = 1;

      num_got = 0;
      cursor_pos = 0;
      str_ptr = str;                         /* copy the buffer pointer */
      curptr = RECSUB(lastptr + 1);
      lastlines[curptr][0] = '\0';
      lastlines[RECSUB(curptr + 1)][0] = '\0';
      if (len > RECALL_LEN - 1)       /* limit len to RECALL_LEN */
             len = RECALL_LEN - 1;

while (1)
      {
      c = get_char_esc();

      if (c == RETURN_KEY)
             break;
      else if (c == DELETE_KEY || c == BACKSPACE_KEY)
             {
             if (cursor_left())
                    {
                    ++cursor_pos;
                    for (i = cursor_pos; i < num_got; ++i)
                           {
                           str[i - 1] = str[i];
                           sys_putchar(str[i]);
                           }
                    sys_putchar(' ');
                    for(i = cursor_pos; i <= num_got; ++i)
                           sys_putchar('\b');
                    -num_got;
                    -cursor_pos;
                    }
             }
      else if (c == CTRL('X'))         /* erase line? */
             clear_line();
      else if (c == CTRL('A'))         /* insert/overtype? */
             insert_mode ^= 1;
      else if (c == CTRL('B'))         /* beginning-of-line? */
             {
             while (cursor_left())
                    ;
             }
      else if (c == CTRL('E'))         /* end-of-line? */
             {
             while (cursor_right())
                    ;
             }
      else if (c == CTRL('R'))         /* recall last line? */
             {
             clear_line();
             strcpy(str, lastlines[lastptr]);
             if ((num_got = strlen(str)) > 0)
                    {
                    put_str(str);
                    break;
                    }
             }
      else if (c == UP_ARROW)
             {
             clear_line();
             if (lastlines[curptr][0] != '\0' ||
                    lastlines[RECSUB(curptr - 1)][0] !: '\0')
                    {
                    curptr = RECSUB(curptr - 1);
                    strcpy(str, lastlines[curptr]);
                    put_str(str);
                    cursor_pos = num_got = strlen(str);
                    }
             }
      else if (c == DOWN_ARROW)
             {
             clear_line();
             if (lastlines[curptr][0] != '\0' ||
                    lastlines[RECSUB(curptr + 1)][0] != '\0')
                    {
                    curptr = RECSUB(curptr + 1);
                    strcpy(str, lastlines[curptr]);
                    put_str(str);
                    cursor_pos = num_got = strlen(str);
                    }
             }
      else if (c == LEFT_ARROW)
             {
             if (cursor_pos > 0)
                    {
                    sys_putchar('\b');
                    -cursor_pos;
                    }
             }
      else if (c == RIGHT_ARROW)
             cursor_right();
      else if (' ' <= c && c < 0x7f && num_got < len - 1)
             {
             if (insert_mode)
                    {
                    /* Move right, all the characters
                     * to the right of cursor_pos.
                     */
                    for (i = num_got; i > cursor_pos; -i)
                           str[i] = str[i - 1];
                    str[cursor_pos] = c;
                    for (i = cursor_pos; i <= num_got; ++i)
                           sys_putchar(str[i]);
                    for (i = cursor_pos; i < num_got; ++i)
                           sys_putchar('\b');
                    ++num_got;
                    ++cursor_pos;
                    }
             else    /* insert is off, use overtype mode */
                    {
                    str[cursor_pos] = c;
                    sys_putchar(c);
                    if (cursor_pos == num_got)
                           ++num_got;
                           ++cursor_pos;
                           }
                    }
             }
      
      str[num_got] = '\0';
      sys_putchar('\n');

      /* If this line is non-empty, and different
       * from the last one accepted, store it into
       * the recall buffer.
       */
      if (num_got > 0 && strcmp(str, lastlines[lastptr]) ! = 0)
             {
             lastptr = RECSUB(lastptr + 1);
             strcpy (lastlines[lastptr], str);
             }
      }


/*
 * Move the cursor right one position, by echoing the
 * character it's currently over.
 * Return 1-OK, 0-can't move.
 */
static int cursor_right( void )
      {
      if (cursor_pos < num_got)
             {
             sys_putchar(str_ptr[cursor_pos]);
             ++cursor_pos;
             return (1);
             }
      return (0);
      }

/*
 * Move the cursor left one position, by echoing
 * a backspace. Return l-OK, 0-can't move.
 */
static int cursor_left( void )
      {
      if (cursor_pos > 0)
             {
             sys_putchar('\b');
             -cursor_pos;
             return (1);
             }
      return (0);
      }


/*
 * Erase all characters on the current line.
 */
static void clear_line( void )
      {
      while (cursor_right())
             ;              /* move right, to end of line */
      cursor_pos = 0;
      while (num_got > 0)
             {
             put_str(erase_one);            /* then, erase to left */
             -num_got;
             }
      }


/*
 * Get a character, with escape processing.
 * Handles special sequences like "ESC [ A" for up-arrow.
 * This function would need to be modified to handle
 * keyboards that are neither PC's nor VT-100's.
 */
static int get_char_esc( void )
      {
      int ch;

      ch = sys_getchar();
      if (ch != ESC_KEY)
             return (ch);

      ch = sys_getchar();
      if (ch != '[')
             return (ch);

      ch = sys_getchar();
      if (ch == 'A')
             return (UP_ARROW);             /* was ESC [ A */
      else if (ch == 'B')
             return (DOWN_ARROW);   /* was ESC [ B */
      else if (ch == 'C')
             return (RIGHT_ARROW);  /* was ESC [ C */
      else if (ch == 'D')
             return (LEFT_ARROW);   /* was ESC [ D */
      else
             return (ch);
      }


/*
 * Put a string to sys_putchar().
 */
static void put_str( char *str )
      {
      while (*str != '\0')
             sys_putchar(*str++);
      }
/* End of File */