home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-02-23 | 42.2 KB | 1,394 lines |
- Newsgroups: comp.sources.misc
- From: thewalt@canuck.CE.Berkeley.EDU (Chris Thewalt)
- Subject: v28i056: input-edit - C input functions for line editing with history, Part01/01
- Message-ID: <1992Feb18.152653.20566@sparky.imd.sterling.com>
- X-Md4-Signature: d07f13a59deb6e202103e5a937ce1dad
- Date: Tue, 18 Feb 1992 15:26:53 GMT
- Approved: kent@sparky.imd.sterling.com
-
- Submitted-by: thewalt@canuck.CE.Berkeley.EDU (Chris Thewalt)
- Posting-number: Volume 28, Issue 56
- Archive-name: input-edit/part01
- Environment: UNIX, MS-DOS
- Supersedes: input-edit: Volume 25, Issue 56
-
- This is a complete repost of the input-edit package posted in volume
- 25 and patched in volume 26.
-
- Motivation:
-
- Many interactive programs read input line by line, but would like to
- provide line editing and history functionality to the end-user that
- runs the program.
-
- The input-edit package provides that functionality. As far as the
- programmer is concerned, the program only asks for the next line
- of input. However, until the user presses the RETURN key they can use
- emacs-style line editing commands and can traverse the history of lines
- previously typed.
-
- Other packages, such as GNU's readline, have greater capability but are
- also substantially larger. Input-edit is small, since it uses neither
- stdio nor any termcap features, and is also quite portable. It only uses
- \b to backspace and \007 to ring the bell on errors. Since it cannot
- edit multiple lines it scrolls long lines left and right on the same line.
-
- Environment:
-
- Input edit uses classic (not ANSI) C, and should run on any Unix
- system (BSD or SYSV), PC's with the MSC compiler, or Vax/VMS.
-
- Porting the package to new systems basicaly requires code to read a
- character when it is typed without echoing it, everything else should be OK.
-
- I have run the package on:
-
- DECstation 5000, Ultrix 4.2 with cc and gcc
- Sun Sparc 2, SunOS 4.1.1, with cc
- SGI Iris, IRIX System V.3, with cc
- PC, DRDOS 5.0, with MSC 6.0
- (I haven't tested the VMS code)
-
- Changes since last release:
-
- * The user no longer calls gl_init() and gl_cleanup(), getline() sets
- required terminal modes on entry and resets before returning. This
- was necessary to capture changes in terminal modes that the main
- program might be making.
- * Getline() now looks to see which characters are bound to signal
- generation, and when these characters are seen getline() resets
- terminal modes before passing on the signal. If the signal handler
- returns to getline(), the screen is automatically updated and editing
- can continue.
- * The toggle key for overwrite mode has been moved from ^G to ^O
- * All code is now classic rather than ANSI C, so any compiler should
- be able to handle it.
- * ^Y now yanks back previously kill'ed (^K) text starting at the
- current location.
- * ^R/^S begin reverse and forward incremental searches through the
- history list.
- * The programmer must add buffers onto the history list by calling
- gl_addhist(char *buffer). This function makes copies of the buffer
- and adds them to the history list if the buffer is not a blank line
- and if the buffer is different than the last item saved (so the
- program need not check for these conditions)
- * The main program can specify the screen width to use with a call to
- gl_setwidth(int width)
- * Getline now insists that both the input and output are connected to
- a terminal. If this is not the case, an error message is written and
- the program is terminated. The main program should check for this
- case and use buffered IO (stdio) for non-interactive sessions.
-
- #!/bin/sh
- # This is a shell archive (produced by shar 3.49)
- # To extract the files from this archive, save it to a file, remove
- # everything above the "!/bin/sh" line above, and type "sh file_name".
- #
- # made 02/11/1992 16:24 UTC by thewalt@canuck.CE.Berkeley.EDU
- # Source directory /usr/users/thewalt/sw/getline
- #
- # existing files will NOT be overwritten unless -c is specified
- #
- # This shar contains:
- # length mode name
- # ------ ---------- ------------------------------------------
- # 8280 -rw-r--r-- README
- # 1608 -rw-r--r-- CHANGES
- # 26245 -rw-r--r-- getline.c
- # 309 -rw-r--r-- testgl.c
- #
- # ============= README ==============
- if test -f 'README' -a X"$1" != X"-c"; then
- echo 'x - skipping README (File already exists)'
- else
- echo 'x - extracting README (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'README' &&
- *************************** Motivation **********************************
- X
- Many interactive programs read input line by line, but would like to
- provide line editing and history functionality to the end-user that
- runs the program.
- X
- The input-edit package provides that functionality. As far as the
- programmer is concerned, the program only asks for the next line
- of input. However, until the user presses the RETURN key they can use
- emacs-style line editing commands and can traverse the history of lines
- previously typed.
- X
- Other packages, such as GNU's readline, have greater capability but are
- also substantially larger. Input-edit is small, since it uses neither
- stdio nor any termcap features, and is also quite portable. It only uses
- \b to backspace and \007 to ring the bell on errors. Since it cannot
- edit multiple lines it scrolls long lines left and right on the same line.
- X
- Input edit uses classic (not ANSI) C, and should run on any Unix
- system (BSD or SYSV), PC's with the MSC compiler, or Vax/VMS (untested by me).
- Porting the package to new systems basicaly requires code to read a
- character when it is typed without echoing it, everything else should be OK.
- X
- I have run the package on:
- X
- X DECstation 5000, Ultrix 4.2 with cc and gcc
- X Sun Sparc 2, SunOS 4.1.1, with cc
- X SGI Iris, IRIX System V.3, with cc
- X PC, DRDOS 5.0, with MSC 6.0
- X
- The description below is broken into two parts, the end-user (editing)
- interface and the programmer interface. Send bug reports, fixes and
- enhancements to:
- X
- Chris Thewalt (thewalt@ce.berkeley.edu)
- 2/4/92
- X
- PS: I don't have, and don't want to add, a vi mode, sorry.
- X
- ************************** End-User Interface ***************************
- X
- Entering printable keys generally inserts new text into the buffer (unless
- in overwrite mode, see below). Other special keys can be used to modify
- the text in the buffer. In the description of the keys below, ^n means
- Control-n, or holding the CONTROL key down while pressing "n". Errors
- will ring the terminal bell.
- X
- ^A/^E : Move cursor to beginning/end of the line.
- ^F/^B : Move cursor forward/backward one character.
- ^D : Delete the character under the cursor.
- ^H, DEL : Delete the character to the left of the cursor.
- ^K : Kill from the cursor to the end of line.
- ^L : Redraw current line.
- ^O : Toggle overwrite/insert mode. Initially in insert mode. Text
- X added in overwrite mode (including yanks) overwrite
- X existing text, while insert mode does not overwrite.
- ^P/^N : Move to previous/next item on history list.
- ^R/^S : Perform incremental reverse/forward search for string on
- X the history list. Typing normal characters adds to the current
- X search string and searches for a match. Typing ^R/^S marks
- X the start of a new search, and moves on to the next match.
- X Typing ^H or DEL deletes the last character from the search
- X string, and searches from the starting location of the last search.
- X Therefore, repeated DEL's appear to unwind to the match nearest
- X the point at which the last ^R or ^S was typed. If DEL is
- X repeated until the search string is empty the search location
- X begins from the start of the history list. Typing ESC or
- X any other editing character accepts the current match and
- X loads it into the buffer, terminating the search.
- ^T : Toggle the characters under and to the left of the cursor.
- ^Y : Yank previously killed text back at current location. Note that
- X this will overwrite or insert, depending on the current mode.
- TAB : By default adds spaces to buffer to get to next TAB stop
- X (just after every 8th column), although this may be rebound by the
- X programmer, as described below.
- NL, CR : returns current buffer to the program.
- X
- DOS and ANSI terminal arrow key sequences are recognized, and act like:
- X
- X up : same as ^P
- X down : same as ^N
- X left : same as ^B
- X right : same as ^F
- X
- ************************** Programmer Interface ***************************
- X
- The programmer accesses input-edit through three functions, and optionally
- through three additional function pointer hooks. The three functions are:
- X
- char *getline(char *prompt)
- X
- X Prints the prompt and allows the user to edit the current line. A
- X pointer to the line is returned when the user finishes by
- X typing a newline or a return. Unlike GNU readline, the returned
- X pointer points to a static buffer, so it should not be free'd, and
- X the buffer contains the newline character. The user enters an
- X end-of-file by typing ^D on an empty line, in which case the
- X first character of the returned buffer is '\0'. Getline never
- X returns a NULL pointer. The getline functions sets terminal modes
- X needed to make it work, and resets them before returning to the
- X caller. The getline function also looks for characters that would
- X generate a signal, and resets the terminal modes before raising the
- X signal condition. If the signal handler returns to getline,
- X the screen is automatically redrawn and editing can continue.
- X Getline now requires both the input and output stream be connected
- X to the terminal (not redirected) so the main program should check
- X to make sure this is true. If input or output have been redirected
- X the main program should use buffered IO (stdio) rather than
- X the slow 1 character read()s that getline uses.
- X
- void gl_setwidth(int width)
- X
- X Set the width of the terminal to the specified width. The default
- X width is 80 characters, so this function need only be called if the
- X width of the terminal is not 80. Since horizontal scrolling is
- X controlled by this parameter it is important to get it right.
- X
- void gl_histadd(char *buf)
- X
- X The gl_histadd function checks to see if the buf is not empty or
- X whitespace, and also checks to make sure it is different than
- X the last saved buffer to avoid repeats on the history list.
- X If the buf is a new non-blank string a copy is made and saved on
- X the history list, so the caller can re-use the specified buf.
- X
- The main loop in testgl.c, included in this directory, shows how the
- input-edit package can be used:
- X
- extern char *getline();
- extern void gl_histadd();
- main()
- {
- X char *p;
- X do {
- X p = getline("PROMPT>>>> ");
- X gl_histadd(p);
- X fputs(p, stdout);
- X } while (*p != 0);
- }
- X
- In order to allow the main program to have additional access to the buffer,
- to implement things such as completion or auto-indent modes, three
- function pointers can be bound to user functions to modify the buffer as
- described below. By default gl_in_hook and gl_out_hook are set to NULL,
- and gl_tab_hook is bound to a function that inserts spaces until the next
- logical tab stop is reached. The user can reassign any of these pointers
- to other functions. Each of the functions bound to these hooks receives
- the current buffer as the first argument, and must return the location of
- the leftmost change made in the buffer. If the buffer isn't modified the
- functions should return -1. When the hook function returns the screen is
- updated to reflect any changes made by the user function.
- X
- int (*gl_in_hook)(char *buf)
- X
- X If gl_in_hook is non-NULL the function is called each time a new
- X buffer is loaded. It is called when getline is entered, with an
- X empty buffer, it is called each time a new buffer is loaded from
- X the history with ^P or ^N, and it is called when an incremental
- X search string is accepted (when the search is terminated). The
- X buffer can be modified and will be redrawn upon return to getline().
- X
- int (*gl_out_hook)(char *buf)
- X
- X If gl_out_hook is non-NULL it is called when a line has been
- X completed by the user entering a newline or return. The buffer
- X handed to the hook does not yet have the newline appended. If the
- X buffer is modified the screen is redrawn before getline returns the
- X buffer to the caller.
- X
- int (*gl_tab_hook)(char *buf, int prompt_width, int *cursor_loc)
- X
- X If gl_tab_hook is non-NULL, it is called whenever a tab is typed.
- X In addition to receiving the buffer, the current prompt width is
- X given (needed to do tabbing right) and a pointer to the cursor
- X offset is given, where a 0 offset means the first character in the
- X line. Not only does the cursor_loc tell the programmer where the
- X TAB was received, but it can be reset so that the cursor will end
- X up at the specified location after the screen is redrawn.
- SHAR_EOF
- chmod 0644 README ||
- echo 'restore of README failed'
- Wc_c="`wc -c < 'README'`"
- test 8280 -eq "$Wc_c" ||
- echo 'README: original size 8280, current size' "$Wc_c"
- fi
- # ============= CHANGES ==============
- if test -f 'CHANGES' -a X"$1" != X"-c"; then
- echo 'x - skipping CHANGES (File already exists)'
- else
- echo 'x - extracting CHANGES (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'CHANGES' &&
- Changes from last release (see the README file for more details):
- X
- * The user no longer calls gl_init() and gl_cleanup(), getline() sets
- X required terminal modes on entry and resets before returning. This
- X was necessary to capture changes in terminal modes that the main
- X program might be making.
- * Getline() now looks to see which characters are bound to signal
- X generation, and when these characters are seen getline() resets
- X terminal modes before passing on the signal. If the signal handler
- X returns to getline(), the screen is automatically updated and editing
- X can continue.
- * The toggle key for overwrite mode has been moved from ^G to ^O
- * All code is now classic rather than ANSI C, so any compiler should
- X be able to handle it.
- * ^Y now yanks back previously kill'ed (^K) text starting at the
- X current location.
- * ^R/^S begin reverse and forward incremental searches through the
- X history list.
- * The programmer must add buffers onto the history list by calling
- X gl_addhist(char *buffer). This function makes copies of the buffer
- X and adds them to the history list if the buffer is not a blank line
- X and if the buffer is different than the last item saved (so the
- X program need not check for these conditions)
- * The main program can specify the screen width to use with a call to
- X gl_setwidth(int width)
- * Getline now insists that both the input and output are connected to
- X a terminal. If this is not the case, an error message is written and
- X the program is terminated. The main program should check for this
- X case and use buffered IO (stdio) for non-interactive sessions.
- X
- SHAR_EOF
- chmod 0644 CHANGES ||
- echo 'restore of CHANGES failed'
- Wc_c="`wc -c < 'CHANGES'`"
- test 1608 -eq "$Wc_c" ||
- echo 'CHANGES: original size 1608, current size' "$Wc_c"
- fi
- # ============= getline.c ==============
- if test -f 'getline.c' -a X"$1" != X"-c"; then
- echo 'x - skipping getline.c (File already exists)'
- else
- echo 'x - extracting getline.c (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'getline.c' &&
- #ifndef lint
- static char rcsid[] =
- "$Id: getline.c,v 3.1 1992/02/11 16:15:16 thewalt Exp thewalt $";
- static char *copyright = "Copyright (C) 1991, 1992, Chris Thewalt";
- #endif
- X
- /*
- X * Copyright (C) 1991, 1992 by Chris Thewalt (thewalt@ce.berkeley.edu)
- X *
- X * Permission to use, copy, modify, and distribute this software
- X * for any purpose and without fee is hereby granted, provided
- X * that the above copyright notices appear in all copies and that both the
- X * copyright notice and this permission notice appear in supporting
- X * documentation. This software is provided "as is" without express or
- X * implied warranty.
- X */
- X
- static int gl_tab(); /* forward reference needed for gl_tab_hook */
- X
- /********************* exported interface ********************************/
- X
- char *getline(); /* read a line of input */
- void gl_setwidth(); /* specify width of screen */
- void gl_histadd(); /* adds entries to hist */
- X
- int (*gl_in_hook)() = 0;
- int (*gl_out_hook)() = 0;
- int (*gl_tab_hook)() = gl_tab;
- X
- /******************** imported interface *********************************/
- X
- #include <string.h>
- #include <ctype.h>
- #include <errno.h>
- #include <signal.h>
- X
- extern int isatty();
- extern void *malloc();
- extern void free();
- extern int kill();
- X
- /******************** internal interface *********************************/
- X
- #define BUF_SIZE 1024
- X
- static int gl_init_done = -1; /* terminal mode flag */
- static int gl_termw = 80; /* actual terminal width */
- static int gl_scroll = 27; /* width of EOL scrolling region */
- static int gl_width = 0; /* net size available for input */
- static int gl_extent = 0; /* how far to redraw, 0 means all */
- static int gl_overwrite = 0; /* overwrite mode */
- static int gl_pos, gl_cnt = 0; /* position and size of input */
- static char gl_buf[BUF_SIZE]; /* input buffer */
- static char gl_killbuf[BUF_SIZE]=""; /* killed text */
- static char *gl_prompt; /* to save the prompt string */
- static char gl_intrc = 0; /* keyboard SIGINT char */
- static char gl_quitc = 0; /* keyboard SIGQUIT char */
- static char gl_suspc = 0; /* keyboard SIGTSTP char */
- static char gl_dsuspc = 0; /* delayed SIGTSTP char */
- static int gl_search_mode = 0; /* search mode flag */
- X
- static void gl_init(); /* prepare to edit a line */
- static void gl_cleanup(); /* to undo gl_init */
- static void gl_char_init(); /* get ready for no echo input */
- static void gl_char_cleanup(); /* undo gl_char_init */
- X
- static void gl_addchar(); /* install specified char */
- static void gl_del(); /* del, either left (-1) or cur (0) */
- static void gl_error(); /* write error msg and die */
- static void gl_fixup(); /* fixup state variables and screen */
- static int gl_getc(); /* read one char from terminal */
- static void gl_kill(); /* delete to EOL */
- static void gl_newline(); /* handle \n or \r */
- static void gl_putc(); /* write one char to terminal */
- static void gl_puts(); /* write a line to terminal */
- static void gl_redraw(); /* issue \n and redraw all */
- static void gl_transpose(); /* transpose two chars */
- static void gl_yank(); /* yank killed text */
- X
- static void hist_init(); /* initializes hist pointers */
- static char *hist_next(); /* return ptr to next item */
- static char *hist_prev(); /* return ptr to prev item */
- static char *hist_save(); /* makes copy of a string, without NL */
- X
- static void search_addchar(); /* increment search string */
- static void search_term(); /* reset with current contents */
- static void search_back(); /* look back for current string */
- static void search_forw(); /* look forw for current string */
- X
- /************************ nonportable part *********************************/
- X
- extern int write();
- extern void exit();
- X
- #ifdef MSDOS
- #include <bios.h>
- #endif
- X
- #ifdef unix
- extern int read();
- extern int ioctl();
- #include <sys/ioctl.h>
- X
- #ifdef TIOCGETP /* use BSD interface if possible */
- #include <sgtty.h>
- struct sgttyb new_tty, old_tty;
- struct tchars tch;
- struct ltchars ltch;
- #else
- #ifdef SIGTSTP /* need POSIX interface to handle SUSP */
- #include <termios.h>
- struct termios new_termios, old_termios;
- #else /* use SYSV interface */
- #include <termio.h>
- struct termio new_termio, old_termio;
- #endif
- #endif
- #endif /* unix */
- X
- #ifdef vms
- #include <descrip.h>
- #include <ttdef.h>
- #include <iodef.h>
- #include unixio
- X
- static int setbuff[2]; /* buffer to set terminal attributes */
- static short chan = -1; /* channel to terminal */
- struct dsc$descriptor_s descrip; /* VMS descriptor */
- #endif
- X
- static void
- gl_char_init() /* turn off input echo */
- {
- #ifdef unix
- #ifdef TIOCGETP /* BSD */
- X ioctl(0, TIOCGETC, &tch);
- X ioctl(0, TIOCGLTC, <ch);
- X gl_intrc = tch.t_intrc;
- X gl_quitc = tch.t_quitc;
- X gl_suspc = ltch.t_suspc;
- X gl_dsuspc = ltch.t_dsuspc;
- X ioctl(0, TIOCGETP, &old_tty);
- X new_tty = old_tty;
- X new_tty.sg_flags |= RAW;
- X new_tty.sg_flags &= ~ECHO;
- X ioctl(0, TIOCSETP, &new_tty);
- #else
- #ifdef SIGTSTP /* POSIX */
- X tcgetattr(0, &old_termios);
- X gl_intrc = old_termios.c_cc[VINTR];
- X gl_quitc = old_termios.c_cc[VQUIT];
- #ifdef VSUSP
- X gl_suspc = old_termios.c_cc[VSUSP];
- #endif
- #ifdef VDSUSP
- X gl_dsuspc = old_termios.c_cc[VDSUSP];
- #endif
- X new_termios = old_termios;
- X new_termios.c_iflag &= ~(BRKINT|ISTRIP|IXON|IXOFF);
- X new_termios.c_iflag |= (IGNBRK|IGNPAR);
- X new_termios.c_lflag &= ~(ICANON|ISIG|IEXTEN|ECHO);
- X new_termios.c_cc[VMIN] = 1;
- X new_termios.c_cc[VTIME] = 0;
- X ioctl(0, TCSETA, &new_termios);
- #else /* SYSV */
- X ioctl(0, TCGETA, &old_termio);
- X gl_intrc = old_termio.c_cc[VINTR];
- X gl_quitc = old_termio.c_cc[VQUIT];
- X new_termio = old_termio;
- X new_termio.c_iflag &= ~(BRKINT|ISTRIP|IXON|IXOFF);
- X new_termio.c_iflag |= (IGNBRK|IGNPAR);
- X new_termio.c_lflag &= ~(ICANON|ISIG|ECHO);
- X new_termio.c_cc[VMIN] = 1;
- X new_termio.c_cc[VTIME] = 0;
- X ioctl(0, TCSETA, &new_termio);
- #endif
- #endif
- #endif /* unix */
- X
- #ifdef vms
- X descrip.dsc$w_length = strlen("tt:");
- X descrip.dsc$b_dtype = DSC$K_DTYPE_T;
- X descrip.dsc$b_class = DSC$K_CLASS_S;
- X descrip.dsc$a_pointer = "tt:";
- X (void)sys$assign(&descrip,&chan,0,0);
- X (void)sys$qiow(0,chan,IO$_SENSEMODE,0,0,0,setbuff,8,0,0,0,0);
- X setbuff[1] |= TT$M_NOECHO;
- X (void)sys$qiow(0,chan,IO$_SETMODE,0,0,0,setbuff,8,0,0,0,0);
- #endif /* vms */
- }
- X
- static void
- gl_char_cleanup() /* undo effects of gl_char_init */
- {
- #ifdef unix
- #ifdef TIOCSETP /* BSD */
- X ioctl(0, TIOCSETP, &old_tty);
- #else
- #ifdef SIGTSTP /* POSIX */
- X tcsetattr(0, TCSANOW, &old_termios);
- #else /* SYSV */
- X ioctl(0, TCSETA, &old_termio);
- #endif
- #endif
- #endif /* unix */
- X
- #ifdef vms
- X setbuff[1] &= ~TT$M_NOECHO;
- X (void)sys$qiow(0,chan,IO$_SETMODE,0,0,0,setbuff,8,0,0,0,0);
- X sys$dassgn(chan);
- X chan = -1;
- #endif
- }
- X
- static int
- gl_getc()
- /* get a character without echoing it to screen */
- {
- X int c;
- X char ch;
- X
- #ifdef unix
- X c = (read(0, &ch, 1) > 0)? ch : -1;
- #endif
- #ifdef MSDOS
- X c = _bios_keybrd(_NKEYBRD_READ);
- X if ((c & 0377) == 224) {
- X switch (c = (c >> 8) & 0377) {
- X case 72: c = 16; /* up -> ^P */
- X break;
- X case 80: c = 14; /* down -> ^N */
- X break;
- X case 75: c = 2; /* left -> ^B */
- X break;
- X case 77: c = 6; /* right -> ^F */
- X break;
- X default: c = 0; /* make it garbage */
- X }
- X } else {
- X c = c & 0377;
- X }
- #endif
- #ifdef vms
- X if(chan < 0) {
- X c='\0';
- X }
- X (void)sys$qiow(0,chan,IO$_TTYREADALL,0,0,0,&c,1,0,0,0,0);
- X c &= 0177; /* get a char */
- #endif
- X return c;
- }
- X
- static void
- gl_putc(c)
- int c;
- {
- X char ch = c;
- X
- X write(1, &ch, 1);
- #ifdef unix
- #ifdef TIOCSETP /* BSD in RAW mode, map NL to NL,CR */
- X if (ch == '\n') {
- X ch = '\r';
- X write(1, &ch, 1);
- X }
- #endif
- #endif
- }
- X
- /******************** fairly portable part *********************************/
- X
- static void
- gl_puts(buf)
- char *buf;
- {
- X int len = strlen(buf);
- X
- X write(1, buf, len);
- }
- X
- static void
- gl_error(buf)
- char *buf;
- {
- X int len = strlen(buf);
- X
- X gl_cleanup();
- X write(2, buf, len);
- X exit(1);
- }
- X
- static void
- gl_init()
- /* set up variables and terminal */
- {
- X if (gl_init_done < 0) { /* -1 only on startup */
- X hist_init();
- X }
- X if (isatty(0) == 0 || isatty(1) == 0)
- X gl_error("\n*** Error: getline(): not interactive, use stdio.\n");
- X gl_char_init();
- X gl_init_done = 1;
- }
- X
- static void
- gl_cleanup()
- /* undo effects of gl_init, as necessary */
- {
- X if (gl_init_done > 0)
- X gl_char_cleanup();
- X gl_init_done = 0;
- }
- X
- void
- gl_setwidth(w)
- int w;
- {
- X if (w > 20) {
- X gl_termw = w;
- X gl_scroll = w / 3;
- X } else {
- X gl_error("\n*** Error: minimum screen width is 21\n");
- X }
- }
- X
- char *
- getline(prompt)
- char *prompt;
- {
- X int c, loc, tmp;
- X int sig;
- X
- X gl_init();
- X gl_prompt = (prompt)? prompt : "";
- X gl_buf[0] = 0;
- X if (gl_in_hook)
- X gl_in_hook(gl_buf);
- X gl_fixup(gl_prompt, -2, BUF_SIZE);
- X while ((c = gl_getc()) >= 0) {
- X gl_extent = 0; /* reset to full extent */
- X if (isprint(c)) {
- X if (gl_search_mode)
- X search_addchar(c);
- X else
- X gl_addchar(c);
- X } else {
- X if (gl_search_mode) {
- X if (c == '\033' || c == '\016' || c == '\020') {
- X search_term();
- X c = 0; /* ignore the character */
- X } else if (c == '\010' || c == '\177') {
- X search_addchar(-1); /* unwind search string */
- X c = 0;
- X } else if (c != '\022' && c != '\023') {
- X search_term(); /* terminate and handle char */
- X }
- X }
- X switch (c) {
- X case '\n': case '\r': /* newline */
- X gl_newline();
- X gl_cleanup();
- X return gl_buf;
- X /*NOTREACHED*/
- X break;
- X case '\001': gl_fixup(gl_prompt, -1, 0); /* ^A */
- X break;
- X case '\002': gl_fixup(gl_prompt, -1, gl_pos-1); /* ^B */
- X break;
- X case '\004': /* ^D */
- X if (gl_cnt == 0) {
- X gl_buf[0] = 0;
- X gl_cleanup();
- X gl_putc('\n');
- X return gl_buf;
- X } else {
- X gl_del(0);
- X }
- X break;
- X case '\005': gl_fixup(gl_prompt, -1, gl_cnt); /* ^E */
- X break;
- X case '\006': gl_fixup(gl_prompt, -1, gl_pos+1); /* ^F */
- X break;
- X case '\010': case '\177': gl_del(-1); /* ^H and DEL */
- X break;
- X case '\t': /* TAB */
- X if (gl_tab_hook) {
- X tmp = gl_pos;
- X loc = gl_tab_hook(gl_buf, strlen(gl_prompt), &tmp);
- X if (loc >= 0 || tmp != gl_pos)
- X gl_fixup(gl_prompt, loc, tmp);
- X }
- X break;
- X case '\013': gl_kill(); /* ^K */
- X break;
- X case '\014': gl_redraw(); /* ^L */
- X break;
- X case '\016': /* ^N */
- X strcpy(gl_buf, hist_next());
- X if (gl_in_hook)
- X gl_in_hook(gl_buf);
- X gl_fixup(gl_prompt, 0, BUF_SIZE);
- X break;
- X case '\017': gl_overwrite = !gl_overwrite; /* ^O */
- X break;
- X case '\020': /* ^P */
- X strcpy(gl_buf, hist_prev());
- X if (gl_in_hook)
- X gl_in_hook(gl_buf);
- X gl_fixup(gl_prompt, 0, BUF_SIZE);
- X break;
- X case '\022': search_back(1); /* ^R */
- X break;
- X case '\023': search_forw(1); /* ^S */
- X break;
- X case '\024': gl_transpose(); /* ^T */
- X break;
- X case '\031': gl_yank(); /* ^Y */
- X break;
- X case '\033': /* ansi arrow keys */
- X c = gl_getc();
- X if (c == '[') {
- X switch(c = gl_getc()) {
- X case 'A': /* up */
- X strcpy(gl_buf, hist_prev());
- X if (gl_in_hook)
- X gl_in_hook(gl_buf);
- X gl_fixup(gl_prompt, 0, BUF_SIZE);
- X break;
- X case 'B': /* down */
- X strcpy(gl_buf, hist_next());
- X if (gl_in_hook)
- X gl_in_hook(gl_buf);
- X gl_fixup(gl_prompt, 0, BUF_SIZE);
- X break;
- X case 'C': gl_fixup(gl_prompt, -1, gl_pos+1); /* right */
- X break;
- X case 'D': gl_fixup(gl_prompt, -1, gl_pos-1); /* left */
- X break;
- X default: gl_putc('\007'); /* who knows */
- X break;
- X }
- X } else
- X gl_putc('\007');
- X break;
- X default: /* check for a terminal signal */
- #ifdef unix
- X if (c > 0) { /* ignore 0 (reset above) */
- X sig = 0;
- #ifdef SIGINT
- X if (c == gl_intrc)
- X sig = SIGINT;
- #endif
- #ifdef SIGQUIT
- X if (c == gl_quitc)
- X sig = SIGQUIT;
- #endif
- #ifdef SIGTSTP
- X if (c == gl_suspc || c == gl_dsuspc)
- X sig = SIGTSTP;
- #endif
- X if (sig != 0) {
- X gl_cleanup();
- X kill(0, sig);
- X gl_init();
- X gl_redraw();
- X c = 0;
- X }
- X }
- #endif /* unix */
- X if (c > 0)
- X gl_putc('\007');
- X break;
- X }
- X }
- X }
- X gl_cleanup();
- X gl_buf[0] = 0;
- X return gl_buf;
- }
- X
- static void
- gl_addchar(c)
- int c;
- /* adds the character c to the input buffer at current location */
- {
- X int i;
- X
- X if (gl_cnt >= BUF_SIZE - 1)
- X gl_error("\n*** Error: getline(): input buffer overflow\n");
- X if (gl_overwrite == 0 || gl_pos == gl_cnt) {
- X for (i=gl_cnt; i >= gl_pos; i--)
- X gl_buf[i+1] = gl_buf[i];
- X gl_buf[gl_pos] = c;
- X gl_fixup(gl_prompt, gl_pos, gl_pos+1);
- X } else {
- X gl_buf[gl_pos] = c;
- X gl_extent = 1;
- X gl_fixup(gl_prompt, gl_pos, gl_pos+1);
- X }
- }
- X
- static void
- gl_yank()
- /* adds the kill buffer to the input buffer at current location */
- {
- X int i, len;
- X
- X len = strlen(gl_killbuf);
- X if (len > 0) {
- X if (gl_overwrite == 0) {
- X if (gl_cnt + len >= BUF_SIZE - 1)
- X gl_error("\n*** Error: getline(): input buffer overflow\n");
- X for (i=gl_cnt; i >= gl_pos; i--)
- X gl_buf[i+len] = gl_buf[i];
- X for (i=0; i < len; i++)
- X gl_buf[gl_pos+i] = gl_killbuf[i];
- X gl_fixup(gl_prompt, gl_pos, gl_pos+len);
- X } else {
- X if (gl_pos + len > gl_cnt) {
- X if (gl_pos + len >= BUF_SIZE - 1)
- X gl_error("\n*** Error: getline(): input buffer overflow\n");
- X gl_buf[gl_pos + len] = 0;
- X }
- X for (i=0; i < len; i++)
- X gl_buf[gl_pos+i] = gl_killbuf[i];
- X gl_extent = len;
- X gl_fixup(gl_prompt, gl_pos, gl_pos+len);
- X }
- X } else
- X gl_putc('\007');
- }
- X
- static void
- gl_transpose()
- /* switch character under cursor and to left of cursor */
- {
- X int c;
- X
- X if (gl_pos > 0 && gl_cnt > gl_pos) {
- X c = gl_buf[gl_pos-1];
- X gl_buf[gl_pos-1] = gl_buf[gl_pos];
- X gl_buf[gl_pos] = c;
- X gl_extent = 2;
- X gl_fixup(gl_prompt, gl_pos-1, gl_pos);
- X } else
- X gl_putc('\007');
- }
- X
- static void
- gl_newline()
- /*
- X * Cleans up entire line before returning to caller. A \n is appended.
- X * If line longer than screen, we redraw starting at beginning
- X */
- {
- X int change = gl_cnt;
- X int len = gl_cnt;
- X int loc = gl_width - 5; /* shifts line back to start position */
- X
- X if (gl_cnt >= BUF_SIZE - 1)
- X gl_error("\n*** Error: getline(): input buffer overflow\n");
- X if (gl_out_hook) {
- X change = gl_out_hook(gl_buf);
- X len = strlen(gl_buf);
- X }
- X if (loc > len)
- X loc = len;
- X gl_fixup(gl_prompt, change, loc); /* must do this before appending \n */
- X gl_buf[len] = '\n';
- X gl_buf[len+1] = '\0';
- X gl_putc('\n');
- }
- X
- static void
- gl_del(loc)
- int loc;
- /*
- X * Delete a character. The loc variable can be:
- X * -1 : delete character to left of cursor
- X * 0 : delete character under cursor
- X */
- {
- X int i;
- X
- X if ((loc == -1 && gl_pos > 0) || (loc == 0 && gl_pos < gl_cnt)) {
- X for (i=gl_pos+loc; i < gl_cnt; i++)
- X gl_buf[i] = gl_buf[i+1];
- X gl_fixup(gl_prompt, gl_pos+loc, gl_pos+loc);
- X } else
- X gl_putc('\007');
- }
- X
- static void
- gl_kill()
- /* delete from current position to the end of line */
- {
- X if (gl_pos < gl_cnt) {
- X strcpy(gl_killbuf, gl_buf + gl_pos);
- X gl_buf[gl_pos] = '\0';
- X gl_fixup(gl_prompt, gl_pos, gl_pos);
- X } else
- X gl_putc('\007');
- }
- X
- static void
- gl_redraw()
- /* emit a newline, reset and redraw prompt and current input line */
- {
- X if (gl_init_done > 0) {
- X gl_putc('\n');
- X gl_fixup(gl_prompt, -2, gl_pos);
- X }
- }
- X
- static void
- gl_fixup(prompt, change, cursor)
- char *prompt;
- int change, cursor;
- /*
- X * This function is used both for redrawing when input changes or for
- X * moving within the input line. The parameters are:
- X * prompt: compared to last_prompt[] for changes;
- X * change : the index of the start of changes in the input buffer,
- X * with -1 indicating no changes, -2 indicating we're on
- X * a new line, redraw everything.
- X * cursor : the desired location of the cursor after the call.
- X * A value of BUF_SIZE can be used to indicate the cursor should
- X * move just past the end of the input line.
- X */
- {
- X static int gl_shift; /* index of first on screen character */
- X static int off_right; /* true if more text right of screen */
- X static int off_left; /* true if more text left of screen */
- X static char last_prompt[80] = "";
- X int left = 0, right = -1; /* bounds for redraw */
- X int pad; /* how much to erase at end of line */
- X int backup; /* how far to backup before fixing */
- X int new_shift; /* value of shift based on cursor */
- X int extra; /* adjusts when shift (scroll) happens */
- X int i;
- X int new_right = -1; /* alternate right bound, using gl_extent */
- X int l1, l2;
- X
- X if (change == -2) { /* reset */
- X gl_pos = gl_cnt = gl_shift = off_right = off_left = 0;
- X gl_puts(prompt);
- X strcpy(last_prompt, prompt);
- X change = 0;
- X gl_width = gl_termw - strlen(prompt);
- X } else if (strcmp(prompt, last_prompt) != 0) {
- X l1 = strlen(last_prompt);
- X l2 = strlen(prompt);
- X gl_cnt = gl_cnt + l1 - l2;
- X strcpy(last_prompt, prompt);
- X backup = gl_pos - gl_shift + l1;
- X for (i=0; i < backup; i++)
- X gl_putc('\b');
- X gl_puts(prompt);
- X gl_pos = gl_shift;
- X gl_width = gl_termw - l2;
- X change = 0;
- X }
- X pad = (off_right)? gl_width - 1 : gl_cnt - gl_shift; /* old length */
- X backup = gl_pos - gl_shift;
- X if (change >= 0) {
- X gl_cnt = strlen(gl_buf);
- X if (change > gl_cnt)
- X change = gl_cnt;
- X }
- X if (cursor > gl_cnt) {
- X if (cursor != BUF_SIZE) /* BUF_SIZE means end of line */
- X gl_putc('\007');
- X cursor = gl_cnt;
- X }
- X if (cursor < 0) {
- X gl_putc('\007');
- X cursor = 0;
- X }
- X if (off_right || (off_left && cursor < gl_shift + gl_width - gl_scroll / 2))
- X extra = 2; /* shift the scrolling boundary */
- X else
- X extra = 0;
- X new_shift = cursor + extra + gl_scroll - gl_width;
- X if (new_shift > 0) {
- X new_shift /= gl_scroll;
- X new_shift *= gl_scroll;
- X } else
- X new_shift = 0;
- X if (new_shift != gl_shift) { /* scroll occurs */
- X gl_shift = new_shift;
- X off_left = (gl_shift)? 1 : 0;
- X off_right = (gl_cnt > gl_shift + gl_width - 1)? 1 : 0;
- X left = gl_shift;
- X new_right = right = (off_right)? gl_shift + gl_width - 2 : gl_cnt;
- X } else if (change >= 0) { /* no scroll, but text changed */
- X if (change < gl_shift + off_left) {
- X left = gl_shift;
- X } else {
- X left = change;
- X backup = gl_pos - change;
- X }
- X off_right = (gl_cnt > gl_shift + gl_width - 1)? 1 : 0;
- X right = (off_right)? gl_shift + gl_width - 2 : gl_cnt;
- X new_right = (gl_extent && (right > left + gl_extent))?
- X left + gl_extent : right;
- X }
- X pad -= (off_right)? gl_width - 1 : gl_cnt - gl_shift;
- X pad = (pad < 0)? 0 : pad;
- X if (left <= right) { /* clean up screen */
- X for (i=0; i < backup; i++)
- X gl_putc('\b');
- X if (left == gl_shift && off_left) {
- X gl_putc('$');
- X left++;
- X }
- X for (i=left; i < new_right; i++)
- X gl_putc(gl_buf[i]);
- X gl_pos = new_right;
- X if (off_right && new_right == right) {
- X gl_putc('$');
- X gl_pos++;
- X } else {
- X for (i=0; i < pad; i++) /* erase remains of prev line */
- X gl_putc(' ');
- X gl_pos += pad;
- X }
- X }
- X i = gl_pos - cursor; /* move to final cursor location */
- X if (i > 0) {
- X while (i--)
- X gl_putc('\b');
- X } else {
- X for (i=gl_pos; i < cursor; i++)
- X gl_putc(gl_buf[i]);
- X }
- X gl_pos = cursor;
- }
- X
- static int
- gl_tab(buf, offset, loc)
- char *buf;
- int offset;
- int *loc;
- /* default tab handler, acts like tabstops every 8 cols */
- {
- X int i, count, len;
- X
- X len = strlen(buf);
- X count = 8 - (offset + *loc) % 8;
- X for (i=len; i >= *loc; i--)
- X buf[i+count] = buf[i];
- X for (i=0; i < count; i++)
- X buf[*loc+i] = ' ';
- X i = *loc;
- X *loc = i + count;
- X return i;
- }
- X
- /******************* History stuff **************************************/
- X
- #ifndef HIST_SIZE
- #define HIST_SIZE 100
- #endif
- X
- static int hist_pos = 0, hist_last = 0;
- static char *hist_buf[HIST_SIZE];
- X
- static void
- hist_init()
- {
- X int i;
- X
- X hist_buf[0] = "";
- X for (i=1; i < HIST_SIZE; i++)
- X hist_buf[i] = (char *)0;
- }
- X
- void
- gl_histadd(buf)
- char *buf;
- {
- X static char *prev = 0;
- X char *p = buf;
- X int len;
- X
- X while (*p == ' ' || *p == '\t' || *p == '\n')
- X p++;
- X if (*p) {
- X len = strlen(buf);
- X if (strchr(p, '\n')) /* previously line already has NL stripped */
- X len--;
- X if (prev == 0 || strlen(prev) != len ||
- X strncmp(prev, buf, len) != 0) {
- X hist_buf[hist_last] = hist_save(buf);
- X prev = hist_buf[hist_last];
- X hist_last = (hist_last + 1) % HIST_SIZE;
- X if (hist_buf[hist_last] && *hist_buf[hist_last]) {
- X free(hist_buf[hist_last]);
- X }
- X hist_buf[hist_last] = "";
- X }
- X }
- X hist_pos = hist_last;
- }
- X
- static char *
- hist_prev()
- /* loads previous hist entry into input buffer, sticks on first */
- {
- X char *p = 0;
- X int next = (hist_pos - 1 + HIST_SIZE) % HIST_SIZE;
- X
- X if (hist_buf[hist_pos] != 0 && next != hist_last) {
- X hist_pos = next;
- X p = hist_buf[hist_pos];
- X }
- X if (p == 0) {
- X p = "";
- X gl_putc('\007');
- X }
- X return p;
- }
- X
- static char *
- hist_next()
- /* loads next hist entry into input buffer, clears on last */
- {
- X char *p = 0;
- X
- X if (hist_pos != hist_last) {
- X hist_pos = (hist_pos+1) % HIST_SIZE;
- X p = hist_buf[hist_pos];
- X }
- X if (p == 0) {
- X p = "";
- X gl_putc('\007');
- X }
- X return p;
- }
- X
- static char *
- hist_save(p)
- char *p;
- /* makes a copy of the string */
- {
- X char *s = 0;
- X int len = strlen(p);
- X char *nl = strchr(p, '\n');
- X
- X if (nl) {
- X if ((s = malloc(len)) != 0) {
- X strncpy(s, p, len-1);
- X s[len-1] = 0;
- X }
- X } else {
- X if ((s = malloc(len+1)) != 0) {
- X strcpy(s, p);
- X }
- X }
- X if (s == 0)
- X gl_error("\n*** Error: hist_save() failed on malloc\n");
- X return s;
- }
- X
- /******************* Search stuff **************************************/
- X
- static char search_prompt[101]; /* prompt includes search string */
- static char search_string[100];
- static int search_pos = 0; /* current location in search_string */
- static int search_forw_flg = 0; /* search direction flag */
- static int search_last = 0; /* last match found */
- X
- static void
- search_update(c)
- int c;
- {
- X if (c == 0) {
- X search_pos = 0;
- X search_string[0] = 0;
- X search_prompt[0] = '?';
- X search_prompt[1] = ' ';
- X search_prompt[2] = 0;
- X } else if (c > 0) {
- X search_string[search_pos] = c;
- X search_string[search_pos+1] = 0;
- X search_prompt[search_pos] = c;
- X search_prompt[search_pos+1] = '?';
- X search_prompt[search_pos+2] = ' ';
- X search_prompt[search_pos+3] = 0;
- X search_pos++;
- X } else {
- X if (search_pos > 0) {
- X search_pos--;
- X search_string[search_pos] = 0;
- X search_prompt[search_pos] = '?';
- X search_prompt[search_pos+1] = ' ';
- X search_prompt[search_pos+2] = 0;
- X } else {
- X gl_putc('\007');
- X hist_pos = hist_last;
- X }
- X }
- }
- X
- static void
- search_addchar(c)
- int c;
- {
- X char *loc;
- X
- X search_update(c);
- X if (c < 0) {
- X if (search_pos > 0) {
- X hist_pos = search_last;
- X } else {
- X gl_buf[0] = 0;
- X hist_pos = hist_last;
- X }
- X strcpy(gl_buf, hist_buf[hist_pos]);
- X }
- X if ((loc = strstr(gl_buf, search_string)) != 0) {
- X gl_fixup(search_prompt, 0, loc - gl_buf);
- X } else if (search_pos > 0) {
- X if (search_forw_flg) {
- X search_forw(0);
- X } else {
- X search_back(0);
- X }
- X } else {
- X gl_fixup(search_prompt, 0, 0);
- X }
- }
- X
- static void
- search_term()
- {
- X gl_search_mode = 0;
- X if (gl_buf[0] == 0) /* not found, reset hist list */
- X hist_pos = hist_last;
- X if (gl_in_hook)
- X gl_in_hook(gl_buf);
- X gl_fixup(gl_prompt, 0, gl_pos);
- }
- X
- static void
- search_back(new_search)
- int new_search;
- {
- X int found = 0;
- X char *p, *loc;
- X
- X search_forw_flg = 0;
- X if (gl_search_mode == 0) {
- X search_last = hist_pos = hist_last;
- X search_update(0);
- X gl_search_mode = 1;
- X gl_buf[0] = 0;
- X gl_fixup(search_prompt, 0, 0);
- X } else if (search_pos > 0) {
- X while (!found) {
- X p = hist_prev();
- X if (*p == 0) { /* not found, done looking */
- X gl_buf[0] = 0;
- X gl_fixup(search_prompt, 0, 0);
- X found = 1;
- X } else if ((loc = strstr(p, search_string)) != 0) {
- X strcpy(gl_buf, p);
- X gl_fixup(search_prompt, 0, loc - p);
- X if (new_search)
- X search_last = hist_pos;
- X found = 1;
- X }
- X }
- X } else {
- X gl_putc('\007');
- X }
- }
- X
- static void
- search_forw(new_search)
- int new_search;
- {
- X int found = 0;
- X char *p, *loc;
- X
- X search_forw_flg = 1;
- X if (gl_search_mode == 0) {
- X search_last = hist_pos = hist_last;
- X search_update(0);
- X gl_search_mode = 1;
- X gl_buf[0] = 0;
- X gl_fixup(search_prompt, 0, 0);
- X } else if (search_pos > 0) {
- X while (!found) {
- X p = hist_next();
- X if (*p == 0) { /* not found, done looking */
- X gl_buf[0] = 0;
- X gl_fixup(search_prompt, 0, 0);
- X found = 1;
- X } else if ((loc = strstr(p, search_string)) != 0) {
- X strcpy(gl_buf, p);
- X gl_fixup(search_prompt, 0, loc - p);
- X if (new_search)
- X search_last = hist_pos;
- X found = 1;
- X }
- X }
- X } else {
- X gl_putc('\007');
- X }
- }
- SHAR_EOF
- chmod 0644 getline.c ||
- echo 'restore of getline.c failed'
- Wc_c="`wc -c < 'getline.c'`"
- test 26245 -eq "$Wc_c" ||
- echo 'getline.c: original size 26245, current size' "$Wc_c"
- fi
- # ============= testgl.c ==============
- if test -f 'testgl.c' -a X"$1" != X"-c"; then
- echo 'x - skipping testgl.c (File already exists)'
- else
- echo 'x - extracting testgl.c (Text)'
- sed 's/^X//' << 'SHAR_EOF' > 'testgl.c' &&
- #include <stdio.h>
- X
- extern char *getline();
- extern void gl_histadd();
- extern void exit();
- X
- main()
- /*
- X * just echo user input lines, letting user edit them and move through
- X * history list
- X */
- {
- X char *p;
- X
- X do {
- X p = getline("PROMPT>>>> ");
- X gl_histadd(p);
- X fputs(p, stdout);
- X } while (*p != 0);
- }
- SHAR_EOF
- chmod 0644 testgl.c ||
- echo 'restore of testgl.c failed'
- Wc_c="`wc -c < 'testgl.c'`"
- test 309 -eq "$Wc_c" ||
- echo 'testgl.c: original size 309, current size' "$Wc_c"
- fi
- exit 0
-
- exit 0 # Just in case...
-