home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume25 / input-edit / part01 / getline.c next >
Encoding:
C/C++ Source or Header  |  1991-11-12  |  16.7 KB  |  588 lines

  1. #ifndef lint
  2. static char     rcsid[] =
  3. "$Id: getline.c,v 1.8 1991/11/09 20:27:48 thewalt Exp thewalt $";
  4. #endif
  5.  
  6. /* 
  7.  * Fairly portable (ANSI C),  emacs style line editing input package.  
  8.  * This package uses \b to move, and \007 to ring the bell.  
  9.  * It uses a fixed screen width, as initialized in the gl_init() call,
  10.  * and does not draw in the last location to avoid line wraps.
  11.  * The only non-portable part is how to turn off character echoing.
  12.  * This code works for *NIX of BSD or SYSV flavor, as well as MSDOS (MSC6.0).
  13.  * No TERMCAP features are used, so long lines are scrolled on one line 
  14.  * rather than extending over several lines.  The function getline 
  15.  * returns a pointer to a static buffer area which holds the input string, 
  16.  * including the newline. On EOF the first character is set equal to '\0'.  
  17.  * The caller supplies a desired prompt, as shown in the prototype:
  18.  *
  19.  *      char *getline(char *prompt)
  20.  *
  21.  * Getline is different from GNU readline in that:
  22.  *    - getline has much more limited editing capabilities, but it
  23.  *      is also much smaller and doesn't need termcap.
  24.  *    - you don't free the buffer when done, since it is static 
  25.  *      (make a copy yourself if you want one)
  26.  *    - the newline is appended to the buffer
  27.  *    - you don't add lines to history, it is done automatically.
  28.  *
  29.  * The function gl_init(int screen_width) should be called before 
  30.  * calling getline(char *prompt), and gl_cleanup(void) should be 
  31.  * called before before exiting.  The function gl_redraw(void) may also 
  32.  * be called to redraw the screen (as is done when ^L or ^R are read).
  33.  *
  34.  * The editing keys are:
  35.  *  ^A, ^E   - move to beginnning or end of line, respectively.
  36.  *  ^F, ^B   - nondestructive move forward or back one location, respectively.
  37.  *  ^D       - delete the character currently under the cursor, or
  38.  *             send EOF if no characters in the buffer.
  39.  *  ^H, DEL  - delete character left of the cursor.
  40.  *  ^K       - delete from cursor to end of line.
  41.  *  ^P, ^N   - move through history, previous and next, respectively.
  42.  *  ^L, ^R   - redraw the current line.
  43.  *  TAB      - call user defined function if bound, or insert spaces
  44.  *             to get to next TAB stop (just past every 8th column).
  45.  *  NL, CR   - places line on history list if nonblank, calls output
  46.  *             cleanup function if bound, appends newline and returns
  47.  *             to the caller.
  48.  *
  49.  * In addition, the caller can modify the buffer in certain ways, which
  50.  * may be useful for things like auto-indent modes.  There are three
  51.  * function pointers which can be bound to user functions.
  52.  * Each of these functions must return the index of the first location 
  53.  * at which the buffer was modified, or -1 if the buffer wasn't modified.  
  54.  * Each of the functions receive the current input buffer as the first 
  55.  * argument.  The screen is automatically cleaned up if the buffer is changed.
  56.  * The user is responsible for not writing beyond the end of the static 
  57.  * buffer.  The function pointer prototypes are:
  58.  * 
  59.  *   int (*gl_in_hook)(char *buf)
  60.  *       - called on entry to getline, and each time a new history
  61.  *         string is loaded, from a ^P or ^N. Initally NULL.
  62.  *   int (*gl_out_hook)(char *buf)
  63.  *       - called when a \n or \r is read, before appending \n and
  64.  *         returning to caller. Initally NULL.
  65.  *   int (*gl_tab_hook)(char *buf, int offset, int *loc)
  66.  *       - called whenever a TAB is read.  The second argument is
  67.  *         the current line offset due to the width of the prompt. 
  68.  *         The third argument is a pointer to the variable holding the 
  69.  *         current location in the buffer.  The location may be reset 
  70.  *         by the user to move the cursor when the call returns.
  71.  *         Initially a built in tabbing function is bound.
  72.  *
  73.  * Please send bug reports, fixes and enhancements to Chris Thewalt,
  74.  * thewalt@ce.berkeley.edu
  75.  */
  76.  
  77. static char *copyright = "Copyright (C) 1991, Chris Thewalt";
  78. /*
  79.  * Copyright (C) 1991 by Chris Thewalt
  80.  *
  81.  * Permission to use, copy, modify, and distribute this software 
  82.  * for any purpose and without fee is hereby granted, provided
  83.  * that the above copyright notices appear in all copies and that both the
  84.  * copyright notice and this permission notice appear in supporting
  85.  * documentation.  This software is provided "as is" without express or
  86.  * implied warranty.
  87.  */
  88.  
  89. #include <stdio.h>
  90. #include <stdlib.h>
  91. #include <string.h>
  92. #include <ctype.h>
  93.  
  94. extern int      isatty();    
  95.  
  96. #define BUF_SIZE 1024
  97. #define SCROLL   30
  98.  
  99. static int      gl_init_done = 0;    /* -1 is terminal, 1 is batch  */
  100. static int      gl_screen = 80;        /* screen width */
  101. static int      gl_width = 0;        /* net size available for input */
  102. static int      gl_pos, gl_cnt = 0;     /* position and size of input */
  103. static char     gl_buf[BUF_SIZE];       /* input buffer */
  104. static char    *gl_prompt;        /* to save the prompt string */
  105.  
  106. void            gl_init(int);        
  107. void            gl_cleanup(void);    /* to undo gl_init */
  108. void            gl_redraw(void);    /* issue \n and redraw all */
  109. static void     gl_char_init(void);    /* get ready for no echo input */
  110. static void     gl_char_cleanup(void);    /* undo gl_char_init */
  111. static int      gl_getchar(void);       /* read one char from stdin */
  112. static void     gl_addchar(int);    /* install specified char */
  113. static void     gl_newline(void);    /* handle \n or \r */
  114. static void     gl_fixup(int, int);    /* fixup state variables and screen */
  115. static void     gl_del(int);        /* del, either left (-1) or cur (0) */
  116. static void     gl_kill(void);        /* delete to EOL */
  117. static int      gl_tab(char *, int, int *);    /* default TAB handler */
  118.  
  119. static void     hist_add(void);        /* adds nonblank entries to hist */
  120. static void     hist_init(void);    /* initializes hist pointers */
  121. static void     hist_next(void);    /* copies next entry to input buf */
  122. static void     hist_prev(void);    /* copies prev entry to input buf */
  123. static char    *hist_save(char *);    /* makes copy of a string */
  124.  
  125. int         (*gl_in_hook)(char *) = 0;
  126. int         (*gl_out_hook)(char *) = 0;
  127. int         (*gl_tab_hook)(char *, int, int *) = gl_tab;
  128.  
  129. /************************ nonportable part *********************************/
  130. #ifdef MSDOS
  131. #include <bios.h>
  132. #endif
  133.  
  134. #ifdef unix
  135. #include <sys/ioctl.h>
  136. #ifndef TIOCGETP
  137. #include <termio.h>
  138. struct termio tty, old_tty;
  139. #else
  140. #include <sgtty.h>
  141. struct sgttyb   tty, old_tty;
  142. #endif /* TIOCGETP */
  143. extern int      ioctl();
  144. #endif    /* unix */
  145.  
  146. static void
  147. gl_char_init()
  148. /* turn off input echo */
  149. {
  150. #ifdef unix
  151. #ifdef TIOCGETP
  152.     ioctl(0, TIOCGETP, &old_tty);
  153.     tty = old_tty;
  154.     tty.sg_flags |= CBREAK;
  155.     tty.sg_flags &= ~ECHO;
  156.     ioctl(0, TIOCSETP, &tty);
  157. #else
  158.     ioctl(0, TCGETA, &old_tty);
  159.     tty = old_tty;
  160.     tty.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
  161.     tty.c_cc[VMIN] = 1;
  162.     ioctl(0, TCSETA, &tty);
  163. #endif 
  164. #endif /* unix */
  165. }
  166.  
  167. static void
  168. gl_char_cleanup()
  169. /* undo effects of gl_char_init, as necessary */
  170. {
  171. #ifdef unix
  172. #ifdef TIOCSETP
  173.     ioctl(0, TIOCSETP, &old_tty);
  174. #else
  175.     ioctl(0, TCSETA, &old_tty);
  176. #endif
  177. #endif /* unix */
  178. }
  179.  
  180. static int
  181. gl_getchar()
  182. /* get a character without echoing it to screen */
  183. {
  184.     int             c;
  185.  
  186. #ifdef unix
  187.     c = fgetc(stdin);
  188. #endif
  189. #ifdef MSDOS
  190.     c = _bios_keybrd(_NKEYBRD_READ) & 0177;   /* only using 7 bit ASCII */
  191. #endif
  192.     return c;
  193. }
  194. /******************** fairly portable part *********************************/
  195. void
  196. gl_init(int scrn_wdth)
  197. /* set up variables and terminal */
  198. {
  199.     gl_screen = scrn_wdth;
  200.     if (gl_init_done == 0) {
  201.         hist_init();
  202.         if (isatty(0) && isatty(1)) { 
  203.             setvbuf(stdin, (char *)0, _IONBF, 0);
  204.             setvbuf(stdout, (char *)0, _IONBF, 0);
  205.         gl_char_init();
  206.             gl_init_done = -1;        /* -1 means terminal */
  207.         } else { 
  208.             gl_init_done = 1;        /* 1 means batch */
  209.     }
  210.     }
  211.     gl_pos = gl_cnt = 0;
  212. }
  213.  
  214. void
  215. gl_cleanup()
  216. /* undo effects of gl_init, as necessary */
  217. {
  218.     if (gl_init_done == -1)
  219.         gl_char_cleanup();
  220.     gl_init_done = 0;
  221. }
  222.  
  223. char *
  224. getline(char *prompt)
  225. {
  226.     int             c, loc, tmp;
  227.  
  228.     if (!gl_init_done)
  229.     gl_init(80);
  230.     gl_buf[0] = 0;        /* used as end of input indicator */
  231.     if (gl_init_done == 1) {    /* no input editing, and no prompt output */
  232.     fgets(gl_buf, BUF_SIZE, stdin);
  233.     return gl_buf;
  234.     }
  235.     gl_fixup(-1, 0);            /* this resets gl_fixup */
  236.     gl_width = gl_screen - strlen(prompt);
  237.     if (prompt == NULL)    
  238.     prompt = "";
  239.     gl_prompt = prompt;
  240.     gl_pos = gl_cnt = 0;
  241.     fputs(prompt, stdout);
  242.     if (gl_in_hook) {
  243.     loc = gl_in_hook(gl_buf);
  244.     if (loc >= 0)
  245.         gl_fixup(0, BUF_SIZE);
  246.     }
  247.     while ((c = gl_getchar()) != EOF) {
  248.     if (isprint(c)) {
  249.         gl_addchar(c);
  250.     } else {
  251.         switch (c) {
  252.           case '\n': case '\r':             /* newline */
  253.         gl_newline();
  254.         return gl_buf;
  255.         break;
  256.           case '\001': gl_fixup(-1, 0);        /* ^A */
  257.         break;
  258.           case '\002': gl_fixup(-1, gl_pos-1);    /* ^B */
  259.         break;
  260.           case '\004':                /* ^D */
  261.         if (gl_cnt == 0) {
  262.             gl_buf[0] = 0;
  263.             gl_cleanup();
  264.             fputc('\n', stdout);
  265.             return gl_buf;
  266.         } else {
  267.             gl_del(0);
  268.         }
  269.         break;
  270.           case '\005': gl_fixup(-1, gl_cnt);    /* ^E */
  271.         break;
  272.           case '\006': gl_fixup(-1, gl_pos+1);    /* ^F */
  273.         break;
  274.           case '\010': case '\177': gl_del(-1);    /* ^H and DEL */
  275.         break;
  276.           case '\t':                    /* TAB */
  277.                 if (gl_tab_hook) {
  278.             tmp = gl_pos;
  279.                 loc = gl_tab_hook(gl_buf, strlen(gl_prompt), &tmp);
  280.                 if (loc >= 0 || tmp != gl_pos)
  281.                     gl_fixup(loc, tmp);
  282.                 }
  283.         break;
  284.           case '\013': gl_kill();            /* ^K */
  285.         break;
  286.           case '\014': case '\022':    gl_redraw();    /* ^L and ^R */
  287.         break;
  288.           case '\016': hist_next();            /* ^N */
  289.         break;
  290.           case '\020': hist_prev();            /* ^P */
  291.         break;
  292.           default:
  293.         fputc('\007', stdout);
  294.         break;
  295.         }
  296.     }
  297.     }
  298.     gl_cleanup();
  299.     return gl_buf;
  300. }
  301.  
  302. static void
  303. gl_addchar(int c)
  304. /* adds the character c to the input buffer at current location */
  305. {
  306.     int  i;
  307.  
  308.     if (gl_cnt >= BUF_SIZE - 1) {
  309.     fprintf(stderr, "getline: input buffer overflow\n");
  310.     exit(1);
  311.     }
  312.     for (i=gl_cnt; i >= gl_pos; i--)
  313.          gl_buf[i+1] = gl_buf[i];
  314.     gl_buf[gl_pos] = c;
  315.     gl_fixup(gl_pos, gl_pos+1);
  316. }
  317.  
  318. static void
  319. gl_newline()
  320. /*
  321.  * Cleans up entire line before returning to caller. A \n is appended.
  322.  * If line longer than screen, we redraw starting at beginning
  323.  */
  324. {
  325.     int change = gl_cnt;
  326.     int len = gl_cnt;
  327.     int loc = gl_width - 5;    /* shifts line back to start position */
  328.  
  329.     if (gl_cnt >= BUF_SIZE - 1) {
  330.     fprintf(stderr, "getline: input buffer overflow\n");
  331.     exit(1);
  332.     }
  333.     hist_add();            /* only adds if nonblank */
  334.     if (gl_out_hook) {
  335.     change = gl_out_hook(gl_buf);
  336.         len = strlen(gl_buf);
  337.     } 
  338.     if (loc > len)
  339.     loc = len;
  340.     gl_fixup(change, loc);    /* must do this before appending \n */
  341.     gl_buf[len] = '\n';
  342.     gl_buf[len+1] = '\0';
  343.     fputc('\n', stdout);
  344. }
  345.  
  346. static void
  347. gl_del(int loc)
  348. /*
  349.  * Delete a character.  The loc variable can be:
  350.  *    -1 : delete character to left of cursor
  351.  *     0 : delete character under cursor
  352.  */
  353. {
  354.     int i;
  355.  
  356.     if (loc == -1 && gl_pos > 0 || loc == 0 && gl_pos < gl_cnt) {
  357.         for (i=gl_pos+loc; i < gl_cnt; i++)
  358.         gl_buf[i] = gl_buf[i+1];
  359.     gl_fixup(gl_pos+loc, gl_pos+loc);
  360.     } else
  361.     fputc('\007', stdout);
  362. }
  363.  
  364. static void
  365. gl_kill()
  366. /* delete from current position to the end of line */
  367. {
  368.     if (gl_pos < gl_cnt) {
  369.     gl_buf[gl_pos] = '\0';
  370.     gl_fixup(gl_pos, gl_pos);
  371.     } else
  372.     fputc('\007', stdout);
  373. }
  374.  
  375. void
  376. gl_redraw()
  377. /* emit a newline, reset and redraw prompt and current input line */
  378. {
  379.     if (gl_init_done == -1) {
  380.         fputc('\n', stdout);
  381.         fputs(gl_prompt, stdout);
  382.         gl_pos = 0;
  383.         gl_fixup(0, BUF_SIZE);
  384.     }
  385. }
  386.  
  387. static void
  388. gl_fixup(int change, int cursor)
  389. /*
  390.  * This function is used both for redrawing when input changes or for
  391.  * moving within the input line.  The parameters are:
  392.  *   change : the index of the start of changes in the input buffer,
  393.  *            with -1 indicating no changes.
  394.  *   cursor : the desired location of the cursor after the call.
  395.  *            A value of BUF_SIZE can be used  to indicate the cursor should
  396.  *            move just past the end of the input line.
  397.  */
  398. {
  399.     static int   gl_shift;    /* index of first on screen character */
  400.     static int   off_right;    /* true if more text right of screen */
  401.     static int   off_left;    /* true if more text left of screen */
  402.     int          left = 0, right = -1;        /* bounds for redraw */
  403.     int          pad;        /* how much to erase at end of line */
  404.     int          backup;        /* how far to backup before fixing */
  405.     int          new_shift;     /* value of shift based on cursor */
  406.     int          extra;         /* adjusts when shift (scroll) happens */
  407.     int          i;
  408.  
  409.     if (change == -1 && cursor == 0 && gl_buf[0] == 0) {   /* reset */
  410.     gl_shift = off_right = off_left = 0;
  411.     return;
  412.     }
  413.     pad = (off_right)? gl_width - 1 : gl_cnt - gl_shift;   /* old length */
  414.     backup = gl_pos - gl_shift;
  415.     if (change >= 0) {
  416.         gl_cnt = strlen(gl_buf);
  417.         if (change > gl_cnt)
  418.         change = gl_cnt;
  419.     }
  420.     if (cursor > gl_cnt) {
  421.     if (cursor != BUF_SIZE)        /* BUF_SIZE means end of line */
  422.         fputc('\007', stdout);
  423.     cursor = gl_cnt;
  424.     }
  425.     if (cursor < 0) {
  426.     fputc('\007', stdout);
  427.     cursor = 0;
  428.     }
  429.     if (off_right || off_left && cursor < gl_shift + gl_width - SCROLL / 2)
  430.     extra = 2;            /* shift the scrolling boundary */
  431.     else 
  432.     extra = 0;
  433.     new_shift = cursor + extra + SCROLL - gl_width;
  434.     if (new_shift > 0) {
  435.     new_shift /= SCROLL;
  436.     new_shift *= SCROLL;
  437.     } else
  438.     new_shift = 0;
  439.     if (new_shift != gl_shift) {    /* scroll occurs */
  440.     gl_shift = new_shift;
  441.     off_left = (gl_shift)? 1 : 0;
  442.     off_right = (gl_cnt > gl_shift + gl_width - 1)? 1 : 0;
  443.         left = gl_shift;
  444.     right = (off_right)? gl_shift + gl_width - 2 : gl_cnt;
  445.     } else if (change >= 0) {        /* no scroll, but text changed */
  446.     if (change < gl_shift + off_left) {
  447.         left = gl_shift;
  448.     } else {
  449.         left = change;
  450.         backup = gl_pos - change;
  451.     }
  452.     off_right = (gl_cnt > gl_shift + gl_width - 1)? 1 : 0;
  453.     right = (off_right)? gl_shift + gl_width - 2 : gl_cnt;
  454.     }
  455.     pad -= (off_right)? gl_width - 1 : gl_cnt - gl_shift;
  456.     pad = (pad < 0)? 0 : pad;
  457.     if (left <= right) {        /* clean up screen */
  458.     for (i=0; i < backup; i++)
  459.         fputc('\b', stdout);
  460.     if (left == gl_shift && off_left) {
  461.         fputc('$', stdout);
  462.         left++;
  463.         }
  464.     for (i=left; i < right; i++)
  465.         fputc(gl_buf[i], stdout);
  466.     if (off_right) {
  467.         fputc('$', stdout);
  468.         gl_pos = right + 1;
  469.     } else { 
  470.         for (i=0; i < pad; i++)    /* erase remains of prev line */
  471.         fputc(' ', stdout);
  472.         gl_pos = right + pad;
  473.     }
  474.     }
  475.     i = gl_pos - cursor;        /* move to final cursor location */
  476.     if (i > 0) {
  477.     while (i--)
  478.        fputc('\b', stdout);
  479.     } else {
  480.     for (i=gl_pos; i < cursor; i++)
  481.         fputc(gl_buf[i], stdout);
  482.     }
  483.     gl_pos = cursor;
  484. }
  485.  
  486. static int
  487. gl_tab(char *buf, int offset, int *loc)
  488. /* default tab handler, acts like tabstops every 8 cols */
  489. {
  490.     int i, count, len;
  491.  
  492.     len = strlen(buf);
  493.     count = 8 - (offset + *loc) % 8;
  494.     for (i=len; i >= *loc; i--)
  495.         buf[i+count] = buf[i];
  496.     for (i=0; i < count; i++)
  497.         buf[*loc+i] = ' ';
  498.     i = *loc;
  499.     *loc = i + count;
  500.     return i;
  501. }
  502.  
  503. /******************* History stuff **************************************/
  504.  
  505. #ifndef HIST_SIZE
  506. #define HIST_SIZE 100
  507. #endif
  508.  
  509. int             hist_pos, hist_last;
  510. char           *hist_buf[HIST_SIZE];
  511.  
  512. static void
  513. hist_init()
  514. {
  515.     int i;
  516.  
  517.     for (i=0; i < HIST_SIZE; i++)
  518.     hist_buf[i] = (char *)NULL;
  519. }
  520.  
  521. static void
  522. hist_add()
  523. {
  524.     char *p = gl_buf;
  525.  
  526.     while (*p == ' ' || *p == '\t')    /* only save nonblank line */
  527.     p++;
  528.     if (*p) {
  529.         hist_buf[hist_last] = hist_save(gl_buf);
  530.         hist_last = (hist_last + 1) % HIST_SIZE;
  531.         if (hist_buf[hist_last]) {    /* erase next location */
  532.         free(hist_buf[hist_last]);
  533.         hist_buf[hist_last] = NULL;
  534.         }
  535.     }
  536.     hist_pos = hist_last;
  537. }
  538.  
  539. static void
  540. hist_prev()
  541. /* loads previous hist entry into input buffer, sticks on first */
  542. {
  543.     int   next;
  544.  
  545.     next = (hist_pos - 1 + HIST_SIZE) % HIST_SIZE;
  546.     if (next != hist_last) {
  547.         if (hist_buf[next]) {
  548.         hist_pos = next;
  549.         strcpy(gl_buf, hist_buf[hist_pos]);
  550.     } else
  551.         fputc('\007', stdout);
  552.     } else
  553.     fputc('\007', stdout);
  554.     if (gl_in_hook)
  555.     gl_in_hook(gl_buf);
  556.     gl_fixup(0, BUF_SIZE);
  557. }
  558.  
  559. static void
  560. hist_next()
  561. /* loads next hist entry into input buffer, clears on last */
  562. {
  563.     if (hist_pos != hist_last) {
  564.     hist_pos = (hist_pos + 1) % HIST_SIZE;
  565.     if (hist_buf[hist_pos]) {
  566.         strcpy(gl_buf, hist_buf[hist_pos]);
  567.     } else {
  568.         gl_buf[0] = 0;
  569.     }
  570.     } else
  571.     fputc('\007', stdout);
  572.     if (gl_in_hook) 
  573.     gl_in_hook(gl_buf);
  574.     gl_fixup(0, BUF_SIZE);
  575. }
  576.  
  577. static char *
  578. hist_save(char *p)
  579. /* makes a copy of the string */
  580. {
  581.     char *s = NULL;
  582.  
  583.     if (p && ((s = malloc(strlen(p)+1)) != NULL)) {
  584.             strcpy(s, p);
  585.     }
  586.     return s;
  587. }
  588.