/* * Copyright (c) 1987 by David L. Parsons * All rights reserved. */ #include #include #include #include "tsh.h" #include "glob.h" extern char *mmalloc(); extern char *xstrdup(); #define BELL 007 #define BACKSP 010 #define CTRLU ('U'-'@') /* erase line */ #define CTRLV ('V'-'@') /* escape next */ #define CTRLZ ('Z'-'@') /* HUP */ #define CTRLC ('C'-'@') /* interrupt */ #define ESC 033 #define SCREENWIDTH 78 /* ick-shouldn't be hardcoded */ #define W_ADJ 0 #define W_SCROLL 1 #define W_BAD 2 #define HUGESIZE 256 #define HISTORY 8 /* 32 strings in the history */ int cmdnumber = 1; /* what command? */ static char *command; /* where ttygetc goes */ static int cursor; /* current cursor position */ static int oldpos; /* last cursor position, for redraw */ static int onright; /* rhs for insert in the middle */ static char yanked[HUGESIZE]; /* yanked text */ static char isyanked; /* stuff yanked? */ static char undostr[HUGESIZE]; /* stuff to undo */ static int undopos; /* where we undid it */ static char *prompt; /* current prompt */ static char lastpatt[40] = ""; /* last search pattern */ static char lastdir = 0; /* last direction */ static int currline; /* current command # for movement */ static char *history[HISTORY]; /* our history */ static int lhs, rhs; static char star=' '; extern char ignoreeof; #define bell() ttyputc(7) static getline(len) { register i = cursor; register c; while ((c = ttygetc()) != ESC && c != '\r') { switch (c) { case CTRLC: return EOF; case BACKSP: if (i > 0) { c = command[--i]; command[i] = 0; if (i < lhs) { /* if we've rolled off the lhs of */ cursor = i; /* the screen, redisplay */ wscroll(); } else wcharerase(c); } break; case CTRLU: onright = cursor = i = command[0] = 0; ttyputs("@\n"); wscroll(); break; case CTRLZ: if (!ignoreeof) shellexit(0); bell(); break; case CTRLV: ttyputs("^\b"); c = ttygetc(); default: if (c && i < len) { display(c); command[i++] = c; } else bell(); break; } } command[oldpos=cursor=i] = 0; return c; } static advance(pos) register pos; { pos += cursor; return (pos <= strlen(command)) ? pos : (-1); } static backup(pos) { register tmp = cursor - pos; return (tmp >= 0) ? tmp : (-1); } static column(pos) register pos; { return (pos > 0 && pos <= strlen(command)) ? (pos-1) : (-1); } static worder(count, ftn, classify) register count; register (*ftn)(), (*classify)(); { register tmp; if (count < 1) count = 1; if ((tmp = (*ftn)(count, classify)) == -1) bell(); else cursor = tmp; } #define ISSPACE 0 #define ISALNUM 1 #define ISOTHER 2 static cclass(c) register char c; { return isspace(c) ? ISSPACE : (isalnum(c) ? ISALNUM : ISOTHER); } static cspace(c) register char c; { return isspace(c) ? ISSPACE : ISOTHER; } forword(count, class) register count; register (*class)(); { register idx, ccl; for (idx=cursor; command[idx] && count-- > 0; ) { if (command[1+idx] == 0) return -1; ccl = (*class)(command[idx]); while (command[idx] && (*class)(command[idx]) == ccl) ++idx; while (isspace(command[idx])) ++idx; } return (idx && command[idx] == 0) ? (idx-1) : idx; } backword(count, class) register count; register (*class)(); { register idx, ccl; for (idx=cursor-1; count-- > 0; ) { while (idx >= 0 && isspace(command[idx])) --idx; if (idx >= 0) { ccl = (*class)(command[idx]); while (idx >= 0 && (*class)(command[idx]) == ccl) --idx; } else return -1; } return idx+1; } endword(count, class) register count; register (*class)(); { register idx, ccl; for (idx=1+cursor; count-- > 0; ) { while (isspace(command[idx])) ++idx; if (command[idx]) { ccl = (*class)(command[idx]); while (command[idx] && (*class)(command[idx]) == ccl) ++idx; } else return -1; } return idx-1; } static char charfind; static charsearch(count, ftn) int (*ftn)(); { charfind = ttygetc(); if (charfind == CTRLC) softinterrupt(INTR); else if (charfind != ESC) if (charfind) operate(count, 1, ftn); else bell(); } static f_on(count) register count; { register idx; for (idx = cursor; count-- > 0; ) { if (command[idx] == 0) break; do { ++idx; } while (command[idx] && command[idx] != charfind); } return (command[idx] == charfind) ? idx : (-1); } static b_on(count) register count; { register idx; for (idx = cursor; count-- > 0; ) { if (idx <= 0) break; do { --idx; } while (idx > 0 && command[idx] != charfind); } return (command[idx] == charfind) ? idx : (-1); } static f_to(count) { register idx = f_on(count); if (idx > cursor) return idx-1; } static b_to(count) { register idx = b_on(count); if (idx < cursor) return idx+1; } operate(count, dflt, ftn) register count; int (*ftn)(); { register tmp; if ((tmp = (*ftn)(count ? count : dflt)) != -1) cursor = tmp; else bell(); } char * goldcmd(cmdno) register cmdno; { if (cmdno <= cmdnumber && cmdno > 0 && cmdno > cmdnumber-HISTORY) return (cmdno==cmdnumber) ? "" : history[cmdno%HISTORY]; return NULL; } static getold(cmdno) register cmdno; { char *p; if (p=goldcmd(cmdno)) { cursor = lhs = onright = 0; strcpy(command, p); wscroll(); currline = cmdno; } else bell(); } static movelines(count, dft) register count; { if (count == 0) count = dft; getold(currline+count); } static gpatt(key) { register i=0, c; char pat[40]; ttyputs("\r%s%c", prompt, key); while ((c = ttygetc()) != '\r') { if (c == INTR) softinterrupt(INTR); else if (c == BACKSP) { if (i == 0) return 0; else wcharerase(pat[--i]); } else if (c == ESC) return 0; else if (c && i < 39) display(pat[i++] = c); else bell(); } pat[i] = 0; if (i > 0) strcpy(lastpatt, pat); return 1; } static matched(target) register char *target; { register psize, tsize, i; psize = strlen(lastpatt); tsize = strlen(target) - psize; for (i = 0; i<=tsize; i++) if (strncmp(target+i, lastpatt, psize) == 0) return 1; return 0; } static searchfor(again) { register idx; if ((again || gpatt('/')) && lastpatt[0]) { for (idx = currline-1; idx > 0 && idx > cmdnumber-HISTORY; --idx) if (matched(history[idx%HISTORY])) { getold(idx); return; } bell(); } wscroll(); } static searchback(again) { register idx; if ((again || gpatt('?')) && lastpatt[0]) { for (idx = currline+1; idx < cmdnumber; ++idx) if (matched(history[idx%HISTORY])) { getold(idx); return; } bell(); } wscroll(); } /* * movement() does movement commands */ static movement(c, count) register count; { switch (c) { case '0': if (count > 0) return 0; /* else 0 == 0|, so we fall through */ case '|': operate(count, 1, column); break; case '$': cursor = strlen(command); break; case BACKSP: case 'h': operate(count, 1, backup); break; case ' ': case 'l': operate(count, 1, advance); break; case 'w': worder(count, forword, cclass); break; case 'b': worder(count, backword,cclass); break; case 'e': worder(count, endword, cclass); break; case 'W': worder(count, forword, cspace); break; case 'B': worder(count, backword,cspace); break; case 'E': worder(count, endword, cspace); break; case 'f': charsearch(count, f_on); break; case 'F': charsearch(count, b_on); break; case 't': charsearch(count, f_to); break; case 'T': charsearch(count, b_to); break; default: return 0; } return 1; } static yankit(c, count, low, high) register int *low, *high; { int basepos = cursor; register i, j; if (movement(c, count)) { if (strchr("Ee|ft", c)) cursor++; if (basepos < cursor) { *low = basepos; *high = cursor; } else { *low = cursor; *high = basepos; } cursor = basepos; /* restore old cursor */ if (*low != *high) { /* yank stuff out... */ for (i = *low, j=0; i < *high; i++, j++) yanked[j] = command[i]; yanked[j] = 0; isyanked = 1; return 1; } } bell(); return 0; } static setrhs() { rhs = lhs + (SCREENWIDTH-strlen(prompt))-1; if (rhs > strlen(command)) rhs = strlen(command); } static mark() { strcpy(undostr, command); undopos = cursor; } static delete(c, count) char c; { int low, high; mark(); if (yankit(c, count, &low, &high)) { strcpy(&command[cursor=low], &command[high]); setrhs(); if (wpos() == W_SCROLL) wscroll(); else redisplay(); return 1; } return 0; } static yank(c, count) { int low, high, save=cursor; register status; status = yankit(c, count, &low, &high); cursor = save; return status; } static char * format(c) register char c; { static char buf[8]; c &= 0x7f; if (iscntrl(c)) sprintf(buf, "^%c", (c==127) ? '?' : (c+'@')); else { buf[0] = c; buf[1] = 0; } return buf; } static display(c) { ttyputs("%s", format(c)); } static reposition() { switch (wpos()) { case W_ADJ: wmovecur(); break; case W_SCROLL: wscroll(); break; } } static redisplay() { register i, x; register char *pl; ttyputs("\r%s", prompt); x = strlen(prompt); for (i=lhs; command[i] && i<=rhs; i++) { ttyputs("%s", pl=format(command[i])); x += strlen(pl); } while (x++ < SCREENWIDTH) ttyputc(' '); ttyputs("%c\r%s", star, prompt); x = strlen(prompt); for (i=lhs; i oldpos) { for (i=oldpos; i < cursor; i++) display(command[i]); } else for (i=oldpos-1; i >= cursor; --i) { for (tp=format(command[i]); *tp; ++tp) ttyputc('\b'); } oldpos = cursor; } static wcharerase(c) { register size; for (size = strlen(format(c)); size-- > 0; ) ttyputs("\b \b"); } /* * recenter the window around the cursor */ static wscroll() { if ((lhs = cursor-((SCREENWIDTH-strlen(prompt))/2)) < 0) lhs = 0; setrhs(); if (lhs > 0) star = command[rhs] ? '*' : '<'; else star = command[rhs] ? '>' : ' '; redisplay(); } static wpos() { if (cursor >= lhs && cursor <= rhs) return W_ADJ; if (cursor >= 0 && cursor <= strlen(command)) return W_SCROLL; return W_BAD; } store(x) register char *x; { register i, idx; char *strdup(); for (i=0; x[i]; i++) if (!isspace(x[i])) { idx = cmdnumber%HISTORY; if (history[idx]) free(history[idx]); if (history[idx] = strdup(x)) cmdnumber++; return; } } #if 1 complete() { register start, end, idx; register char *limit, *tail, *p; char pattern[HUGESIZE]; /* pattern we're looking for */ char *args[2]; /* fake argv array */ struct args_t expanded; /* expanded arglist */ if (!isspace(command[cursor])) { /* * get token to match... */ for (start=cursor-1; start>=0 && !isspace(command[start]); --start) ; ++start; for (end=cursor+1; command[end] && !isspace(command[end]); end++) ; /* * pull pattern out for wildargs() call */ strncpy(pattern, command+start, end-start); pattern[end-start] = 0; if (strchr(pattern, '*') == 0 && strchr(pattern, '?') == 0) { pattern[end-start] = '*'; pattern[1+(end-start)] = 0; } args[0] = (char*)0; args[1] = pattern; limit = &pattern[HUGESIZE - strlen(command)]; /* don't care about whether wildcard finds any matches, 'cause * we're gonna be copying things anyways */ expand_arglist(2, args, &expanded); for (tail=pattern,idx=0; idx= limit) break; *tail++ = ' '; while (*p) *tail++ = *p++; } free_arglist(&expanded); if (tail > pattern) { mark(); command[start] = 0; strcpy(tail, command+end); strcat(command, pattern+1); cursor = start + (int)((long)tail-(long)pattern) - 1; setrhs(); if (wpos() == W_SCROLL) wscroll(); else redisplay(); return 1; } } bell(); return 0; } #endif egetline(top_prompt, string) char *top_prompt; char *string; { int lastpos; register c; char hold[HUGESIZE]; register count; prompt = top_prompt; command= string; cursor = command[0] = lhs = rhs = onright = 0; undopos = -1; currline = cmdnumber; ttyputs("%s", prompt); if (catcher() != 0) return EOF; while ((c=getline(HUGESIZE-onright)) == ESC) { if (onright) strcpy(command+cursor, hold); setrhs(); if (wpos() == W_SCROLL) wscroll(); else if (onright) redisplay(); do { if (!movement(c=ttygetc(), count)) switch (c) { case CTRLC: return EOF; case 'a': c = 'i'; if (command[cursor]) ++cursor; mark(); break; case 'I': c = 'i'; cursor = 0; mark(); break; case 'A': c = 'i'; cursor = strlen(command); mark(); break; #if 1 case '*': if (complete()) c = 'i'; break; #endif case 'i': mark(); break; case 'x': delete('l', count); break; case 'X': delete('h', count); break; case 'd': delete(ttygetc(), count); break; case 'D': delete('$', 0); break; case 'y': yank(ttygetc(), count); break; case 'Y': yank('$', 0); break; case 's': if (delete('l', count)) c = 'i'; break; case 'S': if (delete('h', count)) c = 'i'; break; case 'c': if (delete(ttygetc(), count)) c = 'i'; break; case 'C': if (delete('$', 0)) c = 'i'; break; case 'p': if (command[cursor]) ++cursor; case 'P': mark(); if (isyanked && strlen(yanked) + strlen(command) < HUGESIZE) { strcpy(hold, &command[cursor]); strcpy(&command[cursor], yanked); strcat(command, hold); rhs = 0; } else bell(); break; case 'r': case '~': { register last; register repch; last = cursor+(count ? count : 1); if (last <= strlen(command)) { if (c == 'r' && (repch = ttygetc()) == ESC) break; mark(); for (; cursor < last; cursor++) if (c == 'r') command[cursor] = repch; else if (isalpha(command[cursor])) command[cursor] ^= 32; } else bell(); } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': count = (count*10) + (c-'0'); continue; case '/': searchfor(0); lastdir = c; break; case '?': searchback(0); lastdir = c; break; case 'n': if (lastdir == '/') searchfor(1); else if (lastdir == '?') searchback(1); else bell(); break; case '+': case 'j': movelines(count, 1); break; case '-': case 'k': movelines(-count, -1); break; case 'G': getold(count?count:cmdnumber); break; case '\r': onright = 0; goto breakout; case 'u': if (undopos >= 0) { strcpy(hold, command); /* swap lines */ strcpy(command, undostr); strcpy(undostr, hold); count = cursor; /* and pointers */ cursor = undopos; undopos = count; wscroll(); /* redisplay */ break; } case CTRLZ: if (!ignoreeof) softinterrupt(HUP); default: bell(); break; } count = 0; reposition(); } while (c != 'i'); onright = strlen(command+cursor); strcpy(hold, command+cursor); } if (c == EOF) return EOF; breakout: if (onright) { strcpy(command+cursor, hold); setrhs(); if (wpos() == W_SCROLL) wscroll(); else redisplay(); } ttyputc('\n'); return 0; }