/* @(#)curs_io.c (c) copyright 3/18/87 (Dan Heller) */ /* curs_io.c -- curses based I/O */ #include "mush.h" #include "bindings.h" #include "glob.h" static backspace(); #if !defined(M_XENIX) || (defined(M_XENIX) && !defined(CURSES)) char *_unctrl[] = { "^@", "^A", "^B", "^C", "^D", "^E", "^F", "^G", "^H", "^I", "^J", "^K", "^L", "^M", "^N", "^O", "^P", "^Q", "^R", "^S", "^T", "^U", "^V", "^W", "^X", "^Y", "^Z", "^[", "^\\", "^]", "^~", "^_", " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "^?" }; #endif /* !M_XENIX || (M_XENIX && !CURSES) */ char del_line; /* tty delete line character */ char del_word; /* tty delete word character */ char del_char; /* backspace */ char reprint_line; /* usually ^R */ char eofc; /* usually ^D */ char lit_next; /* usually ^V */ char complete; /* word completion, usually ESC */ char complist; /* completion listing, usually ^D */ tty_settings() { int is_tty = isatty(0); if (is_tty) savetty(); #if defined(SYSV) || defined(AIX) eofc = _tty.c_cc[VEOF]; #else #ifdef BSD if (ioctl(0, TIOCGETC, &tchars) != -1) eofc = tchars.t_eofc; else #endif /* BSD */ eofc = CTRL('D'); #endif /* SYSV */ if (!is_tty) { del_line = CTRL('U'); del_char = CTRL('H'); } else { del_line = _tty.sg_kill; del_char = _tty.sg_erase; } #ifdef TIOCGLTC #ifndef AIX /* Just in case */ #ifndef AUX /* AUX defines TIOCGLTC but doesn't use it */ if (ioctl(0, TIOCGLTC, <chars) != -1) { del_word = ltchars.t_werasc; reprint_line = ltchars.t_rprntc; lit_next = ltchars.t_lnextc; } else #endif /* AUX */ #endif /* AIX */ #endif /* TIOCGLTC */ { del_word = CTRL('W'); reprint_line = CTRL('R'); lit_next = CTRL('V'); } } #ifdef Addch #undef Addch #endif /* Addch */ #ifndef CURSES /* Make sure all ifs have matching elses! */ #define Addch(c) \ if (ison(glob_flags, ECHO_FLAG)) \ {;} \ else \ (void) fputc(c, stdout), (void) fflush(stdout) #else /* see end of Getstr */ #define Addch(c) \ if (iscurses) \ addch(c), refresh(); \ else if (ison(glob_flags, ECHO_FLAG)) \ {;} \ else \ (void) fputc(c, stdout), (void) fflush(stdout) #endif /* CURSES */ /* * get a string of at most 'length' chars. * allow backspace-space-backspace, kill word and kill line * (options set by user in stty). * length is the max length this string can get. offset is from beginning * of string. * input of ^D returns -1; otherwise, return the number of chars in string. */ Getstr(String, length, offset) char String[]; register int length; { register int c, literal_next = FALSE, lit_bs = FALSE; struct cmd_map *curr_map; int count = offset, save_wc = wrapcolumn; (void) fflush(stdout); /* make sure everything is flushed before getting input */ if (mac_hide) { curr_map = NULL_MAP; wrapcolumn = 0; } else if (ison(glob_flags, IS_GETTING)) curr_map = bang_map; else if (iscurses) curr_map = NULL_MAP; else curr_map = line_map; while ((c = m_getchar()) != '\n' && c != '\r' && c != EOF && isoff(glob_flags, WAS_INTR)) { /* echo isn't set, so whatever the character, enter it */ if (ison(glob_flags, QUOTE_MACRO) || ison(glob_flags, ECHO_FLAG)) { if (count < length) { String[count++] = c; /* Note: Addch includes ECHO_FLAG test */ if (iscntrl(c)) { Addch('^'); Addch(_unctrl[c][1]); } else Addch(c); } else { print("\nWarning: string too long. Truncated at %d chars.", length); if (ison(glob_flags, QUOTE_MACRO)) { mac_flush(); m_ungetc(reprint_line); continue; } else break; } } /* ^D as the first char on a line or two ^D's in a row is EOF */ else if (c == eofc && !count) break; else if (c == '\\' && count < length) { literal_next = TRUE, lit_bs = FALSE; Addch(String[count++] = '\\'); } else if (c == lit_next && count < length) { literal_next = lit_bs = TRUE; String[count++] = '\\'; if (!in_macro()) { /* if (iscntrl(c)) */ Addch('^'); /* Addch(_unctrl[c][1]); */ } } else if (literal_next) { struct cmd_map *list; literal_next = FALSE; if (iscntrl(c) || c == del_line || c == del_char || c == del_word || c == lit_next || lit_bs) if (!in_macro() || !lit_bs) backspace(String, &count); else --count; else if (in_macro() && c == MAC_LONG_CMD) --count; /* check to see if user is escaping a map or map! */ else for (list = curr_map; list; list = list->m_next) if (list->m_str[0] == c) { if (!in_macro()) backspace(String, &count); else --count; break; } /* A literal-next advances the macro offset */ String[count++] = c; if (iscntrl(c) || c == del_char) { if (iscntrl(c)) { /* * Decrement wrapcolumn because two chars added. * It will be restored from save_wc before return. */ if (wrapcolumn > 1) wrapcolumn--; Addch('^'); } Addch(_unctrl[c][1]); } else Addch(c); } else if (complete && (c == complete || c == complist)) { (void) completion(String, &count, (c == complist), (c == complete)); } else if (c == del_line) { if (count) { do backspace(String, &count); while (count); } } else if (c == reprint_line) String[count] = 0, wprint("\n%s", String); else if (c == del_word) /* word erase */ while (count) { backspace(String, &count); if (!count || isspace(String[count-1]) && !isspace(String[count]) || !isalnum(String[count-1]) && isalnum(String[count])) break; } else if (c == del_char || c == CTRL('H') || c == 127 /* CTRL('?') */) { if (count) backspace(String, &count); /* if iscurses, then backspacing too far cancels a function */ else if (!count && iscurses && isoff(glob_flags, LINE_MACRO)) { mac_flush(); String[0] = '\0'; wrapcolumn = save_wc; return -1; } } else if (count == length) bell(); else if (c == '\t') do { /* Yuck -- tabs break map! */ Addch(' '); String[count] = ' '; } while (++count % 8 && count < length); else if (in_macro() && c == MAC_LONG_CMD) { char cbuf[MAX_LONG_CMD + 1]; if ((c = read_long_cmd(cbuf)) == 0) { c = MAC_LONG_CMD; goto check_expand; /* How could I avoid this? */ } else if (c > 0) { int ok; String[count] = '\0'; if ((ok = reserved_cmd(cbuf, TRUE)) > 0) { /* Reprint the line */ if (iscurses) print(":%s", String); else wprint("\r%s", String); continue; /* Get next char without changing count */ } else if (ok < 0) { String[offset] = '\0'; wrapcolumn = save_wc; return ok; } else goto push_back; } else { /* * Ooops. We read a bunch of stuff we should not * have read, because this isn't really a long command. * Use a trick to push the whole thing back, ala ungetc. * Wouldn't it be nifty if stdio worked this way? :-) */ push_back: if (c > 0) { cbuf[c++] = MAC_LONG_END; cbuf[c] = '\0'; } c = MAC_LONG_CMD; Ungetstr(cbuf); goto check_expand; /* How could I avoid this goto? */ } } else { check_expand: if (!curr_map || !check_map(c, curr_map)) { /* else if (match != MATCH) */ if (c != '\t' && iscntrl(c)) { Addch('^'); Addch(_unctrl[c][1]); /* Decrement wrapcolumn as above */ if (wrapcolumn > 1) wrapcolumn--; } else Addch(c); String[count++] = c; } } /* Null-terminate for macro lookup purposes. * This will be overwritten by the next character. */ String[count] = '\0'; if (line_wrap(String, &count)) break; } (void) fflush(stdout); /* for sys-v folks */ if (c == eofc || c == EOF || ison(glob_flags, WAS_INTR)) { if (feof(stdin)) clearerr(stdin); wrapcolumn = save_wc; return -1; } if (count && String[count-1] == '\\') { int count2; if (isoff(glob_flags, ECHO_FLAG)) putchar('\n'); wrapcolumn = save_wc; /* * NOTE: If the offset passed here is ever made greater than 0, * the value of wrapcolumn must again be changed/restored ... */ if ((count2 = Getstr(&String[count-1], length - count + 1, 0)) == -1) return -1; return count + count2; } if (!iscurses && isoff(glob_flags, ECHO_FLAG)) putchar('\n'); /* Should be null-terminated already, but just in case */ String[count] = '\0'; wrapcolumn = save_wc; return count; } static backspace(str, n) register char *str; int *n; { (*n)--; Addch('\b'); Addch(' '); Addch('\b'); if (iscntrl(str[*n])) { Addch('\b'); Addch(' '); Addch('\b'); /* Re-increment wrapcolumn -- see Getstr */ if (wrapcolumn) wrapcolumn++; } } #undef Addch /* * Check to see if what the user is typing is supposed to be expanded * into a longer string. The first char is 'c' and the map list to use * is in map_list. Continue looping (reading chars from stdin or a * currently active mapping) until a match happens or we've determined * that there is no match. */ check_map(c, map_list) char c; struct cmd_map *map_list; { char mbuf[MAX_MACRO_LEN], *p = mbuf; struct cmd_map *list; int m, n, match; *p++ = c; while (isoff(glob_flags, WAS_INTR)) { m = 0; *p = 0; /* make sure it's null terminated */ /* * loop thru the list of maps and check to see if the typed * char matches the mapping. If it matches completely, substitute * the stuff in x_str and return. If a partial match occurs, then * read the next char until a timeout or no match. */ for (list = map_list; list; list = list->m_next) { if ((match = prefix(mbuf, list->m_str)) == MATCH) { /* Must turn on flags BEFORE pushing */ line_macro(list->x_str); return 1; } else if (match != NO_MATCH) m++; /* something matched partially */ } if (!m) break; /* see if there's anything on the queue to read... */ if (mac_pending() #if !defined(SELECT) && !defined(M_UNIX) #ifdef FIONREAD || !ioctl(0, FIONREAD, &n) && n > 0 #else #ifdef M_XENIX || rdchk(0) > 0 #endif /* M_XENIX */ #endif /* FIONREAD */ #endif /* SELECT */ ) *p++ = m_getchar(); else { /* The user has typed the first part of a map or macro. Give him * a chance to finish it. */ #if defined(BSD) || defined(M_UNIX) || defined(SELECT) /* If the system has select(), use it. It's much faster and * more aesthetic since there is no mandatory timeout. */ struct timeval timer; #ifdef FD_SET fd_set rmask, wmask, xmask; FD_SET(0, &rmask); /* Test stdin for read */ FD_ZERO(&wmask); /* Don't care about write */ FD_ZERO(&xmask); /* Don't care about exception */ #else int rmask = 1, wmask = 0, xmask = 0; #endif /* FD_SET */ timer.tv_sec = 1; timer.tv_usec = 0; n = select(1, &rmask, &wmask, &xmask, &timer); #else /* !SELECT */ #ifdef FIONREAD /* system doesn't have select(), so use FIONREAD to see if * there are any chars on the queue to read. */ (void) sleep(1); (void) ioctl(0, FIONREAD, &n); #else #ifdef M_XENIX (void) sleep(1); n = rdchk(0); #else /* system has neither select() nor FIONREAD, so just set n * and force the user to either complete the map or fail it * without a timeout. Chars won't echo till he does one or * the other. */ n = 1; #endif /* M_XENIX */ #endif /* FIONREAD */ #endif /* SELECT */ if (n > 0) /* don't read all 'n' chars -- there may be a match early */ *p++ = m_getchar(); /* To flush macros and reset flags */ else /* still nothing to read? User doesn't want to use map */ break; } } /* no match or a timeout. This isn't a map, just return. */ *p = 0; if (mbuf[1]) (void) mac_push(mbuf + 1); return 0; } /* * Check for line wrap. This should happen only in composition mode and * only when the variable wrapcolumn has a value greater than zero. Line * wrap is implemented using Ungetstr [that is, mac_push()]. * * Returns 1 if the line was wrapped, 0 if not. */ line_wrap(string, count) char *string; /* The string to be wrapped */ int *count; /* Offset of string terminator */ { char *tail = NULL; int n = *count; if (wrapcolumn < 1 || *count <= wrapcolumn || isoff(glob_flags, IS_GETTING) /* Wrap only in msg body */ || ison(glob_flags, QUOTE_MACRO) /* Don't wrap quoted macros */ || ison(glob_flags, ECHO_FLAG)) /* Can't wrap in echo mode */ return 0; /* Back up past the wrapcolumn point */ for (; n > wrapcolumn; --n) ; /* Look for a space */ while (n && !isspace(string[n])) --n; /* If no break found, return no wrap */ if (!n) return 0; tail = &string[n+1]; /* Skip the break char and any whitespace */ while (n && isspace(string[n])) --n; ++n; /* move back into the whitespace */ /* Erase the stuff that will wrap */ while (*count > n) backspace(string,count); string[*count] = '\0'; /* Push the tail, if any */ if (*tail) Ungetstr(tail); return 1; } /* * Error bell used by completion() */ errbell(ret) int ret; { if (ret < 0 || !chk_option("quiet", "complete,completion")) bell(); return ret; } /* * Perform word completion on the input string */ completion(string, count, showlist, ignore) char *string; /* The string to be completed */ int *count; /* Offset of string terminator */ int showlist; /* Display list, complete if also ignore */ int ignore; /* Ignore the fignore matches, do complete */ { char buf[MAXPATHLEN], *b = buf, **exp; int n = *count, f, len, prefix, trim, overstrike, expandall; if (!*string || !*count) return errbell(-1); /* Look for a delimiter */ while (n > 0 && !index(DELIM, string[--n])) ; if (n > 0 || index(DELIM, string[n])) n++; b = buf + (len = Strcpy(buf, &string[n])); Debug("\nexpanding (%s) ... ", buf); if (!any(buf, FMETA)) { expandall = 0; overstrike = (*buf == '+' || *buf == '~' || *buf == '%'); trim = (overstrike && len > 1); if (!overstrike || len > 1 || (*buf == '+' && showlist)) *b++ = '*', *b = 0; /* Previous behavior for '+' completions (trailing '/'): if (len > 1 || *buf != '~' || *buf != '%') *b++ = '*', *b = 0; */ f = filexp(buf, &exp); if (*--b == '*') *b = 0; /* We need the original buf below */ } else { overstrike = 1; trim = (*buf == '+' || *buf == '~'); /* * Check first to see if the base pattern matches. * If not, append a '*' and try again. * Don't expand all matches in the latter case. */ if ((f = filexp(buf, &exp)) < 1) { *b++ = '*', *b = 0; f = filexp(buf, &exp); *--b = 0; /* We need the original buf below */ expandall = 0; } else expandall = !showlist; } if (ignore) f = fignore(f, &exp); if (f < 0) { Debug("globbing error!\n%s", string); free_vec(exp); return errbell(-1); } else if (f > 0) { Debug("result is: "), print_argv(exp); if (!expandall && f > 1) prefix = lcprefix(exp, overstrike ? 0 : len); else prefix = 0; if (showlist && (f > 1 || !ignore)) { int pfx = prefix; if (!expandall) while (pfx && exp[0][pfx - 1] != '/') --pfx; putchar('\n'); if (columnate(f, exp, pfx) < 0) (void) errbell(-1); /* Reprint the line */ if (iscurses) { wprint(":%s", string); turnon(glob_flags, CNTD_CMD); } else { if (isoff(glob_flags, IS_GETTING)) mail_status(1); wprint("%s", string); } if (!ignore) overstrike = 0; } if (ignore || !showlist) { if (expandall || strlen(exp[0]) > len) { if (!showlist) Debug("%s", string); if (overstrike && (prefix || expandall || f == 1)) { char *tmpv[3]; tmpv[0] = buf; if (trim) tmpv[1] = trim_filename(exp[0]); else tmpv[1] = exp[0]; tmpv[2] = NULL; /* Back up as far as is necessary */ len = lcprefix(tmpv, 0); /* If nothing will be erased, we may need to beep */ if (n + len == *count) { if (!expandall && !tmpv[1][len]) (void) errbell(0); } /* Erase the stuff that will complete */ while (*count > n + len) backspace(string,count); string[*count] = '\0'; } if (expandall || f == 1) { /* Unget the names IN REVERSE ORDER! */ while (f--) { if (trim) b = trim_filename(exp[f]); else b = exp[f]; if (f) { Ungetstr(b); Ungetstr(" "); } else Ungetstr(b + len); } } else { if (prefix > len) { exp[0][prefix] = 0; if (!showlist) Debug("\ncompletion is (%s)\n%s", exp[0], string); if (trim) Ungetstr(trim_filename(exp[0]) + len); else Ungetstr(&exp[0][len]); } else if (!showlist) Debug("\nno longer prefix\n%s", string); /* Special case because "+" always tries to expand "+*" * to get listings and avoid getpath()'s trailing '/'. * No error bell is needed in those cases. */ if (strcmp(buf, "+") != 0) (void) errbell(0); } } else { Debug("no longer prefix\n%s", string); (void) errbell(0); } } } else { Debug("no match\n%s", string); (void) errbell(0); } free_vec(exp); return 1; } fignore(argc, argvp) int argc; char ***argvp; { char *fign = do_set(set_options, "fignore"); char **flist, buf[MAXPATHLEN], *b = buf; int fcnt, i; if (argc < 2 || !fign || !*fign) return argc; if (!argvp || !*argvp && !**argvp) return -1; if ((flist = mk_argv(fign, &fcnt, FALSE)) && fcnt > 0) { *b++ = '*'; for (i = 0; i < fcnt; i++) { if (flist[i][0] == '.' && !any(flist[i], FMETA)) { (void) strcpy(b, flist[i]); (void) strdup(flist[i], buf); } } Debug("ignoring "), print_argv(flist); fcnt = gdiffv(argc, argvp, fcnt, flist); free_vec(flist); if (fcnt == 0) fcnt = argc; else { free_elems(&((*argvp)[fcnt])); (*argvp)[fcnt] = NULL; } } return fcnt; }