v25i056: input-edit - C input functions for line editing with history, Part01/01

39 views
Skip to first unread message

Chris Thewalt

unread,
Nov 12, 1991, 10:31:51 PM11/12/91
to
Submitted-by: the...@canuck.CE.Berkeley.EDU (Chris Thewalt)
Posting-number: Volume 25, Issue 56
Archive-name: input-edit/part01
Environment: UNIX, MS-DOS

environment: ANSI C, UNIX (sysv or bsd) or MSDOS with MSC
(Converting to K&R C should be fairly simple)

tested on: DECstation 5000, Ultrix 4.2 with gcc
Sun Sparc II, SunOS 4.1.1, gcc
SGI Iris, IRIX System V.3, cc
PC, DR DOS 5.0, MSC 6.0

description: The input-edit package can be used in programs that
want to provide an emacs style line editing cabability
with history. The function getline allows the user to
edit the current line and move through the history list
of lines previously typed and returns the buffer to the
caller when RETURN is entered. Long lines are handled
by horizontal scrolling. Does NOT use termcap, uses
only \b to move around.

The actual editing capabilites are a very small subset
of emacs commands, but then the package is very small
and quite portable. Get GNU readline if you need more
powerful editing.

Chris (the...@ce.berkeley.edu)
---------------------------------------------------------------------
#!/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 11/09/1991 20:30 UTC by the...@canuck.CE.Berkeley.EDU
# Source directory /usr/users/thewalt/et/gl
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 1276 -rw-r--r-- README
# 98 -rw-r--r-- Makefile
# 17056 -rw-r--r-- getline.c
# 293 -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' &&
Send bug reports, fixes and enhancements to:
Chris Thewalt (the...@ce.berkeley.edu)
11/9/91
X
A simple driver called testgl.c has been included, all it does is echo each
line of input that is read. The user can perform editing and history
manipulation before hitting RET.
X
The details of getline are all included as comments within getline.c
X
environment: ANSI C, UNIX (sysv or bsd) or MSDOS with MSC
X (Converting to K&R C should be fairly simple)
X
tested on: DECstation 5000, Ultrix 4.2 with gcc
X Sun Sparc II, SunOS 4.1.1, gcc
X SGI Iris, IRIX System V.3, cc
X PC, DR DOS 5.0, MSC 6.0
X
description: Getline can be used in programs that want to provide
X an emacs style line editing cabability with history.
X Getline allows the user to edit the current line
X and move through the history list of lines previously
X typed and returns the buffer to the caller when RETURN
X is entered. Long lines are handled by scrolling.
X Does NOT use termcap, uses only \b to move around.
X
X The actual editing capabilites are a very small subset
X of emacs commands, but then the package is very small
X and quite portable. Get GNU readline if you need more
X powerful editing.
SHAR_EOF
chmod 0644 README ||
echo 'restore of README failed'
Wc_c="`wc -c < 'README'`"
test 1276 -eq "$Wc_c" ||
echo 'README: original size 1276, current size' "$Wc_c"
fi
# ============= Makefile ==============
if test -f 'Makefile' -a X"$1" != X"-c"; then
echo 'x - skipping Makefile (File already exists)'
else
echo 'x - extracting Makefile (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'Makefile' &&
OBJS = testgl.o getline.o
X
testgl: $(OBJS)
X $(CC) -o testgl $(OBJS)
X
clean:
X rm -f $(OBJS) testgl
SHAR_EOF
chmod 0644 Makefile ||
echo 'restore of Makefile failed'
Wc_c="`wc -c < 'Makefile'`"
test 98 -eq "$Wc_c" ||
echo 'Makefile: original size 98, 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 1.8 1991/11/09 20:27:48 thewalt Exp thewalt $";
#endif
X
/*
X * Fairly portable (ANSI C), emacs style line editing input package.
X * This package uses \b to move, and \007 to ring the bell.
X * It uses a fixed screen width, as initialized in the gl_init() call,
X * and does not draw in the last location to avoid line wraps.
X * The only non-portable part is how to turn off character echoing.
X * This code works for *NIX of BSD or SYSV flavor, as well as MSDOS (MSC6.0).
X * No TERMCAP features are used, so long lines are scrolled on one line
X * rather than extending over several lines. The function getline
X * returns a pointer to a static buffer area which holds the input string,
X * including the newline. On EOF the first character is set equal to '\0'.
X * The caller supplies a desired prompt, as shown in the prototype:
X *
X * char *getline(char *prompt)
X *
X * Getline is different from GNU readline in that:
X * - getline has much more limited editing capabilities, but it
X * is also much smaller and doesn't need termcap.
X * - you don't free the buffer when done, since it is static
X * (make a copy yourself if you want one)
X * - the newline is appended to the buffer
X * - you don't add lines to history, it is done automatically.
X *
X * The function gl_init(int screen_width) should be called before
X * calling getline(char *prompt), and gl_cleanup(void) should be
X * called before before exiting. The function gl_redraw(void) may also
X * be called to redraw the screen (as is done when ^L or ^R are read).
X *
X * The editing keys are:
X * ^A, ^E - move to beginnning or end of line, respectively.
X * ^F, ^B - nondestructive move forward or back one location, respectively.
X * ^D - delete the character currently under the cursor, or
X * send EOF if no characters in the buffer.
X * ^H, DEL - delete character left of the cursor.
X * ^K - delete from cursor to end of line.
X * ^P, ^N - move through history, previous and next, respectively.
X * ^L, ^R - redraw the current line.
X * TAB - call user defined function if bound, or insert spaces
X * to get to next TAB stop (just past every 8th column).
X * NL, CR - places line on history list if nonblank, calls output
X * cleanup function if bound, appends newline and returns
X * to the caller.
X *
X * In addition, the caller can modify the buffer in certain ways, which
X * may be useful for things like auto-indent modes. There are three
X * function pointers which can be bound to user functions.
X * Each of these functions must return the index of the first location
X * at which the buffer was modified, or -1 if the buffer wasn't modified.
X * Each of the functions receive the current input buffer as the first
X * argument. The screen is automatically cleaned up if the buffer is changed.
X * The user is responsible for not writing beyond the end of the static
X * buffer. The function pointer prototypes are:
X *
X * int (*gl_in_hook)(char *buf)
X * - called on entry to getline, and each time a new history
X * string is loaded, from a ^P or ^N. Initally NULL.
X * int (*gl_out_hook)(char *buf)
X * - called when a \n or \r is read, before appending \n and
X * returning to caller. Initally NULL.
X * int (*gl_tab_hook)(char *buf, int offset, int *loc)
X * - called whenever a TAB is read. The second argument is
X * the current line offset due to the width of the prompt.
X * The third argument is a pointer to the variable holding the
X * current location in the buffer. The location may be reset
X * by the user to move the cursor when the call returns.
X * Initially a built in tabbing function is bound.
X *
X * Please send bug reports, fixes and enhancements to Chris Thewalt,
X * the...@ce.berkeley.edu
X */
X
static char *copyright = "Copyright (C) 1991, Chris Thewalt";
/*
X * Copyright (C) 1991 by Chris Thewalt
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
X
extern int isatty();
X
#define BUF_SIZE 1024
#define SCROLL 30
X
static int gl_init_done = 0; /* -1 is terminal, 1 is batch */
static int gl_screen = 80; /* screen width */
static int gl_width = 0; /* net size available for input */
static int gl_pos, gl_cnt = 0; /* position and size of input */
static char gl_buf[BUF_SIZE]; /* input buffer */
static char *gl_prompt; /* to save the prompt string */
X
void gl_init(int);
void gl_cleanup(void); /* to undo gl_init */
void gl_redraw(void); /* issue \n and redraw all */
static void gl_char_init(void); /* get ready for no echo input */
static void gl_char_cleanup(void); /* undo gl_char_init */
static int gl_getchar(void); /* read one char from stdin */
static void gl_addchar(int); /* install specified char */
static void gl_newline(void); /* handle \n or \r */
static void gl_fixup(int, int); /* fixup state variables and screen */
static void gl_del(int); /* del, either left (-1) or cur (0) */
static void gl_kill(void); /* delete to EOL */
static int gl_tab(char *, int, int *); /* default TAB handler */
X
static void hist_add(void); /* adds nonblank entries to hist */
static void hist_init(void); /* initializes hist pointers */
static void hist_next(void); /* copies next entry to input buf */
static void hist_prev(void); /* copies prev entry to input buf */
static char *hist_save(char *); /* makes copy of a string */
X
int (*gl_in_hook)(char *) = 0;
int (*gl_out_hook)(char *) = 0;
int (*gl_tab_hook)(char *, int, int *) = gl_tab;
X
/************************ nonportable part *********************************/
#ifdef MSDOS
#include <bios.h>
#endif
X
#ifdef unix
#include <sys/ioctl.h>
#ifndef TIOCGETP
#include <termio.h>
struct termio tty, old_tty;
#else
#include <sgtty.h>
struct sgttyb tty, old_tty;
#endif /* TIOCGETP */
extern int ioctl();
#endif /* unix */
X
static void
gl_char_init()
/* turn off input echo */
{
#ifdef unix
#ifdef TIOCGETP
X ioctl(0, TIOCGETP, &old_tty);
X tty = old_tty;
X tty.sg_flags |= CBREAK;
X tty.sg_flags &= ~ECHO;
X ioctl(0, TIOCSETP, &tty);
#else
X ioctl(0, TCGETA, &old_tty);
X tty = old_tty;
X tty.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
X tty.c_cc[VMIN] = 1;
X ioctl(0, TCSETA, &tty);
#endif
#endif /* unix */
}
X
static void
gl_char_cleanup()
/* undo effects of gl_char_init, as necessary */
{
#ifdef unix
#ifdef TIOCSETP
X ioctl(0, TIOCSETP, &old_tty);
#else
X ioctl(0, TCSETA, &old_tty);
#endif
#endif /* unix */
}
X
static int
gl_getchar()
/* get a character without echoing it to screen */
{
X int c;
X
#ifdef unix
X c = fgetc(stdin);
#endif
#ifdef MSDOS
X c = _bios_keybrd(_NKEYBRD_READ) & 0177; /* only using 7 bit ASCII */
#endif
X return c;
}
/******************** fairly portable part *********************************/
void
gl_init(int scrn_wdth)
/* set up variables and terminal */
{
X gl_screen = scrn_wdth;
X if (gl_init_done == 0) {
X hist_init();
X if (isatty(0) && isatty(1)) {
X setvbuf(stdin, (char *)0, _IONBF, 0);
X setvbuf(stdout, (char *)0, _IONBF, 0);
X gl_char_init();
X gl_init_done = -1; /* -1 means terminal */
X } else {
X gl_init_done = 1; /* 1 means batch */
X }
X }
X gl_pos = gl_cnt = 0;
}
X
void
gl_cleanup()
/* undo effects of gl_init, as necessary */
{
X if (gl_init_done == -1)
X gl_char_cleanup();
X gl_init_done = 0;
}
X
char *
getline(char *prompt)
{
X int c, loc, tmp;
X
X if (!gl_init_done)
X gl_init(80);
X gl_buf[0] = 0; /* used as end of input indicator */
X if (gl_init_done == 1) { /* no input editing, and no prompt output */
X fgets(gl_buf, BUF_SIZE, stdin);
X return gl_buf;
X }
X gl_fixup(-1, 0); /* this resets gl_fixup */
X gl_width = gl_screen - strlen(prompt);
X if (prompt == NULL)
X prompt = "";
X gl_prompt = prompt;
X gl_pos = gl_cnt = 0;
X fputs(prompt, stdout);
X if (gl_in_hook) {
X loc = gl_in_hook(gl_buf);
X if (loc >= 0)
X gl_fixup(0, BUF_SIZE);
X }
X while ((c = gl_getchar()) != EOF) {
X if (isprint(c)) {
X gl_addchar(c);
X } else {
X switch (c) {
X case '\n': case '\r': /* newline */
X gl_newline();
X return gl_buf;
X break;
X case '\001': gl_fixup(-1, 0); /* ^A */
X break;
X case '\002': gl_fixup(-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 fputc('\n', stdout);
X return gl_buf;
X } else {
X gl_del(0);
X }
X break;
X case '\005': gl_fixup(-1, gl_cnt); /* ^E */
X break;
X case '\006': gl_fixup(-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(loc, tmp);
X }
X break;
X case '\013': gl_kill(); /* ^K */
X break;
X case '\014': case '\022': gl_redraw(); /* ^L and ^R */
X break;
X case '\016': hist_next(); /* ^N */
X break;
X case '\020': hist_prev(); /* ^P */
X break;
X default:
X fputc('\007', stdout);
X break;
X }
X }
X }
X gl_cleanup();
X return gl_buf;
}
X
static void
gl_addchar(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 fprintf(stderr, "getline: input buffer overflow\n");
X exit(1);
X }
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_pos, gl_pos+1);
}
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 fprintf(stderr, "getline: input buffer overflow\n");
X exit(1);
X }
X hist_add(); /* only adds if nonblank */
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(change, loc); /* must do this before appending \n */
X gl_buf[len] = '\n';
X gl_buf[len+1] = '\0';
X fputc('\n', stdout);
}
X
static void
gl_del(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_pos+loc, gl_pos+loc);
X } else
X fputc('\007', stdout);
}
X
static void
gl_kill()
/* delete from current position to the end of line */
{
X if (gl_pos < gl_cnt) {
X gl_buf[gl_pos] = '\0';
X gl_fixup(gl_pos, gl_pos);
X } else
X fputc('\007', stdout);
}
X
void
gl_redraw()
/* emit a newline, reset and redraw prompt and current input line */
{
X if (gl_init_done == -1) {
X fputc('\n', stdout);
X fputs(gl_prompt, stdout);
X gl_pos = 0;
X gl_fixup(0, BUF_SIZE);
X }
}
X
static void
gl_fixup(int change, int cursor)
/*
X * This function is used both for redrawing when input changes or for
X * moving within the input line. The parameters are:
X * change : the index of the start of changes in the input buffer,
X * with -1 indicating no changes.
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 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
X if (change == -1 && cursor == 0 && gl_buf[0] == 0) { /* reset */
X gl_shift = off_right = off_left = 0;
X return;
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 fputc('\007', stdout);
X cursor = gl_cnt;
X }
X if (cursor < 0) {
X fputc('\007', stdout);
X cursor = 0;
X }
X if (off_right || off_left && cursor < gl_shift + gl_width - SCROLL / 2)
X extra = 2; /* shift the scrolling boundary */
X else
X extra = 0;
X new_shift = cursor + extra + SCROLL - gl_width;
X if (new_shift > 0) {
X new_shift /= SCROLL;
X new_shift *= 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 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 }
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 fputc('\b', stdout);
X if (left == gl_shift && off_left) {
X fputc('$', stdout);
X left++;
X }
X for (i=left; i < right; i++)
X fputc(gl_buf[i], stdout);
X if (off_right) {
X fputc('$', stdout);
X gl_pos = right + 1;
X } else {
X for (i=0; i < pad; i++) /* erase remains of prev line */
X fputc(' ', stdout);
X gl_pos = right + pad;
X }
X }
X i = gl_pos - cursor; /* move to final cursor location */
X if (i > 0) {
X while (i--)
X fputc('\b', stdout);
X } else {
X for (i=gl_pos; i < cursor; i++)
X fputc(gl_buf[i], stdout);
X }
X gl_pos = cursor;
}
X
static int
gl_tab(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
int hist_pos, hist_last;
char *hist_buf[HIST_SIZE];
X
static void
hist_init()
{
X int i;
X
X for (i=0; i < HIST_SIZE; i++)
X hist_buf[i] = (char *)NULL;
}
X
static void
hist_add()
{
X char *p = gl_buf;
X
X while (*p == ' ' || *p == '\t') /* only save nonblank line */
X p++;
X if (*p) {
X hist_buf[hist_last] = hist_save(gl_buf);
X hist_last = (hist_last + 1) % HIST_SIZE;
X if (hist_buf[hist_last]) { /* erase next location */
X free(hist_buf[hist_last]);
X hist_buf[hist_last] = NULL;
X }
X }
X hist_pos = hist_last;
}
X
static void
hist_prev()
/* loads previous hist entry into input buffer, sticks on first */
{
X int next;
X
X next = (hist_pos - 1 + HIST_SIZE) % HIST_SIZE;
X if (next != hist_last) {
X if (hist_buf[next]) {
X hist_pos = next;
X strcpy(gl_buf, hist_buf[hist_pos]);
X } else
X fputc('\007', stdout);
X } else
X fputc('\007', stdout);
X if (gl_in_hook)
X gl_in_hook(gl_buf);
X gl_fixup(0, BUF_SIZE);
}
X
static void
hist_next()
/* loads next hist entry into input buffer, clears on last */
{
X if (hist_pos != hist_last) {
X hist_pos = (hist_pos + 1) % HIST_SIZE;
X if (hist_buf[hist_pos]) {
X strcpy(gl_buf, hist_buf[hist_pos]);
X } else {
X gl_buf[0] = 0;
X }
X } else
X fputc('\007', stdout);
X if (gl_in_hook)
X gl_in_hook(gl_buf);
X gl_fixup(0, BUF_SIZE);
}
X
static char *
hist_save(char *p)
/* makes a copy of the string */
{
X char *s = NULL;
X
X if (p && ((s = malloc(strlen(p)+1)) != NULL)) {
X strcpy(s, p);
X }
X return s;
}
SHAR_EOF
chmod 0644 getline.c ||
echo 'restore of getline.c failed'
Wc_c="`wc -c < 'getline.c'`"
test 17056 -eq "$Wc_c" ||
echo 'getline.c: original size 17056, 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
char *getline(char *);
void gl_init(int), gl_cleanup(void);
X
main()
/*
X * just echo user input lines, letting user edit them and move through
X * history list
X */
{
X char *p;
X
X gl_init(80);
X while ((p = getline("> ")) && *p)
X fputs(p, stdout);
X gl_cleanup();
}
SHAR_EOF
chmod 0644 testgl.c ||
echo 'restore of testgl.c failed'
Wc_c="`wc -c < 'testgl.c'`"
test 293 -eq "$Wc_c" ||
echo 'testgl.c: original size 293, current size' "$Wc_c"
fi
exit 0

exit 0 # Just in case...
--
Kent Landfield INTERNET: ke...@sparky.IMD.Sterling.COM
Sterling Software, IMD UUCP: uunet!sparky!kent
Phone: (402) 291-8300 FAX: (402) 291-4362
Please send comp.sources.misc-related mail to ke...@uunet.uu.net.

Reply all
Reply to author
Forward
0 new messages