/* input.c -- implements input.h */ #include "input.h" /* input.c * * Various input routines * * written by Daniel Lawrence 5/9/86 * modified by Petri Kutvonen */ #include #include #include #include "bind.h" #include "estruct.h" #include "bindable.h" #include "display.h" #include "exec.h" #include "names.h" #include "terminal.h" #include "utf8.h" #include "wrapper.h" #if PKCODE #if MSDOS && TURBO #include #endif #endif #if PKCODE && (UNIX || (MSDOS && TURBO)) #define COMPLC 1 #else #define COMPLC 0 #endif #define NKBDM 256 /* # of strokes, keyboard macro */ int kbdm[ NKBDM] ; /* Macro */ int *kbdptr ; /* current position in keyboard buf */ int *kbdend = &kbdm[0] ; /* ptr to end of the keyboard */ kbdstate kbdmode = STOP ; /* current keyboard macro mode */ int lastkey = 0 ; /* last keystoke */ int kbdrep = 0 ; /* number of repetitions */ int disinp = TRUE ; /* display input characters */ int metac = CONTROL | '[' ; /* current meta character */ int ctlxc = CONTROL | 'X' ; /* current control X prefix char */ int reptc = CONTROL | 'U' ; /* current universal repeat char */ int abortc = CONTROL | 'G' ; /* current abort command char */ static const int quotec = 0x11 ; /* quote char during mlreply() */ static void outstring( char *s) ; /* * Ask a yes or no question in the message line. Return either TRUE, FALSE, or * ABORT. The ABORT status is returned if the user bumps out of the question * with a ^G. Used any time a confirmation is required. */ int mlyesno( const char *prompt) { char c; /* input character */ for (;;) { /* prompt the user */ mlwrite( "%s (y/n)? ", prompt) ; /* get the response */ c = tgetc(); if (c == ectoc(abortc)) /* Bail out! */ return ABORT; if (c == 'y' || c == 'Y') return TRUE; if (c == 'n' || c == 'N') return FALSE; } } /* * Write a prompt into the message line, then read back a response. Keep * track of the physical position of the cursor. If we are in a keyboard * macro throw the prompt away, and return the remembered response. This * lets macros run at full speed. The reply is always terminated by a carriage * return. Handle erase, kill, and abort keys. */ int mlreply( const char *prompt, char *buf, int nbuf) { return nextarg(prompt, buf, nbuf, ctoec('\n')); } int mlreplyt(const char *prompt, char *buf, int nbuf, int eolchar) { return nextarg(prompt, buf, nbuf, eolchar); } /* * ectoc: * expanded character to character * collapse the CONTROL and SPEC flags back into an ascii code */ int ectoc(int c) { if (c & CONTROL) c = c & ~(CONTROL | 0x40); if (c & SPEC) c = c & 255; return c; } /* * ctoec: * character to extended character * pull out the CONTROL and SPEC prefixes (if possible) */ int ctoec(int c) { if (c >= 0x00 && c <= 0x1F) c = CONTROL | (c + '@'); return c; } /* * get a command name from the command line. Command completion means * that pressing a will attempt to complete an unfinished command * name if it is unique. */ fn_t getname(void) { int cpos; /* current column on screen output */ int c; char *sp; /* pointer to string for output */ struct name_bind *ffp; /* first ptr to entry in name binding table */ struct name_bind *cffp; /* current ptr to entry in name binding table */ struct name_bind *lffp; /* last ptr to entry in name binding table */ char buf[NSTRING]; /* buffer to hold tentative command name */ /* starting at the beginning of the string buffer */ cpos = 0; /* if we are executing a command line get the next arg and match it */ if (clexec) { if( macarg( buf, sizeof buf) != TRUE) return NULL; return fncmatch(&buf[0]); } /* build a name string from the keyboard */ while (TRUE) { c = tgetc(); /* if we are at the end, just match it */ if (c == 0x0d) { buf[cpos] = 0; /* and match it off */ return fncmatch(&buf[0]); } else if (c == ectoc(abortc)) { /* Bell, abort */ ctrlg(FALSE, 0); TTflush(); return NULL; } else if (c == 0x7F || c == 0x08) { /* rubout/erase */ if (cpos != 0) { TTputc('\b'); TTputc(' '); TTputc('\b'); --ttcol; --cpos; TTflush(); } } else if (c == 0x15) { /* C-U, kill */ while (cpos != 0) { TTputc('\b'); TTputc(' '); TTputc('\b'); --cpos; --ttcol; } TTflush(); } else if (c == ' ' || c == 0x1b || c == 0x09) { /* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */ /* attempt a completion */ buf[cpos] = 0; /* terminate it for us */ ffp = &names[0]; /* scan for matches */ while (ffp->n_func != NULL) { if (strncmp(buf, ffp->n_name, strlen(buf)) == 0) { /* a possible match! More than one? */ if ((ffp + 1)->n_func == NULL || (strncmp (buf, (ffp + 1)->n_name, strlen(buf)) != 0)) { /* no...we match, print it */ sp = ffp->n_name + cpos; while (*sp) TTputc(*sp++); TTflush(); return ffp->n_func; } else { /* << << << << << << << << << << << << << << << << << */ /* try for a partial match against the list */ /* first scan down until we no longer match the current input */ lffp = (ffp + 1); while ((lffp + 1)->n_func != NULL) { if (strncmp (buf, (lffp + 1)->n_name, strlen(buf)) != 0) break; ++lffp; } /* and now, attempt to partial complete the string, char at a time */ while (TRUE) { /* add the next char in */ buf[cpos] = ffp-> n_name[cpos]; /* scan through the candidates */ cffp = ffp + 1; while (cffp <= lffp) { if (cffp-> n_name [cpos] != buf [cpos]) goto onward; ++cffp; } /* add the character */ TTputc(buf [cpos++]); } /* << << << << << << << << << << << << << << << << << */ } } ++ffp; } /* no match.....beep and onward */ TTbeep(); onward:; TTflush(); /* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */ } else { if (cpos < NSTRING - 1 && c > ' ') { buf[cpos++] = c; TTputc(c); } ++ttcol; TTflush(); } } } /* tgetc: Get a key from the terminal driver, resolve any keyboard macro action */ int tgetc(void) { int c; /* fetched character */ /* if we are playing a keyboard macro back, */ if (kbdmode == PLAY) { /* if there is some left... */ if (kbdptr < kbdend) return (int) *kbdptr++; /* at the end of last repitition? */ if (--kbdrep < 1) { kbdmode = STOP; #if VISMAC == 0 /* force a screen update after all is done */ update(FALSE); #endif } else { /* reset the macro to the begining for the next rep */ kbdptr = &kbdm[0]; return (int) *kbdptr++; } } /* fetch a character from the terminal driver */ c = TTgetc(); /* record it for $lastkey */ lastkey = c; /* save it if we need to */ if (kbdmode == RECORD) { *kbdptr++ = c; kbdend = kbdptr; /* don't overrun the buffer */ if (kbdptr == &kbdm[NKBDM - 1]) { kbdmode = STOP; TTbeep(); } } /* and finally give the char back */ return c; } /* GET1KEY: Get one keystroke. The only prefixs legal here are the SPEC and CONTROL prefixes. */ int get1key(void) { int c; /* get a keystroke */ c = tgetc(); #if MSDOS if (c == 0) { /* Apply SPEC prefix */ c = tgetc(); if (c >= 0x00 && c <= 0x1F) /* control key? */ c = CONTROL | (c + '@'); return SPEC | c; } #endif if (c >= 0x00 && c <= 0x1F) /* C0 control -> C- */ c = CONTROL | (c + '@'); return c; } /* GETCMD: Get a command from the keyboard. Process all applicable prefix keys */ int getcmd(void) { int c; /* fetched keystroke */ #if VT220 int d; /* second character P.K. */ int cmask = 0; #endif /* get initial character */ c = get1key(); #if VT220 proc_metac: #endif if (c == 128+27) /* CSI */ goto handle_CSI; /* process META prefix */ if (c == (CONTROL | '[')) { c = get1key(); #if VT220 if (c == '[' || c == 'O') { /* CSI P.K. */ handle_CSI: c = get1key(); if (c >= 'A' && c <= 'D') return SPEC | c | cmask; if (c >= 'E' && c <= 'z' && c != 'i' && c != 'c') return SPEC | c | cmask; d = get1key(); if (d == '~') /* ESC [ n ~ P.K. */ return SPEC | c | cmask; switch (c) { /* ESC [ n n ~ P.K. */ case '1': c = d + 32; break; case '2': c = d + 48; break; case '3': c = d + 64; break; default: c = '?'; break; } if (d != '~') /* eat tilde P.K. */ get1key(); if (c == 'i') { /* DO key P.K. */ c = ctlxc; goto proc_ctlxc; } else if (c == 'c') /* ESC key P.K. */ c = get1key(); else return SPEC | c | cmask; } #endif #if VT220 if (c == (CONTROL | '[')) { cmask = META; goto proc_metac; } #endif if (islower(c)) /* Force to upper */ c ^= DIFCASE; if (c >= 0x00 && c <= 0x1F) /* control key */ c = CONTROL | (c + '@'); return META | c; } #if PKCODE else if (c == metac) { c = get1key(); #if VT220 if (c == (CONTROL | '[')) { cmask = META; goto proc_metac; } #endif if (islower(c)) /* Force to upper */ c ^= DIFCASE; if (c >= 0x00 && c <= 0x1F) /* control key */ c = CONTROL | (c + '@'); return META | c; } #endif #if VT220 proc_ctlxc: #endif /* process CTLX prefix */ if (c == ctlxc) { c = get1key(); #if VT220 if (c == (CONTROL | '[')) { cmask = CTLX; goto proc_metac; } #endif if (c >= 'a' && c <= 'z') /* Force to upper */ c -= 0x20; if (c >= 0x00 && c <= 0x1F) /* control key */ c = CONTROL | (c + '@'); return CTLX | c; } /* Accept UTF-8 sequence */ if( c <= 0xC1 || c > 0xF4) return c ; else { char utf[ 4] ; char cc ; utf[ 0] = c ; utf[ 1] = cc = get1key() ; if( (c & 0x20) && ((cc & 0xC0) == 0x80)) { /* at least 3 bytes and a valid encoded char */ utf[ 2] = cc = get1key() ; if( (c & 0x10) && ((cc & 0xC0) == 0x80)) /* at least 4 bytes and a valid encoded char */ utf[ 3] = get1key() ; } utf8_to_unicode( utf, 0, sizeof utf, (unicode_t *) &c) ; } /* otherwise, just return it */ return c; } /* A more generalized prompt/reply function allowing the caller to specify the proper terminator. If the terminator is not a return ('\n') it will echo as "" */ int getstring( const char *prompt, char *buf, int nbuf, int eolchar) { int cpos; /* current character position in string */ int c; boolean quotef ; /* are we quoting the next char? */ #if COMPLC int ffile, ocpos, nskip = 0, didtry = 0; #if MSDOS struct ffblk ffblk; char *fcp; #endif #if UNIX static char tmp[] = "/tmp/meXXXXXX"; FILE *tmpf = NULL; #endif ffile = (strcmp(prompt, "Find file: ") == 0 || strcmp(prompt, "View file: ") == 0 || strcmp(prompt, "Insert file: ") == 0 || strcmp(prompt, "Write file: ") == 0 || strcmp(prompt, "Read file: ") == 0 || strcmp(prompt, "File to execute: ") == 0); #endif cpos = 0; quotef = FALSE; /* prompt the user for the input string */ mlwrite(prompt); for (;;) { #if COMPLC if (!didtry) nskip = -1; didtry = 0; #endif /* get a character from the user */ c = get1key(); /* If it is a , change it to a */ #if PKCODE if (c == (CONTROL | 0x4d) && !quotef) #else if (c == (CONTROL | 0x4d)) #endif c = CONTROL | 0x40 | '\n'; /* if they hit the line terminate, wrap it up */ if (c == eolchar && quotef == FALSE) { buf[cpos++] = 0; /* clear the message line */ mlwrite(""); TTflush(); /* if we default the buffer, return FALSE */ if (buf[0] == 0) return FALSE; return TRUE; } /* change from command form back to character form */ c = ectoc(c); if (c == ectoc(abortc) && quotef == FALSE) { /* Abort the input? */ ctrlg(FALSE, 0); TTflush(); return ABORT; } else if ((c == 0x7F || c == 0x08) && quotef == FALSE) { /* rubout/erase */ if (cpos != 0) { outstring("\b \b"); --ttcol; if (buf[--cpos] < 0x20) { outstring("\b \b"); --ttcol; } if (buf[cpos] == '\n') { outstring("\b\b \b\b"); ttcol -= 2; } TTflush(); } } else if (c == 0x15 && quotef == FALSE) { /* C-U, kill */ while (cpos != 0) { outstring("\b \b"); --ttcol; if (buf[--cpos] < 0x20) { outstring("\b \b"); --ttcol; } if (buf[cpos] == '\n') { outstring("\b\b \b\b"); ttcol -= 2; } } TTflush(); #if COMPLC } else if ((c == 0x09 || c == ' ') && quotef == FALSE && ffile) { /* TAB, complete file name */ char ffbuf[255]; #if MSDOS char sffbuf[128]; int lsav = -1; #endif int n, iswild = 0; didtry = 1; ocpos = cpos; while (cpos != 0) { outstring("\b \b"); --ttcol; if (buf[--cpos] < 0x20) { outstring("\b \b"); --ttcol; } if (buf[cpos] == '\n') { outstring("\b\b \b\b"); ttcol -= 2; } if (buf[cpos] == '*' || buf[cpos] == '?') iswild = 1; #if MSDOS if (lsav < 0 && (buf[cpos] == '\\' || buf[cpos] == '/' || buf[cpos] == ':' && cpos == 1)) lsav = cpos; #endif } TTflush(); if (nskip < 0) { buf[ocpos] = 0; #if UNIX if (tmpf != NULL) fclose(tmpf); strcpy(tmp, "/tmp/meXXXXXX"); strcpy(ffbuf, "echo "); strcat(ffbuf, buf); if (!iswild) strcat(ffbuf, "*"); strcat(ffbuf, " >"); xmkstemp(tmp); strcat(ffbuf, tmp); strcat(ffbuf, " 2>&1"); ue_system( ffbuf) ; tmpf = fopen(tmp, "r"); #endif #if MSDOS strcpy(sffbuf, buf); if (!iswild) strcat(sffbuf, "*.*"); #endif nskip = 0; } #if UNIX c = ' '; for (n = nskip; n > 0; n--) while ((c = getc(tmpf)) != EOF && c != ' '); #endif #if MSDOS if (nskip == 0) { strcpy(ffbuf, sffbuf); c = findfirst(ffbuf, &ffblk, FA_DIREC) ? '*' : ' '; } else if (nskip > 0) c = findnext(&ffblk) ? 0 : ' '; #endif nskip++; if (c != ' ') { TTbeep(); nskip = 0; } #if UNIX while ((c = getc(tmpf)) != EOF && c != '\n' && c != ' ' && c != '*') #endif #if MSDOS if (c == '*') fcp = sffbuf; else { strncpy(buf, sffbuf, lsav + 1); cpos = lsav + 1; fcp = ffblk.ff_name; } while (c != 0 && (c = *fcp++) != 0 && c != '*') #endif { if (cpos < nbuf - 1) buf[cpos++] = c; } #if UNIX if (c == '*') TTbeep(); #endif for (n = 0; n < cpos; n++) { c = buf[n]; if ((c < ' ') && (c != '\n')) { outstring("^"); ++ttcol; c ^= 0x40; } if (c != '\n') { if (disinp) TTputc(c); } else { /* put out for */ outstring(""); ttcol += 3; } ++ttcol; } TTflush(); #if UNIX rewind(tmpf); unlink(tmp); #endif #endif } else if ((c == quotec || c == 0x16) && quotef == FALSE) { quotef = TRUE; } else { quotef = FALSE; if (cpos < nbuf - 1) { buf[cpos++] = c; if ((c < ' ') && (c != '\n')) { outstring("^"); ++ttcol; c ^= 0x40; } if (c != '\n') { if (disinp) TTputc(c); } else { /* put out for */ outstring(""); ttcol += 3; } ++ttcol; TTflush(); } } } } /* * output a string of characters when display input is enabled * * char *s; string to output */ static void outstring( char *s) { if( disinp) while( *s) TTputc( *s++) ; } /* * output a string of output characters * * char *s; string to output */ void ostring( char *s) { if (discmd) while (*s) TTputc( *s++ & 0xFF) ; }