home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 January
/
usenetsourcesnewsgroupsinfomagicjanuary1994.iso
/
sources
/
misc
/
volume28
/
input-edit
/
part01
next >
Wrap
Text File
|
1992-02-23
|
43KB
|
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...