From f0657d322937ad2ff865be9f89cc8c979693088e Mon Sep 17 00:00:00 2001 From: Erik Andersen Date: Wed, 12 Apr 2000 17:49:52 +0000 Subject: Some enhancements I've been working on over the weekend, -Erik --- busybox.def.h | 2 +- cmdedit.c | 619 +++++++++++++++++++++++++++++--------------------------- cmdedit.h | 46 +++-- internal.h | 6 - lash.c | 39 ++-- sh.c | 39 ++-- shell/cmdedit.c | 619 +++++++++++++++++++++++++++++--------------------------- shell/cmdedit.h | 46 +++-- shell/lash.c | 39 ++-- 9 files changed, 783 insertions(+), 672 deletions(-) diff --git a/busybox.def.h b/busybox.def.h index eebe9b973..9a2ba3f69 100644 --- a/busybox.def.h +++ b/busybox.def.h @@ -187,7 +187,7 @@ //#define BB_FEATURE_SORT_REVERSE // // Enable command line editing in the shell -#define BB_FEATURE_SH_COMMAND_EDITING +//#define BB_FEATURE_SH_COMMAND_EDITING // // Enable tab completion in the shell (not yet working very well) //#define BB_FEATURE_SH_TAB_COMPLETION diff --git a/cmdedit.c b/cmdedit.c index d15c69497..9800dd1c6 100644 --- a/cmdedit.c +++ b/cmdedit.c @@ -53,9 +53,10 @@ 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 termio old_term, new_term; /* Current termio and the previous termio before starting ash */ +static int cmdedit_termw = 80; /* actual terminal width */ +static int cmdedit_scroll = 27; /* width of EOL scrolling region */ 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 */ -char *parsenextc; /* copy of parsefile->nextc */ struct history { char *s; @@ -63,85 +64,42 @@ struct history { struct history *n; }; +#define xwrite write -/* Version of write which resumes after a signal is caught. */ -int xwrite(int fd, char *buf, int nbytes) +void +cmdedit_setwidth(int w) { - int ntry; - int i; - int n; - - n = nbytes; - ntry = 0; - for (;;) { - i = write(fd, buf, n); - if (i > 0) { - if ((n -= i) <= 0) - return nbytes; - buf += i; - ntry = 0; - } else if (i == 0) { - if (++ntry > 10) - return nbytes - n; - } else if (errno != EINTR) { - return -1; - } - } + if (w > 20) { + cmdedit_termw = w; + cmdedit_scroll = w / 3; + } else { + errorMsg("\n*** Error: minimum screen width is 21\n"); + } } - -/* Version of ioctl that retries after a signal is caught. */ -int xioctl(int fd, unsigned long request, char *arg) -{ - int i; - - while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR); - return i; -} - - void cmdedit_reset_term(void) { if (reset_term) - xioctl(fileno(stdin), TCSETA, (void *) &old_term); + ioctl(fileno(stdin), TCSETA, (void *) &old_term); } -void prepareToDie(int sig) +void clean_up_and_die(int sig) { cmdedit_reset_term(); fprintf(stdout, "\n"); exit(TRUE); } +/* Go to HOME position */ void input_home(int outputFd, int *cursor) -{ /* Command line input routines */ +{ while (*cursor > 0) { xwrite(outputFd, "\b", 1); --*cursor; } } - -void input_delete(int outputFd, 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 - xwrite(outputFd, (parsenextc + j), 1); - } - - xwrite(outputFd, " \b", 2); - - while (j-- > cursor) - xwrite(outputFd, "\b", 1); -} - - +/* Go to END position */ void input_end(int outputFd, int *cursor, int len) { while (*cursor < len) { @@ -150,22 +108,22 @@ void input_end(int outputFd, int *cursor, int len) } } - -void input_backspace(int outputFd, int *cursor, int *len) +/* Delete the char in back of the cursor */ +void input_backspace(char* command, int outputFd, int *cursor, int *len) { int j = 0; if (*cursor > 0) { xwrite(outputFd, "\b \b", 3); --*cursor; - memmove(parsenextc + *cursor, parsenextc + *cursor + 1, + memmove(command + *cursor, command + *cursor + 1, BUFSIZ - *cursor + 1); for (j = *cursor; j < (BUFSIZ - 1); j++) { - if (!*(parsenextc + j)) + if (!*(command + j)) break; else - xwrite(outputFd, (parsenextc + j), 1); + xwrite(outputFd, (command + j), 1); } xwrite(outputFd, " \b", 2); @@ -177,18 +135,61 @@ void input_backspace(int outputFd, int *cursor, int *len) } } -#ifdef BB_FEATURE_SH_TAB_COMPLETION +/* Delete the char in front of the cursor */ +void input_delete(char* command, int outputFd, int cursor, int *len) +{ + int j = 0; -char** username_completion_matches(char* command, int *num_matches) + if (cursor == *len) + return; + + memmove(command + cursor, command + cursor + 1, + BUFSIZ - cursor - 1); + for (j = cursor; j < (BUFSIZ - 1); j++) { + if (!*(command + j)) + break; + else + xwrite(outputFd, (command + j), 1); + } + + xwrite(outputFd, " \b", 2); + + while (j-- > cursor) + xwrite(outputFd, "\b", 1); + --*len; +} + +/* Move forward one charactor */ +void input_forward(int outputFd, int *cursor, int len) +{ + if (*cursor < len) { + xwrite(outputFd, "\033[C", 3); + ++*cursor; + } +} + +/* Move back one charactor */ +void input_backward(int outputFd, int *cursor) +{ + if (*cursor > 0) { + xwrite(outputFd, "\033[D", 3); + --*cursor; + } +} + + + +#ifdef BB_FEATURE_SH_TAB_COMPLETION +char** username_tab_completion(char* command, int *num_matches) { char **matches = (char **) NULL; *num_matches=0; - fprintf(stderr, "\nin username_completion_matches\n"); + fprintf(stderr, "\nin username_tab_completion\n"); return (matches); } #include -char** find_path_executable_n_cwd_matches(char* command, int *num_matches) +char** exe_n_cwd_tab_completion(char* command, int *num_matches) { char *dirName; char **matches = (char **) NULL; @@ -227,6 +228,170 @@ char** find_path_executable_n_cwd_matches(char* command, int *num_matches) return (matches); } + +void input_tab(char* command, int outputFd, int *cursor, int *len) +{ + /* Do TAB completion */ + static int num_matches=0; + static char **matches = (char **) NULL; + int pos = cursor; + + + if (lastWasTab == FALSE) { + char *tmp, *tmp1, *matchBuf; + + /* For now, we will not bother with trying to distinguish + * whether the cursor is in/at a command extression -- we + * will always try all possable matches. If you don't like + * that then feel free to fix it. + */ + + /* Make a local copy of the string -- up + * to the position of the cursor */ + matchBuf = (char *) calloc(BUFSIZ, sizeof(char)); + strncpy(matchBuf, command, cursor); + tmp=matchBuf; + + /* skip past any command seperator tokens */ + while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) { + tmp=++tmp1; + /* skip any leading white space */ + while (*tmp && isspace(*tmp)) + ++tmp; + } + + /* skip any leading white space */ + while (*tmp && isspace(*tmp)) + ++tmp; + + /* Free up any memory already allocated */ + if (matches) { + free(matches); + matches = (char **) NULL; + } + + /* If the word starts with `~' and there is no slash in the word, + * then try completing this word as a username. */ + + /* FIXME -- this check is broken! */ + if (*tmp == '~' && !strchr(tmp, '/')) + matches = username_tab_completion(tmp, &num_matches); + + /* Try to match any executable in our path and everything + * in the current working directory that matches. */ + if (!matches) + matches = exe_n_cwd_tab_completion(tmp, &num_matches); + + /* Don't leak memory */ + free( matchBuf); + + /* Did we find exactly one match? */ + if (matches && num_matches==1) { + /* write out the matched command */ + strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos); + len=strlen(command); + cursor=len; + xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos); + break; + } + } else { + /* Ok -- the last char was a TAB. Since they + * just hit TAB again, print a list of all the + * available choices... */ + if ( matches && num_matches>0 ) { + int i, col; + + /* Go to the next line */ + xwrite(outputFd, "\n", 1); + /* Print the list of matches */ + for (i=0,col=0; i 60 && matches[i+1] != NULL) { + xwrite(outputFd, "\n", 1); + col = 0; + } + } + /* Go to the next line */ + xwrite(outputFd, "\n", 1); + /* Rewrite the prompt */ + xwrite(outputFd, prompt, strlen(prompt)); + /* Rewrite the command */ + xwrite(outputFd, command, len); + /* Put the cursor back to where it used to be */ + for (cursor=len; cursor > pos; cursor--) + xwrite(outputFd, "\b", 1); + } + } +} +#endif + +void get_previous_history(struct history **hp, char* command) +{ + free((*hp)->s); + (*hp)->s = strdup(command); + *hp = (*hp)->p; +} + +void get_next_history(struct history **hp, char* command) +{ + free((*hp)->s); + (*hp)->s = strdup(command); + *hp = (*hp)->n; + + cmdedit_redraw( NULL, hp->s, -2, -2); +} + +#if 0 +/* prompt : if !=NULL, print the prompt + * command: the command line to be displayed + * where : where to display changes from. + * -1 for no change, -2 for new line + * cursor : desired location for the cursor. + * -1 for Beginning of line. + * -2 for End of Line, + */ +static void +cmdedit_redraw(char* prompt, char* command, int where, int cursor) +{ + static char* last_command; + int cmdedit_width; + + if (where == -2) { + /* Rewrite the prompt and clean up static variables */ + xwrite(outputFd, "\n", 1); + if (prompt) { + strcpy(last_command, prompt); + xwrite(outputFd, prompt, strlen(prompt)); + } else { + last_command[0] = '\0'; + xwrite(outputFd, "# ", 2); + } + cmdedit_width = cmdedit_termw - cmdedit_strlen(prompt); + } else if (strcmp(command, last_command) != 0) { + strcpy(last_command, prompt); + } + + /* erase old command from command line */ + len = strlen(command)-strlen(last_command); + while (len>0) + input_backspace(command, outputFd, &cursor, &len); + input_home(outputFd, &cursor); + + /* Rewrite the command */ + xwrite(outputFd, command+where, len); + + /* Put the where it is supposed to be */ + for (cursor=len; cursor > where; cursor--) + xwrite(outputFd, "\b", 1); + + /* write new command */ + strcpy(command, hp->s); + len = strlen(hp->s); + xwrite(outputFd, command+where, len); + cursor = len; +} #endif /* @@ -245,12 +410,12 @@ char** find_path_executable_n_cwd_matches(char* command, int *num_matches) * Furthermore, the "vi" command editing keys are not implemented. * * TODO: implement TAB command completion. :) - * */ -extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, - char command[BUFSIZ]) +extern void cmdedit_read_input(char* prompt, char command[BUFSIZ]) { + int inputFd=fileno(stdin); + int outputFd=fileno(stdout); int nr = 0; int len = 0; int j = 0; @@ -262,297 +427,162 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, struct history *hp = his_end; memset(command, 0, sizeof(command)); - parsenextc = command; if (!reset_term) { - xioctl(inputFd, TCGETA, (void *) &old_term); + ioctl(inputFd, TCGETA, (void *) &old_term); memcpy(&new_term, &old_term, sizeof(struct termio)); new_term.c_cc[VMIN] = 1; new_term.c_cc[VTIME] = 0; new_term.c_lflag &= ~ICANON; /* unbuffered input */ new_term.c_lflag &= ~ECHO; - xioctl(inputFd, TCSETA, (void *) &new_term); reset_term = 1; + ioctl(inputFd, TCSETA, (void *) &new_term); } else { - xioctl(inputFd, TCSETA, (void *) &new_term); + ioctl(inputFd, TCSETA, (void *) &new_term); } - memset(parsenextc, 0, BUFSIZ); + memset(command, 0, BUFSIZ); while (1) { if ((ret = read(inputFd, &c, 1)) < 1) - return ret; - - fprintf(stderr, "\n\nkey=%d (%c)\n\n", c, c); - /* Go to the next line */ - xwrite(outputFd, "\n", 1); - /* Rewrite the prompt */ - xwrite(outputFd, prompt, strlen(prompt)); - /* Rewrite the command */ - xwrite(outputFd, parsenextc, len); + return; switch (c) { + case '\n': + case '\r': + /* Enter */ + *(command + len++ + 1) = c; + xwrite(outputFd, &c, 1); + break_out = 1; + break; case 1: /* Control-a -- Beginning of line */ input_home(outputFd, &cursor); - case 5: - /* Control-e -- End of line */ - input_end(outputFd, &cursor, len); - break; case 2: /* Control-b -- Move back one character */ - if (cursor > 0) { - xwrite(outputFd, "\033[D", 3); - cursor--; + input_backward(outputFd, &cursor); + break; + case 4: + /* Control-d -- Delete one character, or exit + * if the len=0 and no chars to delete */ + if (len == 0) { + xwrite(outputFd, "exit", 4); + clean_up_and_die(0); + } else { + input_delete(command, outputFd, cursor, &len); } break; + case 5: + /* Control-e -- End of line */ + input_end(outputFd, &cursor, len); + break; case 6: /* Control-f -- Move forward one character */ - if (cursor < len) { - xwrite(outputFd, "\033[C", 3); - cursor++; - } + input_forward(outputFd, &cursor, len); break; - case 4: - /* Control-d -- Delete one character */ - if (cursor != len) { - input_delete(outputFd, cursor); - len--; - } else if (len == 0) { - prepareToDie(0); - exit(0); - } + case '\b': + case DEL: + /* control-h and DEL */ + input_backspace(command, outputFd, &cursor, &len); + break; + case '\t': +#ifdef BB_FEATURE_SH_TAB_COMPLETION + input_tab(command, outputFd, &cursor, &len); +#endif break; case 14: - /* Control-n -- Get next command */ + /* Control-n -- Get next command in history */ if (hp && hp->n && hp->n->s) { - free(hp->s); - hp->s = strdup(parsenextc); - hp = hp->n; - goto hop; + get_next_history(&hp, command); + goto rewrite_line; + } else { + xwrite(outputFd, "\007", 1); } break; case 16: - /* Control-p -- Get previous command */ + /* Control-p -- Get previous command from history */ if (hp && hp->p) { - free(hp->s); - hp->s = strdup(parsenextc); - hp = hp->p; - goto hop; - } - break; - case '\t': -#ifdef BB_FEATURE_SH_TAB_COMPLETION - { - /* Do TAB completion */ - static int num_matches=0; - static char **matches = (char **) NULL; - int pos = cursor; - - - if (lastWasTab == FALSE) { - char *tmp, *tmp1, *matchBuf; - - /* For now, we will not bother with trying to distinguish - * whether the cursor is in/at a command extression -- we - * will always try all possable matches. If you don't like - * that then feel free to fix it. - */ - - /* Make a local copy of the string -- up - * to the position of the cursor */ - matchBuf = (char *) calloc(BUFSIZ, sizeof(char)); - strncpy(matchBuf, parsenextc, cursor); - tmp=matchBuf; - - /* skip past any command seperator tokens */ - while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) { - tmp=++tmp1; - /* skip any leading white space */ - while (*tmp && isspace(*tmp)) - ++tmp; - } - - /* skip any leading white space */ - while (*tmp && isspace(*tmp)) - ++tmp; - - /* Free up any memory already allocated */ - if (matches) { - free(matches); - matches = (char **) NULL; - } - - /* If the word starts with `~' and there is no slash in the word, - * then try completing this word as a username. */ - - /* FIXME -- this check is broken! */ - if (*tmp == '~' && !strchr(tmp, '/')) - matches = username_completion_matches(tmp, &num_matches); - - /* Try to match any executable in our path and everything - * in the current working directory that matches. */ - if (!matches) - matches = find_path_executable_n_cwd_matches(tmp, &num_matches); - - /* Don't leak memory */ - free( matchBuf); - - /* Did we find exactly one match? */ - if (matches && num_matches==1) { - /* write out the matched command */ - strncpy(parsenextc+pos, matches[0]+pos, strlen(matches[0])-pos); - len=strlen(parsenextc); - cursor=len; - xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos); - break; - } - } else { - /* Ok -- the last char was a TAB. Since they - * just hit TAB again, print a list of all the - * available choices... */ - if ( matches && num_matches>0 ) { - int i, col; - - /* Go to the next line */ - xwrite(outputFd, "\n", 1); - /* Print the list of matches */ - for (i=0,col=0; i 60 && matches[i+1] != NULL) { - xwrite(outputFd, "\n", 1); - col = 0; - } - } - /* Go to the next line */ - xwrite(outputFd, "\n", 1); - /* Rewrite the prompt */ - xwrite(outputFd, prompt, strlen(prompt)); - /* Rewrite the command */ - xwrite(outputFd, parsenextc, len); - /* Put the cursor back to where it used to be */ - for (cursor=len; cursor > pos; cursor--) - xwrite(outputFd, "\b", 1); - } - } - break; + get_previous_history(&hp, command); + goto rewrite_line; + } else { + xwrite(outputFd, "\007", 1); } -#else - break; -#endif - case '\b': - case DEL: - /* Backspace */ - input_backspace(outputFd, &cursor, &len); - break; - case '\n': - /* Enter */ - *(parsenextc + len++ + 1) = c; - xwrite(outputFd, &c, 1); - break_out = 1; break; case ESC:{ /* escape sequence follows */ if ((ret = read(inputFd, &c, 1)) < 1) - return ret; + return; if (c == '[') { /* 91 */ if ((ret = read(inputFd, &c, 1)) < 1) - return ret; + return; switch (c) { case 'A': - /* Up Arrow -- Get previous command */ + /* Up Arrow -- Get previous command from history */ if (hp && hp->p) { - free(hp->s); - hp->s = strdup(parsenextc); - hp = hp->p; - goto hop; + get_previous_history(&hp, command); + goto rewrite_line; + } else { + xwrite(outputFd, "\007", 1); } break; case 'B': - /* Down Arrow -- Get next command */ + /* Down Arrow -- Get next command in history */ if (hp && hp->n && hp->n->s) { - free(hp->s); - hp->s = strdup(parsenextc); - hp = hp->n; - goto hop; + get_next_history(&hp, command); + goto rewrite_line; + } else { + xwrite(outputFd, "\007", 1); } break; - /* This is where we rewrite the line - * using the selected history item */ - hop: - len = strlen(parsenextc); - - /* return to begining of line */ - for (; cursor > 0; cursor--) - xwrite(outputFd, "\b", 1); - - /* erase old command */ - for (j = 0; j < len; j++) - xwrite(outputFd, " ", 1); - - /* return to begining of line */ - for (j = len; j > 0; j--) - xwrite(outputFd, "\b", 1); - - memset(parsenextc, 0, BUFSIZ); - len = strlen(parsenextc); + /* Rewrite the line with the selected history item */ + rewrite_line: + /* erase old command from command line */ + len = strlen(command)-strlen(hp->s); + while (len>0) + input_backspace(command, outputFd, &cursor, &len); + input_home(outputFd, &cursor); + /* write new command */ - strcpy(parsenextc, hp->s); + strcpy(command, hp->s); len = strlen(hp->s); - xwrite(outputFd, parsenextc, len); + xwrite(outputFd, command, len); cursor = len; break; case 'C': /* Right Arrow -- Move forward one character */ - if (cursor < len) { - xwrite(outputFd, "\033[C", 3); - cursor++; - } + input_forward(outputFd, &cursor, len); break; case 'D': /* Left Arrow -- Move back one character */ - if (cursor > 0) { - xwrite(outputFd, "\033[D", 3); - cursor--; - } + input_backward(outputFd, &cursor); break; case '3': /* Delete */ - if (cursor != len) { - input_delete(outputFd, cursor); - len--; - } + input_delete(command, outputFd, cursor, &len); break; - - //case '5': case '6': /* pgup/pgdown */ - - case '7': - /* rxvt home */ case '1': /* Home (Ctrl-A) */ input_home(outputFd, &cursor); break; - case '8': - /* rxvt END */ case '4': /* End (Ctrl-E) */ input_end(outputFd, &cursor, len); break; + default: + xwrite(outputFd, "\007", 1); } if (c == '1' || c == '3' || c == '4') if ((ret = read(inputFd, &c, 1)) < 1) - return ret; /* read 126 (~) */ + return; /* read 126 (~) */ } if (c == 'O') { /* 79 */ if ((ret = read(inputFd, &c, 1)) < 1) - return ret; + return; switch (c) { case 'H': /* Home (xterm) */ @@ -562,6 +592,8 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, /* End (xterm) */ input_end(outputFd, &cursor, len); break; + default: + xwrite(outputFd, "\007", 1); } } c = 0; @@ -570,8 +602,9 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, default: /* If it's regular input, do the normal thing */ - if (!isprint(c)) /* Skip non-printable characters */ + if (!isprint(c)) { /* Skip non-printable characters */ break; + } if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ break; @@ -579,15 +612,15 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, len++; if (cursor == (len - 1)) { /* Append if at the end of the line */ - *(parsenextc + cursor) = c; + *(command + cursor) = c; } else { /* Insert otherwise */ - memmove(parsenextc + cursor + 1, parsenextc + cursor, + memmove(command + cursor + 1, command + cursor, len - cursor - 1); - *(parsenextc + cursor) = c; + *(command + cursor) = c; for (j = cursor; j < len; j++) - xwrite(outputFd, parsenextc + j, 1); + xwrite(outputFd, command + j, 1); for (; j > cursor; j--) xwrite(outputFd, "\033[D", 3); } @@ -606,12 +639,12 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, } nr = len + 1; - xioctl(inputFd, TCSETA, (void *) &old_term); + ioctl(inputFd, TCSETA, (void *) &old_term); reset_term = 0; /* Handle command history log */ - if (*(parsenextc)) { + if (*(command)) { struct history *h = his_end; @@ -621,7 +654,7 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, h->n = malloc(sizeof(struct history)); h->p = NULL; - h->s = strdup(parsenextc); + h->s = strdup(command); h->n->p = h; h->n->n = NULL; h->n->s = NULL; @@ -634,7 +667,7 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, h->n->p = h; h->n->n = NULL; h->n->s = NULL; - h->s = strdup(parsenextc); + h->s = strdup(command); his_end = h->n; /* After max history, remove the oldest command */ @@ -652,14 +685,14 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, } } - return nr; + return; } extern void cmdedit_init(void) { atexit(cmdedit_reset_term); - signal(SIGINT, prepareToDie); - signal(SIGQUIT, prepareToDie); - signal(SIGTERM, prepareToDie); + signal(SIGINT, clean_up_and_die); + signal(SIGQUIT, clean_up_and_die); + signal(SIGTERM, clean_up_and_die); } #endif /* BB_FEATURE_SH_COMMAND_EDITING */ diff --git a/cmdedit.h b/cmdedit.h index 843a74070..0e465e50e 100644 --- a/cmdedit.h +++ b/cmdedit.h @@ -1,17 +1,35 @@ -/* - * Termios command line History and Editting for NetBSD sh (ash) - * Copyright (c) 1999 - * Main code: Adam Rogoyski - * Etc: Dave Cinege - * Adjusted for busybox: Erik Andersen - * - * 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. - * +#ifndef GETLINE_H +#define GETLINE_H + +/* unix systems can #define POSIX to use termios, otherwise + * the bsd or sysv interface will be used */ -extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, char command[BUFSIZ]); -extern void cmdedit_init(void); +#ifdef __STDC__ +#include + +typedef size_t (*cmdedit_strwidth_proc)(char *); + +void cmdedit_read_input(char* promptStr, char* command); /* read a line of input */ +void cmdedit_setwidth(int); /* specify width of screen */ +void cmdedit_histadd(char *); /* adds entries to hist */ +void cmdedit_strwidth(cmdedit_strwidth_proc); /* to bind cmdedit_strlen */ + +extern int (*cmdedit_in_hook)(char *); +extern int (*cmdedit_out_hook)(char *); +extern int (*cmdedit_tab_hook)(char *, int, int *); + +#else /* not __STDC__ */ + +void cmdedit_read_input(char* promptStr, char* command); +void cmdedit_setwidth(); +void cmdedit_histadd(); +void cmdedit_strwidth(); + +extern int (*cmdedit_in_hook)(); +extern int (*cmdedit_out_hook)(); +extern int (*cmdedit_tab_hook)(); + +#endif /* __STDC__ */ +#endif /* GETLINE_H */ diff --git a/internal.h b/internal.h index 1313f3644..18159b1d3 100644 --- a/internal.h +++ b/internal.h @@ -220,12 +220,6 @@ extern pid_t* findPidByName( char* pidName); extern void *xmalloc (size_t size); extern int find_real_root_device_name(char* name); -#ifdef BB_FEATURE_SH_COMMAND_EDITING -#include -extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, char command[BUFSIZ]); -extern void cmdedit_init(void); -#endif - #if defined BB_INIT || defined BB_SYSLOGD extern int device_open(char *device, int mode); #endif diff --git a/lash.c b/lash.c index f17097c64..44ffe968f 100644 --- a/lash.c +++ b/lash.c @@ -37,6 +37,9 @@ #include #include #include +#ifdef BB_FEATURE_SH_COMMAND_EDITING +#include "cmdedit.h" +#endif #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" @@ -132,6 +135,16 @@ static const char shell_usage[] = static char cwd[1024]; static char *prompt = "# "; +#ifdef BB_FEATURE_SH_COMMAND_EDITING +void win_changed(int sig) +{ + struct winsize win = { 0, 0 }; + ioctl(0, TIOCGWINSZ, &win); + if (win.ws_col > 0) { + cmdedit_setwidth( win.ws_col - 1); + } +} +#endif /* built-in 'cd ' handler */ @@ -398,7 +411,7 @@ static int getCommand(FILE * source, char *command) fflush(stdout); promptStr=(char*)malloc(sizeof(char)*(len+1)); sprintf(promptStr, "%s %s", cwd, prompt); - cmdedit_read_input(promptStr, fileno(stdin), fileno(stdout), command); + cmdedit_read_input(promptStr, command); free( promptStr); return 0; #else @@ -696,7 +709,6 @@ static int parseCommand(char **commandPtr, struct job *job, int *isBg) strcpy(job->text, *commandPtr); } else { /* This leaves any trailing spaces, which is a bit sloppy */ - count = returnCommand - *commandPtr; job->text = malloc(count + 1); strncpy(job->text, *commandPtr, count); @@ -793,14 +805,12 @@ static int runCommand(struct job newJob, struct jobSet *jobList, int inBg) if (inBg) { /* we don't wait for background jobs to return -- append it to the list of backgrounded jobs and leave it alone */ - printf("[%d] %d\n", job->jobId, newJob.progs[newJob.numProgs - 1].pid); } else { jobList->fg = job; /* move the new process group into the foreground */ - if (tcsetpgrp(0, newJob.pgrp)) perror("tcsetpgrp"); } @@ -938,29 +948,24 @@ int shell_main(int argc, char **argv) /* initialize the cwd */ getcwd(cwd, sizeof(cwd)); +#ifdef BB_FEATURE_SH_COMMAND_EDITING + signal(SIGWINCH, win_changed); + win_changed(0); +#endif //if (argv[0] && argv[0][0] == '-') { // shell_source("/etc/profile"); //} if (argc < 2) { - fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, - BB_BT); - fprintf(stdout, - "Enter 'help' for a list of built-in commands.\n\n"); + fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT); + fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n"); } else { input = fopen(argv[1], "r"); - if (!input) + if (!input) { fatalError("A: Couldn't open file '%s': %s\n", argv[1], strerror(errno)); -// else -// fatalError("Got it.\n"); - //exit(shell_source(argv[1])); - - /* Set terminal IO to canonical mode, and save old term settings. */ -#ifdef BB_FEATURE_SH_COMMAND_EDITING - cmdedit_init(); -#endif + } } return (busy_loop(input)); diff --git a/sh.c b/sh.c index f17097c64..44ffe968f 100644 --- a/sh.c +++ b/sh.c @@ -37,6 +37,9 @@ #include #include #include +#ifdef BB_FEATURE_SH_COMMAND_EDITING +#include "cmdedit.h" +#endif #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" @@ -132,6 +135,16 @@ static const char shell_usage[] = static char cwd[1024]; static char *prompt = "# "; +#ifdef BB_FEATURE_SH_COMMAND_EDITING +void win_changed(int sig) +{ + struct winsize win = { 0, 0 }; + ioctl(0, TIOCGWINSZ, &win); + if (win.ws_col > 0) { + cmdedit_setwidth( win.ws_col - 1); + } +} +#endif /* built-in 'cd ' handler */ @@ -398,7 +411,7 @@ static int getCommand(FILE * source, char *command) fflush(stdout); promptStr=(char*)malloc(sizeof(char)*(len+1)); sprintf(promptStr, "%s %s", cwd, prompt); - cmdedit_read_input(promptStr, fileno(stdin), fileno(stdout), command); + cmdedit_read_input(promptStr, command); free( promptStr); return 0; #else @@ -696,7 +709,6 @@ static int parseCommand(char **commandPtr, struct job *job, int *isBg) strcpy(job->text, *commandPtr); } else { /* This leaves any trailing spaces, which is a bit sloppy */ - count = returnCommand - *commandPtr; job->text = malloc(count + 1); strncpy(job->text, *commandPtr, count); @@ -793,14 +805,12 @@ static int runCommand(struct job newJob, struct jobSet *jobList, int inBg) if (inBg) { /* we don't wait for background jobs to return -- append it to the list of backgrounded jobs and leave it alone */ - printf("[%d] %d\n", job->jobId, newJob.progs[newJob.numProgs - 1].pid); } else { jobList->fg = job; /* move the new process group into the foreground */ - if (tcsetpgrp(0, newJob.pgrp)) perror("tcsetpgrp"); } @@ -938,29 +948,24 @@ int shell_main(int argc, char **argv) /* initialize the cwd */ getcwd(cwd, sizeof(cwd)); +#ifdef BB_FEATURE_SH_COMMAND_EDITING + signal(SIGWINCH, win_changed); + win_changed(0); +#endif //if (argv[0] && argv[0][0] == '-') { // shell_source("/etc/profile"); //} if (argc < 2) { - fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, - BB_BT); - fprintf(stdout, - "Enter 'help' for a list of built-in commands.\n\n"); + fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT); + fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n"); } else { input = fopen(argv[1], "r"); - if (!input) + if (!input) { fatalError("A: Couldn't open file '%s': %s\n", argv[1], strerror(errno)); -// else -// fatalError("Got it.\n"); - //exit(shell_source(argv[1])); - - /* Set terminal IO to canonical mode, and save old term settings. */ -#ifdef BB_FEATURE_SH_COMMAND_EDITING - cmdedit_init(); -#endif + } } return (busy_loop(input)); diff --git a/shell/cmdedit.c b/shell/cmdedit.c index d15c69497..9800dd1c6 100644 --- a/shell/cmdedit.c +++ b/shell/cmdedit.c @@ -53,9 +53,10 @@ 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 termio old_term, new_term; /* Current termio and the previous termio before starting ash */ +static int cmdedit_termw = 80; /* actual terminal width */ +static int cmdedit_scroll = 27; /* width of EOL scrolling region */ 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 */ -char *parsenextc; /* copy of parsefile->nextc */ struct history { char *s; @@ -63,85 +64,42 @@ struct history { struct history *n; }; +#define xwrite write -/* Version of write which resumes after a signal is caught. */ -int xwrite(int fd, char *buf, int nbytes) +void +cmdedit_setwidth(int w) { - int ntry; - int i; - int n; - - n = nbytes; - ntry = 0; - for (;;) { - i = write(fd, buf, n); - if (i > 0) { - if ((n -= i) <= 0) - return nbytes; - buf += i; - ntry = 0; - } else if (i == 0) { - if (++ntry > 10) - return nbytes - n; - } else if (errno != EINTR) { - return -1; - } - } + if (w > 20) { + cmdedit_termw = w; + cmdedit_scroll = w / 3; + } else { + errorMsg("\n*** Error: minimum screen width is 21\n"); + } } - -/* Version of ioctl that retries after a signal is caught. */ -int xioctl(int fd, unsigned long request, char *arg) -{ - int i; - - while ((i = ioctl(fd, request, arg)) == -1 && errno == EINTR); - return i; -} - - void cmdedit_reset_term(void) { if (reset_term) - xioctl(fileno(stdin), TCSETA, (void *) &old_term); + ioctl(fileno(stdin), TCSETA, (void *) &old_term); } -void prepareToDie(int sig) +void clean_up_and_die(int sig) { cmdedit_reset_term(); fprintf(stdout, "\n"); exit(TRUE); } +/* Go to HOME position */ void input_home(int outputFd, int *cursor) -{ /* Command line input routines */ +{ while (*cursor > 0) { xwrite(outputFd, "\b", 1); --*cursor; } } - -void input_delete(int outputFd, 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 - xwrite(outputFd, (parsenextc + j), 1); - } - - xwrite(outputFd, " \b", 2); - - while (j-- > cursor) - xwrite(outputFd, "\b", 1); -} - - +/* Go to END position */ void input_end(int outputFd, int *cursor, int len) { while (*cursor < len) { @@ -150,22 +108,22 @@ void input_end(int outputFd, int *cursor, int len) } } - -void input_backspace(int outputFd, int *cursor, int *len) +/* Delete the char in back of the cursor */ +void input_backspace(char* command, int outputFd, int *cursor, int *len) { int j = 0; if (*cursor > 0) { xwrite(outputFd, "\b \b", 3); --*cursor; - memmove(parsenextc + *cursor, parsenextc + *cursor + 1, + memmove(command + *cursor, command + *cursor + 1, BUFSIZ - *cursor + 1); for (j = *cursor; j < (BUFSIZ - 1); j++) { - if (!*(parsenextc + j)) + if (!*(command + j)) break; else - xwrite(outputFd, (parsenextc + j), 1); + xwrite(outputFd, (command + j), 1); } xwrite(outputFd, " \b", 2); @@ -177,18 +135,61 @@ void input_backspace(int outputFd, int *cursor, int *len) } } -#ifdef BB_FEATURE_SH_TAB_COMPLETION +/* Delete the char in front of the cursor */ +void input_delete(char* command, int outputFd, int cursor, int *len) +{ + int j = 0; -char** username_completion_matches(char* command, int *num_matches) + if (cursor == *len) + return; + + memmove(command + cursor, command + cursor + 1, + BUFSIZ - cursor - 1); + for (j = cursor; j < (BUFSIZ - 1); j++) { + if (!*(command + j)) + break; + else + xwrite(outputFd, (command + j), 1); + } + + xwrite(outputFd, " \b", 2); + + while (j-- > cursor) + xwrite(outputFd, "\b", 1); + --*len; +} + +/* Move forward one charactor */ +void input_forward(int outputFd, int *cursor, int len) +{ + if (*cursor < len) { + xwrite(outputFd, "\033[C", 3); + ++*cursor; + } +} + +/* Move back one charactor */ +void input_backward(int outputFd, int *cursor) +{ + if (*cursor > 0) { + xwrite(outputFd, "\033[D", 3); + --*cursor; + } +} + + + +#ifdef BB_FEATURE_SH_TAB_COMPLETION +char** username_tab_completion(char* command, int *num_matches) { char **matches = (char **) NULL; *num_matches=0; - fprintf(stderr, "\nin username_completion_matches\n"); + fprintf(stderr, "\nin username_tab_completion\n"); return (matches); } #include -char** find_path_executable_n_cwd_matches(char* command, int *num_matches) +char** exe_n_cwd_tab_completion(char* command, int *num_matches) { char *dirName; char **matches = (char **) NULL; @@ -227,6 +228,170 @@ char** find_path_executable_n_cwd_matches(char* command, int *num_matches) return (matches); } + +void input_tab(char* command, int outputFd, int *cursor, int *len) +{ + /* Do TAB completion */ + static int num_matches=0; + static char **matches = (char **) NULL; + int pos = cursor; + + + if (lastWasTab == FALSE) { + char *tmp, *tmp1, *matchBuf; + + /* For now, we will not bother with trying to distinguish + * whether the cursor is in/at a command extression -- we + * will always try all possable matches. If you don't like + * that then feel free to fix it. + */ + + /* Make a local copy of the string -- up + * to the position of the cursor */ + matchBuf = (char *) calloc(BUFSIZ, sizeof(char)); + strncpy(matchBuf, command, cursor); + tmp=matchBuf; + + /* skip past any command seperator tokens */ + while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) { + tmp=++tmp1; + /* skip any leading white space */ + while (*tmp && isspace(*tmp)) + ++tmp; + } + + /* skip any leading white space */ + while (*tmp && isspace(*tmp)) + ++tmp; + + /* Free up any memory already allocated */ + if (matches) { + free(matches); + matches = (char **) NULL; + } + + /* If the word starts with `~' and there is no slash in the word, + * then try completing this word as a username. */ + + /* FIXME -- this check is broken! */ + if (*tmp == '~' && !strchr(tmp, '/')) + matches = username_tab_completion(tmp, &num_matches); + + /* Try to match any executable in our path and everything + * in the current working directory that matches. */ + if (!matches) + matches = exe_n_cwd_tab_completion(tmp, &num_matches); + + /* Don't leak memory */ + free( matchBuf); + + /* Did we find exactly one match? */ + if (matches && num_matches==1) { + /* write out the matched command */ + strncpy(command+pos, matches[0]+pos, strlen(matches[0])-pos); + len=strlen(command); + cursor=len; + xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos); + break; + } + } else { + /* Ok -- the last char was a TAB. Since they + * just hit TAB again, print a list of all the + * available choices... */ + if ( matches && num_matches>0 ) { + int i, col; + + /* Go to the next line */ + xwrite(outputFd, "\n", 1); + /* Print the list of matches */ + for (i=0,col=0; i 60 && matches[i+1] != NULL) { + xwrite(outputFd, "\n", 1); + col = 0; + } + } + /* Go to the next line */ + xwrite(outputFd, "\n", 1); + /* Rewrite the prompt */ + xwrite(outputFd, prompt, strlen(prompt)); + /* Rewrite the command */ + xwrite(outputFd, command, len); + /* Put the cursor back to where it used to be */ + for (cursor=len; cursor > pos; cursor--) + xwrite(outputFd, "\b", 1); + } + } +} +#endif + +void get_previous_history(struct history **hp, char* command) +{ + free((*hp)->s); + (*hp)->s = strdup(command); + *hp = (*hp)->p; +} + +void get_next_history(struct history **hp, char* command) +{ + free((*hp)->s); + (*hp)->s = strdup(command); + *hp = (*hp)->n; + + cmdedit_redraw( NULL, hp->s, -2, -2); +} + +#if 0 +/* prompt : if !=NULL, print the prompt + * command: the command line to be displayed + * where : where to display changes from. + * -1 for no change, -2 for new line + * cursor : desired location for the cursor. + * -1 for Beginning of line. + * -2 for End of Line, + */ +static void +cmdedit_redraw(char* prompt, char* command, int where, int cursor) +{ + static char* last_command; + int cmdedit_width; + + if (where == -2) { + /* Rewrite the prompt and clean up static variables */ + xwrite(outputFd, "\n", 1); + if (prompt) { + strcpy(last_command, prompt); + xwrite(outputFd, prompt, strlen(prompt)); + } else { + last_command[0] = '\0'; + xwrite(outputFd, "# ", 2); + } + cmdedit_width = cmdedit_termw - cmdedit_strlen(prompt); + } else if (strcmp(command, last_command) != 0) { + strcpy(last_command, prompt); + } + + /* erase old command from command line */ + len = strlen(command)-strlen(last_command); + while (len>0) + input_backspace(command, outputFd, &cursor, &len); + input_home(outputFd, &cursor); + + /* Rewrite the command */ + xwrite(outputFd, command+where, len); + + /* Put the where it is supposed to be */ + for (cursor=len; cursor > where; cursor--) + xwrite(outputFd, "\b", 1); + + /* write new command */ + strcpy(command, hp->s); + len = strlen(hp->s); + xwrite(outputFd, command+where, len); + cursor = len; +} #endif /* @@ -245,12 +410,12 @@ char** find_path_executable_n_cwd_matches(char* command, int *num_matches) * Furthermore, the "vi" command editing keys are not implemented. * * TODO: implement TAB command completion. :) - * */ -extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, - char command[BUFSIZ]) +extern void cmdedit_read_input(char* prompt, char command[BUFSIZ]) { + int inputFd=fileno(stdin); + int outputFd=fileno(stdout); int nr = 0; int len = 0; int j = 0; @@ -262,297 +427,162 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, struct history *hp = his_end; memset(command, 0, sizeof(command)); - parsenextc = command; if (!reset_term) { - xioctl(inputFd, TCGETA, (void *) &old_term); + ioctl(inputFd, TCGETA, (void *) &old_term); memcpy(&new_term, &old_term, sizeof(struct termio)); new_term.c_cc[VMIN] = 1; new_term.c_cc[VTIME] = 0; new_term.c_lflag &= ~ICANON; /* unbuffered input */ new_term.c_lflag &= ~ECHO; - xioctl(inputFd, TCSETA, (void *) &new_term); reset_term = 1; + ioctl(inputFd, TCSETA, (void *) &new_term); } else { - xioctl(inputFd, TCSETA, (void *) &new_term); + ioctl(inputFd, TCSETA, (void *) &new_term); } - memset(parsenextc, 0, BUFSIZ); + memset(command, 0, BUFSIZ); while (1) { if ((ret = read(inputFd, &c, 1)) < 1) - return ret; - - fprintf(stderr, "\n\nkey=%d (%c)\n\n", c, c); - /* Go to the next line */ - xwrite(outputFd, "\n", 1); - /* Rewrite the prompt */ - xwrite(outputFd, prompt, strlen(prompt)); - /* Rewrite the command */ - xwrite(outputFd, parsenextc, len); + return; switch (c) { + case '\n': + case '\r': + /* Enter */ + *(command + len++ + 1) = c; + xwrite(outputFd, &c, 1); + break_out = 1; + break; case 1: /* Control-a -- Beginning of line */ input_home(outputFd, &cursor); - case 5: - /* Control-e -- End of line */ - input_end(outputFd, &cursor, len); - break; case 2: /* Control-b -- Move back one character */ - if (cursor > 0) { - xwrite(outputFd, "\033[D", 3); - cursor--; + input_backward(outputFd, &cursor); + break; + case 4: + /* Control-d -- Delete one character, or exit + * if the len=0 and no chars to delete */ + if (len == 0) { + xwrite(outputFd, "exit", 4); + clean_up_and_die(0); + } else { + input_delete(command, outputFd, cursor, &len); } break; + case 5: + /* Control-e -- End of line */ + input_end(outputFd, &cursor, len); + break; case 6: /* Control-f -- Move forward one character */ - if (cursor < len) { - xwrite(outputFd, "\033[C", 3); - cursor++; - } + input_forward(outputFd, &cursor, len); break; - case 4: - /* Control-d -- Delete one character */ - if (cursor != len) { - input_delete(outputFd, cursor); - len--; - } else if (len == 0) { - prepareToDie(0); - exit(0); - } + case '\b': + case DEL: + /* control-h and DEL */ + input_backspace(command, outputFd, &cursor, &len); + break; + case '\t': +#ifdef BB_FEATURE_SH_TAB_COMPLETION + input_tab(command, outputFd, &cursor, &len); +#endif break; case 14: - /* Control-n -- Get next command */ + /* Control-n -- Get next command in history */ if (hp && hp->n && hp->n->s) { - free(hp->s); - hp->s = strdup(parsenextc); - hp = hp->n; - goto hop; + get_next_history(&hp, command); + goto rewrite_line; + } else { + xwrite(outputFd, "\007", 1); } break; case 16: - /* Control-p -- Get previous command */ + /* Control-p -- Get previous command from history */ if (hp && hp->p) { - free(hp->s); - hp->s = strdup(parsenextc); - hp = hp->p; - goto hop; - } - break; - case '\t': -#ifdef BB_FEATURE_SH_TAB_COMPLETION - { - /* Do TAB completion */ - static int num_matches=0; - static char **matches = (char **) NULL; - int pos = cursor; - - - if (lastWasTab == FALSE) { - char *tmp, *tmp1, *matchBuf; - - /* For now, we will not bother with trying to distinguish - * whether the cursor is in/at a command extression -- we - * will always try all possable matches. If you don't like - * that then feel free to fix it. - */ - - /* Make a local copy of the string -- up - * to the position of the cursor */ - matchBuf = (char *) calloc(BUFSIZ, sizeof(char)); - strncpy(matchBuf, parsenextc, cursor); - tmp=matchBuf; - - /* skip past any command seperator tokens */ - while (*tmp && (tmp1=strpbrk(tmp, ";|&{(`")) != NULL) { - tmp=++tmp1; - /* skip any leading white space */ - while (*tmp && isspace(*tmp)) - ++tmp; - } - - /* skip any leading white space */ - while (*tmp && isspace(*tmp)) - ++tmp; - - /* Free up any memory already allocated */ - if (matches) { - free(matches); - matches = (char **) NULL; - } - - /* If the word starts with `~' and there is no slash in the word, - * then try completing this word as a username. */ - - /* FIXME -- this check is broken! */ - if (*tmp == '~' && !strchr(tmp, '/')) - matches = username_completion_matches(tmp, &num_matches); - - /* Try to match any executable in our path and everything - * in the current working directory that matches. */ - if (!matches) - matches = find_path_executable_n_cwd_matches(tmp, &num_matches); - - /* Don't leak memory */ - free( matchBuf); - - /* Did we find exactly one match? */ - if (matches && num_matches==1) { - /* write out the matched command */ - strncpy(parsenextc+pos, matches[0]+pos, strlen(matches[0])-pos); - len=strlen(parsenextc); - cursor=len; - xwrite(outputFd, matches[0]+pos, strlen(matches[0])-pos); - break; - } - } else { - /* Ok -- the last char was a TAB. Since they - * just hit TAB again, print a list of all the - * available choices... */ - if ( matches && num_matches>0 ) { - int i, col; - - /* Go to the next line */ - xwrite(outputFd, "\n", 1); - /* Print the list of matches */ - for (i=0,col=0; i 60 && matches[i+1] != NULL) { - xwrite(outputFd, "\n", 1); - col = 0; - } - } - /* Go to the next line */ - xwrite(outputFd, "\n", 1); - /* Rewrite the prompt */ - xwrite(outputFd, prompt, strlen(prompt)); - /* Rewrite the command */ - xwrite(outputFd, parsenextc, len); - /* Put the cursor back to where it used to be */ - for (cursor=len; cursor > pos; cursor--) - xwrite(outputFd, "\b", 1); - } - } - break; + get_previous_history(&hp, command); + goto rewrite_line; + } else { + xwrite(outputFd, "\007", 1); } -#else - break; -#endif - case '\b': - case DEL: - /* Backspace */ - input_backspace(outputFd, &cursor, &len); - break; - case '\n': - /* Enter */ - *(parsenextc + len++ + 1) = c; - xwrite(outputFd, &c, 1); - break_out = 1; break; case ESC:{ /* escape sequence follows */ if ((ret = read(inputFd, &c, 1)) < 1) - return ret; + return; if (c == '[') { /* 91 */ if ((ret = read(inputFd, &c, 1)) < 1) - return ret; + return; switch (c) { case 'A': - /* Up Arrow -- Get previous command */ + /* Up Arrow -- Get previous command from history */ if (hp && hp->p) { - free(hp->s); - hp->s = strdup(parsenextc); - hp = hp->p; - goto hop; + get_previous_history(&hp, command); + goto rewrite_line; + } else { + xwrite(outputFd, "\007", 1); } break; case 'B': - /* Down Arrow -- Get next command */ + /* Down Arrow -- Get next command in history */ if (hp && hp->n && hp->n->s) { - free(hp->s); - hp->s = strdup(parsenextc); - hp = hp->n; - goto hop; + get_next_history(&hp, command); + goto rewrite_line; + } else { + xwrite(outputFd, "\007", 1); } break; - /* This is where we rewrite the line - * using the selected history item */ - hop: - len = strlen(parsenextc); - - /* return to begining of line */ - for (; cursor > 0; cursor--) - xwrite(outputFd, "\b", 1); - - /* erase old command */ - for (j = 0; j < len; j++) - xwrite(outputFd, " ", 1); - - /* return to begining of line */ - for (j = len; j > 0; j--) - xwrite(outputFd, "\b", 1); - - memset(parsenextc, 0, BUFSIZ); - len = strlen(parsenextc); + /* Rewrite the line with the selected history item */ + rewrite_line: + /* erase old command from command line */ + len = strlen(command)-strlen(hp->s); + while (len>0) + input_backspace(command, outputFd, &cursor, &len); + input_home(outputFd, &cursor); + /* write new command */ - strcpy(parsenextc, hp->s); + strcpy(command, hp->s); len = strlen(hp->s); - xwrite(outputFd, parsenextc, len); + xwrite(outputFd, command, len); cursor = len; break; case 'C': /* Right Arrow -- Move forward one character */ - if (cursor < len) { - xwrite(outputFd, "\033[C", 3); - cursor++; - } + input_forward(outputFd, &cursor, len); break; case 'D': /* Left Arrow -- Move back one character */ - if (cursor > 0) { - xwrite(outputFd, "\033[D", 3); - cursor--; - } + input_backward(outputFd, &cursor); break; case '3': /* Delete */ - if (cursor != len) { - input_delete(outputFd, cursor); - len--; - } + input_delete(command, outputFd, cursor, &len); break; - - //case '5': case '6': /* pgup/pgdown */ - - case '7': - /* rxvt home */ case '1': /* Home (Ctrl-A) */ input_home(outputFd, &cursor); break; - case '8': - /* rxvt END */ case '4': /* End (Ctrl-E) */ input_end(outputFd, &cursor, len); break; + default: + xwrite(outputFd, "\007", 1); } if (c == '1' || c == '3' || c == '4') if ((ret = read(inputFd, &c, 1)) < 1) - return ret; /* read 126 (~) */ + return; /* read 126 (~) */ } if (c == 'O') { /* 79 */ if ((ret = read(inputFd, &c, 1)) < 1) - return ret; + return; switch (c) { case 'H': /* Home (xterm) */ @@ -562,6 +592,8 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, /* End (xterm) */ input_end(outputFd, &cursor, len); break; + default: + xwrite(outputFd, "\007", 1); } } c = 0; @@ -570,8 +602,9 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, default: /* If it's regular input, do the normal thing */ - if (!isprint(c)) /* Skip non-printable characters */ + if (!isprint(c)) { /* Skip non-printable characters */ break; + } if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ break; @@ -579,15 +612,15 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, len++; if (cursor == (len - 1)) { /* Append if at the end of the line */ - *(parsenextc + cursor) = c; + *(command + cursor) = c; } else { /* Insert otherwise */ - memmove(parsenextc + cursor + 1, parsenextc + cursor, + memmove(command + cursor + 1, command + cursor, len - cursor - 1); - *(parsenextc + cursor) = c; + *(command + cursor) = c; for (j = cursor; j < len; j++) - xwrite(outputFd, parsenextc + j, 1); + xwrite(outputFd, command + j, 1); for (; j > cursor; j--) xwrite(outputFd, "\033[D", 3); } @@ -606,12 +639,12 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, } nr = len + 1; - xioctl(inputFd, TCSETA, (void *) &old_term); + ioctl(inputFd, TCSETA, (void *) &old_term); reset_term = 0; /* Handle command history log */ - if (*(parsenextc)) { + if (*(command)) { struct history *h = his_end; @@ -621,7 +654,7 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, h->n = malloc(sizeof(struct history)); h->p = NULL; - h->s = strdup(parsenextc); + h->s = strdup(command); h->n->p = h; h->n->n = NULL; h->n->s = NULL; @@ -634,7 +667,7 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, h->n->p = h; h->n->n = NULL; h->n->s = NULL; - h->s = strdup(parsenextc); + h->s = strdup(command); his_end = h->n; /* After max history, remove the oldest command */ @@ -652,14 +685,14 @@ extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, } } - return nr; + return; } extern void cmdedit_init(void) { atexit(cmdedit_reset_term); - signal(SIGINT, prepareToDie); - signal(SIGQUIT, prepareToDie); - signal(SIGTERM, prepareToDie); + signal(SIGINT, clean_up_and_die); + signal(SIGQUIT, clean_up_and_die); + signal(SIGTERM, clean_up_and_die); } #endif /* BB_FEATURE_SH_COMMAND_EDITING */ diff --git a/shell/cmdedit.h b/shell/cmdedit.h index 843a74070..0e465e50e 100644 --- a/shell/cmdedit.h +++ b/shell/cmdedit.h @@ -1,17 +1,35 @@ -/* - * Termios command line History and Editting for NetBSD sh (ash) - * Copyright (c) 1999 - * Main code: Adam Rogoyski - * Etc: Dave Cinege - * Adjusted for busybox: Erik Andersen - * - * 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. - * +#ifndef GETLINE_H +#define GETLINE_H + +/* unix systems can #define POSIX to use termios, otherwise + * the bsd or sysv interface will be used */ -extern int cmdedit_read_input(char* prompt, int inputFd, int outputFd, char command[BUFSIZ]); -extern void cmdedit_init(void); +#ifdef __STDC__ +#include + +typedef size_t (*cmdedit_strwidth_proc)(char *); + +void cmdedit_read_input(char* promptStr, char* command); /* read a line of input */ +void cmdedit_setwidth(int); /* specify width of screen */ +void cmdedit_histadd(char *); /* adds entries to hist */ +void cmdedit_strwidth(cmdedit_strwidth_proc); /* to bind cmdedit_strlen */ + +extern int (*cmdedit_in_hook)(char *); +extern int (*cmdedit_out_hook)(char *); +extern int (*cmdedit_tab_hook)(char *, int, int *); + +#else /* not __STDC__ */ + +void cmdedit_read_input(char* promptStr, char* command); +void cmdedit_setwidth(); +void cmdedit_histadd(); +void cmdedit_strwidth(); + +extern int (*cmdedit_in_hook)(); +extern int (*cmdedit_out_hook)(); +extern int (*cmdedit_tab_hook)(); + +#endif /* __STDC__ */ +#endif /* GETLINE_H */ diff --git a/shell/lash.c b/shell/lash.c index f17097c64..44ffe968f 100644 --- a/shell/lash.c +++ b/shell/lash.c @@ -37,6 +37,9 @@ #include #include #include +#ifdef BB_FEATURE_SH_COMMAND_EDITING +#include "cmdedit.h" +#endif #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" @@ -132,6 +135,16 @@ static const char shell_usage[] = static char cwd[1024]; static char *prompt = "# "; +#ifdef BB_FEATURE_SH_COMMAND_EDITING +void win_changed(int sig) +{ + struct winsize win = { 0, 0 }; + ioctl(0, TIOCGWINSZ, &win); + if (win.ws_col > 0) { + cmdedit_setwidth( win.ws_col - 1); + } +} +#endif /* built-in 'cd ' handler */ @@ -398,7 +411,7 @@ static int getCommand(FILE * source, char *command) fflush(stdout); promptStr=(char*)malloc(sizeof(char)*(len+1)); sprintf(promptStr, "%s %s", cwd, prompt); - cmdedit_read_input(promptStr, fileno(stdin), fileno(stdout), command); + cmdedit_read_input(promptStr, command); free( promptStr); return 0; #else @@ -696,7 +709,6 @@ static int parseCommand(char **commandPtr, struct job *job, int *isBg) strcpy(job->text, *commandPtr); } else { /* This leaves any trailing spaces, which is a bit sloppy */ - count = returnCommand - *commandPtr; job->text = malloc(count + 1); strncpy(job->text, *commandPtr, count); @@ -793,14 +805,12 @@ static int runCommand(struct job newJob, struct jobSet *jobList, int inBg) if (inBg) { /* we don't wait for background jobs to return -- append it to the list of backgrounded jobs and leave it alone */ - printf("[%d] %d\n", job->jobId, newJob.progs[newJob.numProgs - 1].pid); } else { jobList->fg = job; /* move the new process group into the foreground */ - if (tcsetpgrp(0, newJob.pgrp)) perror("tcsetpgrp"); } @@ -938,29 +948,24 @@ int shell_main(int argc, char **argv) /* initialize the cwd */ getcwd(cwd, sizeof(cwd)); +#ifdef BB_FEATURE_SH_COMMAND_EDITING + signal(SIGWINCH, win_changed); + win_changed(0); +#endif //if (argv[0] && argv[0][0] == '-') { // shell_source("/etc/profile"); //} if (argc < 2) { - fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, - BB_BT); - fprintf(stdout, - "Enter 'help' for a list of built-in commands.\n\n"); + fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT); + fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n"); } else { input = fopen(argv[1], "r"); - if (!input) + if (!input) { fatalError("A: Couldn't open file '%s': %s\n", argv[1], strerror(errno)); -// else -// fatalError("Got it.\n"); - //exit(shell_source(argv[1])); - - /* Set terminal IO to canonical mode, and save old term settings. */ -#ifdef BB_FEATURE_SH_COMMAND_EDITING - cmdedit_init(); -#endif + } } return (busy_loop(input)); -- cgit v1.2.3