home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.freefriends.org
/
ftp.freefriends.org.tar
/
ftp.freefriends.org
/
arnold
/
Source
/
mush.rstevens.tar.gz
/
mush.tar
/
curs_io.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-07-11
|
18KB
|
693 lines
/* @(#)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;
}