diff options
Diffstat (limited to 'src/hetio.c')
-rw-r--r-- | src/hetio.c | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/src/hetio.c b/src/hetio.c new file mode 100644 index 0000000..f7d175f --- /dev/null +++ b/src/hetio.c @@ -0,0 +1,397 @@ +/* + * Termios command line History and Editting for NetBSD sh (ash) + * Copyright (c) 1999 + * Main code: Adam Rogoyski <rogoyski@cs.utexas.edu> + * Etc: Dave Cinege <dcinege@psychosis.com> + * + * You may use this code as you wish, so long as the original author(s) + * are attributed in any redistributions of the source code. + * This code is 'as is' with no warranty. + * This code may safely be consumed by a BSD or GPL license. + * + * v 0.5 19990328 Initial release + * + * Future plans: Simple file and path name completion. (like BASH) + * + */ + +/* +Usage and Known bugs: + Terminal key codes are not extensive, and more will probably + need to be added. This version was created on Debian GNU/Linux 2.x. + Delete, Backspace, Home, End, and the arrow keys were tested + to work in an Xterm and console. Ctrl-A also works as Home. + Ctrl-E also works as End. Ctrl-D and Ctrl-U perform their respective + functions. The binary size increase is <3K. + + Editting will not display correctly for lines greater then the + terminal width. (more then one line.) However, history will. +*/ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <ctype.h> +#include <sys/ioctl.h> + +#include "input.h" +#include "output.h" + +#include "hetio.h" + + +#define MAX_HISTORY 15 /* Maximum length of the linked list for the command line history */ + +#define ESC 27 +#define DEL 127 + +static struct history *his_front = NULL; /* First element in command line list */ +static struct history *his_end = NULL; /* Last element in command line list */ +static struct termios old_term, new_term; /* Current termio and the previous termio before starting ash */ + +static int history_counter = 0; /* Number of commands in history list */ +static int reset_term = 0; /* Set to true if the terminal needs to be reset upon exit */ +static int hetio_inter = 0; + +struct history +{ + char *s; + struct history *p; + struct history *n; +}; + + +void input_delete (int); +void input_home (int *); +void input_end (int *, int); +void input_backspace (int *, int *); + + + +void hetio_init(void) +{ + hetio_inter = 1; +} + + +void hetio_reset_term(void) +{ + if (reset_term) + tcsetattr(1, TCSANOW, &old_term); +} + + +void setIO(struct termios *new, struct termios *old) /* Set terminal IO to canonical mode, and save old term settings. */ +{ + tcgetattr(0, old); + memcpy(new, old, sizeof(*new)); + new->c_cc[VMIN] = 1; + new->c_cc[VTIME] = 0; + new->c_lflag &= ~ICANON; /* unbuffered input */ + new->c_lflag &= ~ECHO; + tcsetattr(0, TCSANOW, new); +} + +void input_home(int *cursor) /* Command line input routines */ +{ + while (*cursor > 0) { + out1c('\b'); + --*cursor; + } + flushout(out1); +} + + +void input_delete(int cursor) +{ + int j = 0; + + memmove(parsenextc + cursor, parsenextc + cursor + 1, + BUFSIZ - cursor - 1); + for (j = cursor; j < (BUFSIZ - 1); j++) { + if (!*(parsenextc + j)) + break; + else + out1c(*(parsenextc + j)); + } + + out1str(" \b"); + + while (j-- > cursor) + out1c('\b'); + flushout(out1); +} + + +void input_end(int *cursor, int len) +{ + while (*cursor < len) { + out1str("\033[C"); + ++*cursor; + } + flushout(out1); +} + + +void +input_backspace(int *cursor, int *len) +{ + int j = 0; + + if (*cursor > 0) { + out1str("\b \b"); + --*cursor; + memmove(parsenextc + *cursor, parsenextc + *cursor + 1, + BUFSIZ - *cursor + 1); + + for (j = *cursor; j < (BUFSIZ - 1); j++) { + if (!*(parsenextc + j)) + break; + else + out1c(*(parsenextc + j)); + } + + out1str(" \b"); + + while (j-- > *cursor) + out1c('\b'); + + --*len; + flushout(out1); + } +} + +int hetio_read_input(int fd) +{ + int nr = 0; + + /* Are we an interactive shell? */ + if (!hetio_inter || fd) { + return -255; + } else { + int len = 0; + int j = 0; + int cursor = 0; + int break_out = 0; + int ret = 0; + char c = 0; + struct history *hp = his_end; + + if (!reset_term) { + setIO(&new_term, &old_term); + reset_term = 1; + } else { + tcsetattr(0, TCSANOW, &new_term); + } + + memset(parsenextc, 0, BUFSIZ); + + while (1) { + if ((ret = read(fd, &c, 1)) < 1) + return ret; + + switch (c) { + case 1: /* Control-A Beginning of line */ + input_home(&cursor); + break; + case 5: /* Control-E EOL */ + input_end(&cursor, len); + break; + case 4: /* Control-D */ + if (!len) + exitshell(0); + break; + case 21: /* Control-U */ + /* Return to begining of line. */ + for (; cursor > 0; cursor--) + out1c('\b'); + /* Erase old command. */ + for (j = 0; j < len; j++) { + /* + * Clear buffer while we're at + * it. + */ + parsenextc[j] = 0; + out1c(' '); + } + /* return to begining of line */ + for (; len > 0; len--) + out1c('\b'); + flushout(out1); + break; + case '\b': /* Backspace */ + case DEL: + input_backspace(&cursor, &len); + break; + case '\n': /* Enter */ + *(parsenextc + len++ + 1) = c; + out1c(c); + flushout(out1); + break_out = 1; + break; + case ESC: /* escape sequence follows */ + if ((ret = read(fd, &c, 1)) < 1) + return ret; + + if (c == '[' ) { /* 91 */ + if ((ret = read(fd, &c, 1)) < 1) + return ret; + + switch (c) { + case 'A': + if (hp && hp->p) { /* Up */ + hp = hp->p; + goto hop; + } + break; + case 'B': + if (hp && hp->n && hp->n->s) { /* Down */ + hp = hp->n; + goto hop; + } + break; + +hop: /* hop */ + len = strlen(parsenextc); + + for (; cursor > 0; cursor--) /* return to begining of line */ + out1c('\b'); + + for (j = 0; j < len; j++) /* erase old command */ + out1c(' '); + + for (; j > 0; j--) /* return to begining of line */ + out1c('\b'); + + strcpy (parsenextc, hp->s); /* write new command */ + len = strlen (hp->s); + out1str(parsenextc); + flushout(out1); + cursor = len; + break; + case 'C': /* Right */ + if (cursor < len) { + out1str("\033[C"); + cursor++; + flushout(out1); + } + break; + case 'D': /* Left */ + if (cursor > 0) { + out1str("\033[D"); + cursor--; + flushout(out1); + } + break; + case '3': /* Delete */ + if (cursor != len) { + input_delete(cursor); + len--; + } + break; + case '1': /* Home (Ctrl-A) */ + input_home(&cursor); + break; + case '4': /* End (Ctrl-E) */ + input_end(&cursor, len); + break; + } + if (c == '1' || c == '3' || c == '4') + if ((ret = read(fd, &c, 1)) < 1) + return ret; /* read 126 (~) */ + } + + if (c == 'O') { /* 79 */ + if ((ret = read(fd, &c, 1)) < 1) + return ret; + switch (c) { + case 'H': /* Home (xterm) */ + input_home(&cursor); + break; + case 'F': /* End (xterm_ */ + input_end(&cursor, len); + break; + } + } + + c = 0; + break; + + default: /* If it's regular input, do the normal thing */ + if (!isprint(c)) /* Skip non-printable characters */ + break; + + if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ + break; + + len++; + + if (cursor == (len - 1)) { /* Append if at the end of the line */ + *(parsenextc + cursor) = c; + } else { /* Insert otherwise */ + memmove(parsenextc + cursor + 1, parsenextc + cursor, + len - cursor - 1); + + *(parsenextc + cursor) = c; + + for (j = cursor; j < len; j++) + out1c(*(parsenextc + j)); + for (; j > cursor; j--) + out1str("\033[D"); + } + + cursor++; + out1c(c); + flushout(out1); + break; + } + + if (break_out) /* Enter is the command terminator, no more input. */ + break; + } + + nr = len + 1; + tcsetattr(0, TCSANOW, &old_term); + + if (*(parsenextc)) { /* Handle command history log */ + struct history *h = his_end; + + if (!h) { /* No previous history */ + h = his_front = malloc(sizeof (struct history)); + h->n = malloc(sizeof (struct history)); + h->p = NULL; + h->s = strdup(parsenextc); + + h->n->p = h; + h->n->n = NULL; + h->n->s = NULL; + his_end = h->n; + history_counter++; + } else { /* Add a new history command */ + + h->n = malloc(sizeof (struct history)); + + h->n->p = h; + h->n->n = NULL; + h->n->s = NULL; + h->s = strdup(parsenextc); + his_end = h->n; + + if (history_counter >= MAX_HISTORY) { /* After max history, remove the last known command */ + struct history *p = his_front->n; + + p->p = NULL; + free(his_front->s); + free(his_front); + his_front = p; + } else { + history_counter++; + } + } + } + } + + return nr; +} |