home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 5 Edit
/
05-Edit.zip
/
vile-src.zip
/
vile-8.1
/
bind.c
< prev
next >
Wrap
C/C++ Source or Header
|
1998-07-23
|
56KB
|
2,565 lines
/* This file is for functions having to do with key bindings,
* descriptions, help commands and startup file.
*
* written 11-feb-86 by Daniel Lawrence
*
* $Header: /usr/build/vile/vile/RCS/bind.c,v 1.183 1998/07/23 09:19:56 cmorgan Exp $
*
*/
#ifdef _WIN32
# include <windows.h>
#endif
#include "estruct.h"
#include "edef.h"
#include "nefunc.h"
#define BI_DATA NBST_DATA
#include "btree.h"
#define SHORT_CMD_LEN 4 /* command names longer than this are preferred
over those shorter. e.g. display "quit"
instead of "q" if possible */
extern const int nametblsize;
static KBIND * kcode2kbind ( int code );
#if OPT_CASELESS
#define Strcmp(s,d) cs_strcmp(case_insensitive, s, d)
#define StrNcmp(s,d,len) cs_strncmp(case_insensitive, s, d, len)
#else
#define Strcmp(s,d) strcmp(s, d)
#define StrNcmp(s,d,len) strncmp(s, d, len)
#endif
#if OPT_REBIND
#define isSpecialCmd(k) \
( (k == &f_cntl_a_func)\
||(k == &f_cntl_x_func)\
||(k == &f_poundc_func)\
||(k == &f_unarg_func)\
||(k == &f_esc_func)\
)
static char * kcod2prc (int c, char *seq);
static int install_bind (int c, const CMDFUNC *kcmd, const CMDFUNC **oldfunc);
static int key_to_bind ( const CMDFUNC *kcmd );
static int rebind_key ( int c, const CMDFUNC *kcmd );
static int strinc (const char *sourc, const char *sub);
static int unbindchar ( int c );
static int update_binding_list ( BUFFER *bp );
static void makebindlist (LIST_ARGS);
#endif /* OPT_REBIND */
#if OPT_NAMEBST
static int kbd_complete_bst( int case_insensitive, int c, char *buf, unsigned *pos);
#else
#define kbd_complete_bst(case_insensitive, c, buf, pos) \
kbd_complete(case_insensitive, c, buf, pos, \
(const char *)&nametbl[0], sizeof(nametbl[0]))
#endif /* OPT_NAMEBST */
#if OPT_EVAL || OPT_REBIND
static const NTAB * fnc2ntab ( const CMDFUNC *cfp );
static int prc2kcod ( const char *kk );
#endif
#if OPT_REBIND
static KBIND *KeyBindings = kbindtbl;
#endif
/*----------------------------------------------------------------------------*/
#if OPT_NAMEBST
static BI_NODE*
new_namebst (BI_DATA *a)
{
BI_NODE *p = typecalloc(BI_NODE);
p->value = *a;
if (!(a->n_flags & NBST_READONLY))
BI_KEY(p) = strmalloc(a->bi_key);
return p;
}
static void
old_namebst (BI_NODE *a)
{
if (!(a->value.n_flags & NBST_READONLY))
free(TYPECAST(char,BI_KEY(a)));
free(a);
}
static void
dpy_namebst (BI_NODE *a GCC_UNUSED, int level GCC_UNUSED)
{
#if OPT_TRACE
while (level-- > 0)
TRACE((". "));
TRACE(("%p -> %s (%d)\n", a, BI_KEY(a), a->balance));
#endif
}
static BI_TREE namebst = {new_namebst, old_namebst, dpy_namebst};
#endif /* OPT_NAMEBST */
/*----------------------------------------------------------------------------*/
int
no_such_function(const char * fnp)
{
mlforce("[No such function \"%s\"]", fnp != 0 ? fnp : "");
return FALSE;
}
/* give me some help!!!! bring up a buffer and read the help file into it */
/* ARGSUSED */
int
help(int f GCC_UNUSED, int n GCC_UNUSED)
{
register BUFFER *bp; /* buffer pointer to help */
char *fname; /* ptr to file returned by flook() */
int alreadypopped;
/* first check if we are already here */
bp = bfind(HELP_BufName, BFSCRTCH);
if (bp == NULL)
return FALSE;
if (bp->b_active == FALSE) { /* never been used */
fname = flook(helpfile, FL_ANYWHERE|FL_READABLE);
if (fname == NULL) {
mlforce("[Sorry, can't find the help information]");
(void)zotbuf(bp);
return(FALSE);
}
alreadypopped = (bp->b_nwnd != 0);
/* and read the stuff in */
if (readin(fname, 0, bp, TRUE) == FALSE ||
popupbuff(bp) == FALSE) {
(void)zotbuf(bp);
return(FALSE);
}
set_bname(bp, HELP_BufName);
set_rdonly(bp, fname, MDVIEW);
make_local_b_val(bp,MDIGNCASE); /* easy to search, */
set_b_val(bp,MDIGNCASE,TRUE);
b_set_scratch(bp);
if (!alreadypopped)
shrinkwrap();
}
if (!swbuffer(bp))
return FALSE;
if (help_at >= 0) {
if (!gotoline(TRUE, help_at))
return FALSE;
mlwrite("[Type '1G' to return to start of help information]");
help_at = -1; /* until zotbuf is called, we let normal
DOT tracking keep our position */
}
return TRUE;
}
#if OPT_REBIND
#if OPT_TERMCHRS
/* patch: this table and the corresponding initializations should be
* generated by 'mktbls'. In any case, the table must be sorted to use
* name-completion on it.
*/
static const struct {
const char *name;
int *value;
char how_to;
} TermChrs[] = {
{"backspace", &backspc, 's'},
{"interrupt", &intrc, 's'},
{"line-kill", &killc, 's'},
{"mini-edit", &editc, 's'},
{"name-complete", &name_cmpl, 0},
{"quote-next", "ec, 0},
{"start-output", &startc, 's'},
{"stop-output", &stopc, 's'},
{"suspend", &suspc, 's'},
{"test-completions", &test_cmpl, 0},
{"word-kill", &wkillc, 's'},
{0}
};
/*----------------------------------------------------------------------------*/
/* list the current chrs into the current buffer */
/* ARGSUSED */
static void
makechrslist(int dum1 GCC_UNUSED, void *ptr GCC_UNUSED)
{
register int i;
char temp[NLINE];
bprintf("--- Terminal Character Settings %*P\n", term.t_ncol-1, '-');
for (i = 0; TermChrs[i].name != 0; i++) {
bprintf("\n%s = %s",
TermChrs[i].name,
kcod2prc(*(TermChrs[i].value), temp));
}
}
/*
* Find a special-character definition, given the name
*/
static int
chr_lookup(const char *name)
{
register int j;
for (j = 0; TermChrs[j].name != 0; j++)
if (!strcmp(name, TermChrs[j].name))
return j;
return -1;
}
/*
* The 'chr_complete()' and 'chr_eol()' functions are invoked from
* 'kbd_reply()' to setup the mode-name completion and query displays.
*/
static int
chr_complete(int c, char *buf, unsigned *pos)
{
return kbd_complete(FALSE, c, buf, pos, (const char *)&TermChrs[0],
sizeof(TermChrs[0]));
}
static int
/*ARGSUSED*/
chr_eol(
const char * buffer GCC_UNUSED,
unsigned cpos GCC_UNUSED,
int c GCC_UNUSED,
int eolchar GCC_UNUSED)
{
return isSpace(c);
}
#if OPT_UPBUFF
/* ARGSUSED */
static int
update_termchrs(BUFFER *bp GCC_UNUSED)
{
return show_termchrs(FALSE,1);
}
#endif
/* ARGSUSED */
int
set_termchrs(int f GCC_UNUSED, int n GCC_UNUSED)
{
register int s, j;
static TBUFF *name;
int c;
/* get the table-entry */
tb_scopy(&name, "");
if ((s = kbd_reply("Terminal setting: ", &name, chr_eol,
' ', 0, chr_complete)) == TRUE) {
j = chr_lookup(tb_values(name));
switch (TermChrs[j].how_to) {
case 's':
default:
c = key_to_bind((CMDFUNC *)0);
if (c < 0)
return(FALSE);
*(TermChrs[j].value) = c;
break;
}
update_scratch(TERMINALCHARS_BufName, update_termchrs);
}
return s;
}
/* ARGSUSED */
int
show_termchrs(int f GCC_UNUSED, int n GCC_UNUSED)
{
return liststuff(TERMINALCHARS_BufName, FALSE, makechrslist, 0, (void *)0);
}
#endif /* OPT_TERMCHRS */
static void
ostring( /* output a string of output characters */
char *s) /* string to output */
{
if (discmd)
kbd_puts(s);
}
/* bindkey: add a new key to the key binding table */
/* ARGSUSED */
int
bindkey(int f GCC_UNUSED, int n GCC_UNUSED)
{
register const CMDFUNC *kcmd; /* ptr to the requested function to bind to */
char cmd[NLINE];
char *fnp;
/* prompt the user to type in a key to bind */
/* and get the function name to bind it to */
fnp = kbd_engl("Bind function whose full name is: ", cmd);
if (fnp == NULL || (kcmd = engl2fnc(fnp)) == NULL) {
return no_such_function(fnp);
}
return rebind_key(key_to_bind(kcmd), kcmd);
}
/*
* Prompt-for and return the key-code to bind.
*/
static int
key_to_bind(register const CMDFUNC *kcmd)
{
char outseq[NLINE]; /* output buffer for keystroke sequence */
register int c;
mlprompt("...to keyboard sequence (type it exactly): ");
/* get the command sequence to bind */
if (clexec) {
char tok[NSTRING];
macarg(tok); /* get the next token */
c = prc2kcod(tok);
} else {
/* perhaps we only want a single key, not a sequence */
/* (see more comments below) */
if (isSpecialCmd(kcmd))
c = keystroke();
else
c = kbd_seq();
}
if (c >= 0) {
/* change it to something we can print as well */
ostring(kcod2prc(c, outseq));
hst_append_s(outseq, FALSE);
} else {
mlforce("[Not a proper key-sequence]");
}
return c;
}
/*
* Given a key-code and a command-function pointer, rebind the key-code to
* the command-function.
*/
static int
rebind_key (
register int c,
register const CMDFUNC *kcmd)
{
static const CMDFUNC ignored = { INIT_UNION( unimpl ) },
*old = &ignored;
return install_bind (c, kcmd, &old);
}
/*
* Prefix-keys can be only bound to one value. This procedure tests the
* argument 'kcmd' to see if it is a prefix key, and if so, unbinds the
* key, and sets the corresponding global variable to the new value.
* The calling procedure will then do the binding per se.
*/
static void
reset_prefix (
register int c,
register const CMDFUNC *kcmd)
{
if (isSpecialCmd(kcmd)) {
register int j;
/* search for an existing binding for the prefix key */
if ((j = fnc2kcod(kcmd)) >= 0)
(void)unbindchar(j);
/* reset the appropriate global prefix variable */
if (kcmd == &f_cntl_a_func)
cntl_a = c;
if (kcmd == &f_cntl_x_func)
cntl_x = c;
if (kcmd == &f_poundc_func)
poundc = c;
if (kcmd == &f_unarg_func)
reptc = c;
if (kcmd == &f_esc_func)
abortc = c;
}
}
/*
* Bind a command-function pointer to a given key-code (saving the old
* value of the function-pointer via an pointer given by the caller).
*/
static int
install_bind (
register int c,
register const CMDFUNC *kcmd,
const CMDFUNC **oldfunc)
{
register KBIND *kbp; /* pointer into a binding table */
if (c < 0)
return FALSE; /* not a legal key-code */
/* if the function is a prefix key, i.e. we're changing the definition
of a prefix key, then they typed a dummy function name, which
has been translated into a dummy function pointer */
*oldfunc = kcod2fnc(c);
reset_prefix(-1, *oldfunc);
reset_prefix(c, kcmd);
if (!isspecial(c)) {
asciitbl[c] = TYPECAST(CMDFUNC,kcmd);
} else {
if ((kbp = kcode2kbind(c)) != 0) { /* change it in place */
kbp->k_cmd = kcmd;
} else {
if ((kbp = typealloc(KBIND)) == 0) {
return no_memory("Key-Binding");
}
kbp->k_link = KeyBindings;
kbp->k_code = (short)c; /* add keycode */
kbp->k_cmd = kcmd; /* and func pointer */
KeyBindings = kbp;
}
}
update_scratch(BINDINGLIST_BufName, update_binding_list);
return(TRUE);
}
/* unbindkey: delete a key from the key binding table */
/* ARGSUSED */
int
unbindkey(int f GCC_UNUSED, int n GCC_UNUSED)
{
register int c; /* command key to unbind */
char outseq[NLINE]; /* output buffer for keystroke sequence */
/* prompt the user to type in a key to unbind */
mlprompt("Unbind this key sequence: ");
/* get the command sequence to unbind */
if (clexec) {
char tok[NSTRING];
macarg(tok); /* get the next token */
c = prc2kcod(tok);
if (c < 0) {
mlforce("[Illegal key-sequence \"%s\"]",tok);
return FALSE;
}
} else {
c = kbd_seq();
if (c < 0) {
mlforce("[Not a bindable key-sequence]");
return(FALSE);
}
}
/* change it to something we can print as well */
ostring(kcod2prc(c, outseq));
/* if it isn't bound, bitch */
if (unbindchar(c) == FALSE) {
mlforce("[Key not bound]");
return(FALSE);
}
update_scratch(BINDINGLIST_BufName, update_binding_list);
return(TRUE);
}
static int
unbindchar(int c) /* command key to unbind */
{
register KBIND *kbp; /* pointer into the command table */
register KBIND *skbp; /* saved pointer into the command table */
if (!isspecial(c))
{
if (asciitbl[c])
{
asciitbl[c] = 0;
return TRUE;
}
return FALSE;
}
/* check first entry in KeyBindings table */
kbp = skbp = KeyBindings;
if (kbp->k_code == c)
{
KeyBindings = kbp->k_link;
free(kbp);
return TRUE;
}
/* check KeyBindings */
while (kbp != kbindtbl)
{
if (kbp->k_code == c)
{
/* relink previous */
skbp->k_link = kbp->k_link;
free(kbp);
return TRUE;
}
skbp = kbp;
kbp = kbp->k_link;
}
/* nope, check kbindtbl */
skbp = 0;
for (skbp = 0; kbp->k_cmd; kbp++)
{
if (!skbp && kbp->k_code == c)
skbp = kbp;
}
/* not found */
if (!skbp)
return FALSE;
--kbp; /* backup to the last legit entry */
if (skbp != kbp)
{
/* copy the last entry to the current one */
skbp->k_code = kbp->k_code;
skbp->k_cmd = kbp->k_cmd;
}
/* null out the last one */
kbp->k_code = 0;
kbp->k_cmd = 0;
return TRUE;
}
/* describe bindings bring up a fake buffer and list the key bindings
into it with view mode */
/* remember whether we last did "apropos" or "describe-bindings" */
static char *last_apropos_string;
static CMDFLAGS last_whichcmds;
static int append_to_binding_list;
/* ARGSUSED */
static int
update_binding_list(BUFFER *bp GCC_UNUSED)
{
return liststuff(BINDINGLIST_BufName, append_to_binding_list,
makebindlist, (int)last_whichcmds, (void *)last_apropos_string);
}
/* ARGSUSED */
int
desbind(int f GCC_UNUSED, int n GCC_UNUSED)
{
last_apropos_string = (char *)0;
last_whichcmds = 0;
return update_binding_list((BUFFER *)0);
}
/* ARGSUSED */
int
desmotions(int f GCC_UNUSED, int n GCC_UNUSED)
{
last_apropos_string = (char *)0;
last_whichcmds = MOTION;
return update_binding_list((BUFFER *)0);
}
/* ARGSUSED */
int
desopers(int f GCC_UNUSED, int n GCC_UNUSED)
{
last_apropos_string = (char *)0;
last_whichcmds = OPER;
return update_binding_list((BUFFER *)0);
}
/* ARGSUSED */
int
desapro(int f GCC_UNUSED, int n GCC_UNUSED) /* Apropos (List functions that match a substring) */
{
register int s;
static char mstring[NSTRING]; /* string to match cmd names to */
s = mlreply("Apropos string: ", mstring, sizeof(mstring));
if (s != TRUE)
return(s);
last_apropos_string = mstring;
last_whichcmds = 0;
return update_binding_list((BUFFER *)0);
}
static char described_cmd[NLINE+1]; /* string to match cmd names to */
/* ARGSUSED */
int
desfunc(int f GCC_UNUSED, int n GCC_UNUSED) /* describe-function */
{
register int s;
char *fnp;
/* force an exact match by strinc() later on from makefuncdesc() */
described_cmd[0] = '^';
fnp = kbd_engl("Describe function whose full name is: ",
described_cmd+1);
if (fnp == NULL || engl2fnc(fnp) == NULL) {
s = no_such_function(fnp);
} else {
last_apropos_string = described_cmd;
last_whichcmds = 0;
append_to_binding_list = TRUE;
s = update_binding_list((BUFFER *)0);
append_to_binding_list = FALSE;
}
return s;
}
/* ARGSUSED */
int
deskey(int f GCC_UNUSED, int n GCC_UNUSED) /* describe the command for a certain key */
{
register int c; /* key to describe */
char outseq[NSTRING]; /* output buffer for command sequence */
const NTAB *nptr; /* name table pointer */
int s;
/* prompt the user to type us a key to describe */
mlprompt("Describe the function bound to this key sequence: ");
/* get the command sequence to describe
change it to something we can print as well */
/* check to see if we are executing a command line */
if (clexec) {
char tok[NSTRING];
macarg(tok); /* get the next token */
c = prc2kcod(tok);
if (c < 0) {
mlforce("[Illegal key-sequence \"%s\"]",tok);
return(FALSE);
}
} else {
c = kbd_seq_nomap();
if (c < 0) {
mlforce("[Not a bindable key-sequence]");
return(FALSE);
}
}
(void)kcod2prc(c, outseq);
hst_append_s(outseq, FALSE); /* cannot replay this, but can see it */
/* find the right ->function */
if ((nptr = fnc2ntab(kcod2fnc(c))) == NULL) {
mlwrite("Key sequence '%s' is not bound to anything.",
outseq);
return TRUE;
}
/* describe it */
described_cmd[0] = '^';
(void)strcpy(described_cmd + 1, nptr->n_name);
last_apropos_string = described_cmd;
last_whichcmds = 0;
append_to_binding_list = TRUE;
s = update_binding_list((BUFFER *)0);
append_to_binding_list = FALSE;
mlwrite("Key sequence '%s' is bound to function \"%s\"",
outseq, nptr->n_name);
return s;
}
/* returns a name in double-quotes */
static char *
quoted(char *dst, const char *src)
{
return strcat(strcat(strcpy(dst, "\""), src), "\"");
}
/* returns the number of columns used by the given string */
static unsigned
converted_len(register char *buffer)
{
register unsigned len = 0, c;
while ((c = *buffer++) != EOS) {
if (c == '\t')
len |= 7;
len++;
}
return len;
}
/* force the buffer to a tab-stop if needed */
static char *
to_tabstop(char *buffer)
{
register unsigned cpos = converted_len(buffer);
if (cpos & 7)
(void)strcat(buffer, "\t");
return skip_string(buffer);
}
/* convert a key binding, padding to the next multiple of 8 columns */
static void
convert_kcode(int c, char *buffer)
{
(void)kcod2prc(c, to_tabstop(buffer));
}
#if OPT_NAMEBST
struct bindlist_data {
int mask; /* oper/motion mask */
int min; /* minimum key length */
char *apropos; /* key check */
};
static int
btree_walk(BI_NODE *node, int (*func)(BI_NODE *, const void *),
const void *data)
{
if (node)
{
if (btree_walk(BI_LEFT(node), func, data))
return 1;
if (BI_KEY(node))
func(node, data);
if (btree_walk(BI_RIGHT(node), func, data))
return 1;
}
return 0;
}
static int
clearflag_func(BI_NODE *n, const void *d GCC_UNUSED)
{
n->value.n_flags &= ~NBST_DONE;
return 0;
}
static int
addsynonym_func(BI_NODE *node, const void *d)
{
const CMDFUNC *func = (const CMDFUNC *)d;
static char outseq[NLINE]; /* output buffer for text */
if (node->value.n_cmd == func &&
!(node->value.n_flags & NBST_DONE))
{
strcpy(outseq, " or\t");
quoted(outseq+5, BI_KEY(node));
if (!addline(curbp, outseq, -1))
return 1;
}
return 0;
}
static int
makebind_func(BI_NODE *node, const void *d)
{
const struct bindlist_data *data = (const struct bindlist_data *)d;
static KBIND *kbp; /* pointer into a key binding table */
static char outseq[NLINE]; /* output buffer for keystroke sequence */
const CMDFUNC *cmd = node->value.n_cmd;
register int i;
/* has this been listed? */
if (node->value.n_flags & NBST_DONE)
return 0;
/* are we interested in this type of command? */
if (data->mask && !(cmd->c_flags & data->mask))
return 0;
/* try to avoid alphabetizing by the real short names */
if (data->min && (int) strlen(BI_KEY(node)) <= data->min)
return 0;
/* if we are executing an apropos command
and current string doesn't include the search string */
if (data->apropos && !strinc(BI_KEY(node), data->apropos))
return 0;
/* add in the command name */
quoted(outseq, BI_KEY(node));
while (converted_len(outseq) < 32)
strcat(outseq, "\t");
/* look in the simple ascii binding table first */
for (i = 0; i < N_chars; i++)
if (asciitbl[i] == cmd)
convert_kcode(i, outseq);
/* then look in the multi-key table */
#if OPT_REBIND
for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link)
{
if (kbp->k_cmd == cmd)
convert_kcode(kbp->k_code, outseq);
}
#endif
for (kbp = kbindtbl; kbp->k_cmd; kbp++)
if (kbp->k_cmd == cmd)
convert_kcode(kbp->k_code, outseq);
/* dump the line */
if (!addline(curbp, outseq, -1))
return 1;
node->value.n_flags |= NBST_DONE;
/* add synonyms */
btree_walk(&namebst.head, addsynonym_func, cmd);
#if OPT_ONLINEHELP
if (cmd->c_help && *cmd->c_help)
lsprintf(outseq, " (%s %s )",
(cmd->c_flags & MOTION) ? "motion: " :
(cmd->c_flags & OPER) ? "operator: " : "",
cmd->c_help);
else
lsprintf(outseq, " ( no help for this command )");
if (!addline(curbp, outseq, -1))
return 1;
if (cmd->c_flags & GLOBOK)
if (!addline(curbp, " (may follow global command)", -1))
return 1;
#endif
/* blank separator */
if (!addline(curbp, "", -1))
return 1;
return 0;
}
/* build a binding list (limited or full) */
/* ARGSUSED */
static void
makebindlist(
int whichmask,
void *mstring) /* match string if partial list, NULL to list all */
{
struct bindlist_data data;
data.mask = whichmask;
data.min = SHORT_CMD_LEN;
data.apropos = (char *)mstring;
/* let us know this is in progress */
mlwrite("[Building binding list]");
/* clear the NBST_DONE flag */
btree_walk(&namebst.head, clearflag_func, 0);
/* create binding list */
if (btree_walk(&namebst.head, makebind_func, &data))
return;
/* catch entries with no synonym > SHORT_CMD_LEN */
data.min = 0;
if (btree_walk(&namebst.head, makebind_func, &data))
return;
mlerase(); /* clear the message line */
}
#else /* OPT_NAMEBST */
/* fully describe a function into the current buffer, given a pointer to
* its name table entry */
static int
makefuncdesc(int j, char *listed)
{
register KBIND *kbp; /* pointer into a key binding table */
int i;
const CMDFUNC *cmd = nametbl[j].n_cmd;
char outseq[NLINE]; /* output buffer for keystroke sequence */
/* add in the command name */
(void)quoted(outseq, nametbl[j].n_name);
while (converted_len(outseq) < 32)
(void)strcat(outseq, "\t");
/* look in the simple ascii binding table first */
for (i = 0; i < N_chars; i++)
if (asciitbl[i] == cmd)
convert_kcode(i, outseq);
/* then look in the multi-key table */
#if OPT_REBIND
for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
if (kbp->k_cmd == cmd)
convert_kcode(kbp->k_code, outseq);
}
#endif
for (kbp = kbindtbl; kbp->k_cmd; kbp++)
if (kbp->k_cmd == cmd)
convert_kcode(kbp->k_code, outseq);
/* dump the line */
if (!addline(curbp,outseq,-1))
return FALSE;
/* then look for synonyms */
(void)strcpy(outseq, " or\t");
for (i = 0; nametbl[i].n_name != 0; i++) {
/* if it's the one we're on, skip */
if (i == j)
continue;
/* if it's already been listed, skip */
if (listed[i])
continue;
/* if it's not a synonym, skip */
if (nametbl[i].n_cmd != cmd)
continue;
(void)quoted(outseq+5, nametbl[i].n_name);
if (!addline(curbp,outseq,-1))
return FALSE;
}
#if OPT_ONLINEHELP
if (cmd->c_help && cmd->c_help[0])
(void)lsprintf(outseq," (%s %s )",
(cmd->c_flags & MOTION) ? "motion: " :
(cmd->c_flags & OPER) ? "operator: " : "",
cmd->c_help);
else
(void)lsprintf(outseq," ( no help for this command )");
if (!addline(curbp,outseq,-1))
return FALSE;
if (cmd->c_flags & GLOBOK) {
if (!addline(curbp," (may follow global command)",-1))
return FALSE;
}
#endif
/* blank separator */
if (!addline(curbp,"",-1))
return FALSE;
return TRUE;
}
/* build a binding list (limited or full) */
/* ARGSUSED */
static void
makebindlist(
int whichmask,
void *mstring) /* match string if partial list, NULL to list all */
{
int pass;
int j;
int ok = TRUE; /* reset if out-of-memory, etc. */
char *listed = typecallocn(char, (ALLOC_T)nametblsize);
if (listed == 0) {
(void)no_memory(BINDINGLIST_BufName);
return;
}
/* let us know this is in progress */
mlwrite("[Building binding list]");
/* build the contents of this window, inserting it line by line */
for (pass = 0; pass < 2; pass++) {
for (j = 0; nametbl[j].n_name != 0; j++) {
/* if we've already described this one, move on */
if (listed[j])
continue;
/* are we interested in this type of command? */
if (whichmask && !(nametbl[j].n_cmd->c_flags & whichmask))
continue;
/* try to avoid alphabetizing by the real short names */
if (pass == 0 && (int)strlen(nametbl[j].n_name) <= SHORT_CMD_LEN)
continue;
/* if we are executing an apropos command
and current string doesn't include the search string */
if (mstring
&& (strinc(nametbl[j].n_name, (char *)mstring) == FALSE))
continue;
ok = makefuncdesc(j, listed);
if (!ok)
break;
listed[j] = TRUE; /* mark it as already listed */
}
}
if (ok)
mlerase(); /* clear the message line */
free(listed);
}
#endif /* OPT_NAMEBST */
/* much like the "standard" strstr, but if the substring starts
with a '^', we discard it and force an exact match. */
static int
strinc( /* does source include sub? */
const char *sourc, /* string to search in */
const char *sub) /* substring to look for */
{
const char *sp; /* ptr into source */
const char *nxtsp; /* next ptr into source */
const char *tp; /* ptr into substring */
int exact = (*sub == '^');
if (exact)
sub++;
/* for each character in the source string */
sp = sourc;
while (*sp) {
tp = sub;
nxtsp = sp;
/* is the substring here? */
while (*tp) {
if (*nxtsp++ != *tp)
break;
tp++;
}
if ((*tp == EOS) && (!exact || *nxtsp == EOS))
return(TRUE);
if (exact) /* we only get one chance */
break;
/* no, onward */
sp++;
}
return(FALSE);
}
#endif /* OPT_REBIND */
/* Look up the existence of a file along the normal or PATH
environment variable. Look first in the HOME directory if
asked and possible
*/
char *
flook(
char *fname, /* base file name to search for */
UINT hflag) /* Look in the HOME environment variable first? */
{
register char *home; /* path to home directory */
#if ENVFUNC && OPT_PATHLOOKUP
register const char *path; /* environmental PATH variable */
#endif
static char fspec[NSTRING]; /* full path spec to search */
#if SYS_VMS
register char *sp; /* pointer into path spec */
static TBUFF *myfiles;
#endif
int mode = (hflag & (FL_EXECABLE|FL_WRITEABLE|FL_READABLE));
/* take care of special cases */
if (!fname || !fname[0] || isSpace(fname[0]))
return NULL;
else if (isShellOrPipe(fname))
return fname;
if (hflag & FL_HERE) {
if (ffaccess(fname, mode)) {
return(fname);
}
}
#if ENVFUNC
if (hflag & FL_HOME) {
home = getenv("HOME");
if (home != NULL) {
/* try home dir file spec */
if (ffaccess(pathcat(fspec,home,fname), mode)) {
return(fspec);
}
}
}
#endif /* ENVFUNC */
if (hflag & FL_EXECDIR) { /* is it where we found the executable? */
if (exec_pathname
&& exec_pathname[0] != EOS
&& ffaccess(pathcat(fspec, exec_pathname, fname), mode))
return(fspec);
}
if (hflag & FL_TABLE) {
/* then look it up via the table method */
path = startup_path;
while ((path = parse_pathlist(path, fspec)) != 0) {
if (ffaccess(pathcat(fspec, fspec, fname), mode)) {
return(fspec);
}
}
}
if (hflag & FL_PATH) {
#if ENVFUNC
#if OPT_PATHLOOKUP
/* then look along $PATH */
#if SYS_VMS
/* On VAX/VMS, the PATH environment variable is only the
* current-dir. Fake up an acceptable alternative.
*/
if (!tb_length(myfiles)) {
char mypath[NFILEN];
(void)strcpy(mypath, prog_arg);
if ((sp = vms_pathleaf(mypath)) == mypath)
(void)strcpy(mypath, current_directory(FALSE));
else
*sp = EOS;
if (!tb_init(&myfiles, EOS)
|| !tb_sappend(&myfiles, mypath)
|| !tb_sappend(&myfiles, ",SYS$SYSTEM:,SYS$LIBRARY:")
|| !tb_append(&myfiles, EOS))
return NULL;
}
path = tb_values(myfiles);
#else /* UNIX or MSDOS */
path = getenv("PATH"); /* get the PATH variable */
#endif
while ((path = parse_pathlist(path, fspec)) != 0) {
if (ffaccess(pathcat(fspec, fspec, fname), mode)) {
return(fspec);
}
}
#endif /* OPT_PATHLOOKUP */
#endif /* ENVFUNC */
}
return NULL; /* no such luck */
}
/* translate a keycode to its binding-string */
char *
kcod2pstr(
int c, /* sequence to translate */
char *seq) /* destination string for sequence */
{
seq[0] = (char)kcod2escape_seq(c, &seq[1]);
return seq;
}
/* Translate a 16-bit keycode to a string that will replay into the same
* code.
*/
int
kcod2escape_seq (
int c,
char * ptr)
{
char *base = ptr;
/* ...just for completeness */
if (c & CTLA) *ptr++ = (char)cntl_a;
else if (c & CTLX) *ptr++ = (char)cntl_x;
else if (c & SPEC) *ptr++ = (char)poundc;
#if SYS_WINNT
#define ALTPLUS "Alt+"
#define CTRLPLUS "Ctrl+"
#define SHIFTPLUS "Shift+"
#define W32INSERT "Insert"
else if (c & W32_KEY)
{
if (c & W32_SHIFT)
{
strcpy(ptr, SHIFTPLUS);
ptr += sizeof(SHIFTPLUS) - 1;
}
if (c & W32_CTRL)
{
strcpy(ptr, CTRLPLUS);
ptr += sizeof(CTRLPLUS) - 1;
}
if (c & W32_ALT)
{
strcpy(ptr, ALTPLUS);
ptr += sizeof(ALTPLUS) - 1;
}
c &= W32_NOMOD;
if (c == VK_INSERT)
{
strcpy(ptr, W32INSERT);
ptr += sizeof(W32INSERT) - 1;
}
else
*ptr++ = c; /* Pickup <modifier>+...<single_char> */
*ptr = EOS;
return (int)(ptr - base);
}
#endif
*ptr++ = (char)c;
*ptr = EOS;
return (int)(ptr - base);
}
/* translates a binding string into printable form */
#if OPT_REBIND
static char *
bytes2prc(char *dst, char *src, int n)
{
char *base = dst;
register int c;
register const char *tmp;
for ( ; n != 0; dst++, src++, n--) {
c = *src;
tmp = 0;
if (c & HIGHBIT) {
*dst++ = 'M';
*dst++ = '-';
c &= ~HIGHBIT;
}
if (c == ' ') {
tmp = "<space>";
} else if (isCntrl(c)) {
*dst++ = '^';
*dst = tocntrl(c);
} else {
*dst = (char)c;
}
if (tmp != 0) {
while ((*dst++ = *tmp++) != EOS)
;
dst -= 2; /* point back to last nonnull */
}
if (n > 1) {
*++dst = '-';
}
}
*dst = EOS;
return base;
}
/* translate a 10-bit keycode to its printable name (like "M-j") */
static char *
kcod2prc(
int c, /* sequence to translate */
char *seq) /* destination string for sequence */
{
char temp[NSTRING];
(void)kcod2pstr(c,temp);
#if SYS_WINNT
if (c & W32_KEY)
{
/* Translation is complete, by defn. */
strcpy(seq, temp + 1);
return (seq);
}
#endif
return bytes2prc(seq, temp + 1, (int)*temp);
}
#endif
/* kcode2kbind: translate a 10-bit key-binding to the table-pointer
*/
static KBIND *
kcode2kbind(register int code)
{
register KBIND *kbp; /* pointer into a binding table */
#if OPT_REBIND
for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
if (kbp->k_code == code)
return kbp;
}
#endif
for (kbp = kbindtbl; kbp->k_cmd; kbp++) {
if (kbp->k_code == code)
return kbp;
}
return 0;
}
/* kcod2fnc: translate a 10-bit keycode to a function pointer */
/* (look a key binding up in the binding table) */
const CMDFUNC *
kcod2fnc(
int c) /* key to find what is bound to it */
{
if (isspecial(c)) {
register KBIND *kp = kcode2kbind(c);
return (kp != 0) ? kp->k_cmd : 0;
}
return asciitbl[c];
}
/* fnc2kcod: translate a function pointer to a keycode */
int
fnc2kcod(const CMDFUNC *f)
{
register KBIND *kbp;
register int c;
for (c = 0; c < N_chars; c++)
if (f == asciitbl[c])
return c;
#if OPT_REBIND
for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
if (kbp->k_cmd == f)
return kbp->k_code;
}
#endif
for (kbp = kbindtbl; kbp->k_cmd != 0; kbp++) {
if (kbp->k_cmd == f)
return kbp->k_code;
}
return -1; /* none found */
}
/* fnc2pstr: translate a function pointer to a pascal-string that a user
could enter. returns a pointer to a static array */
#if DISP_X11
char *
fnc2pstr(const CMDFUNC *f)
{
register int c;
static char seq[10];
c = fnc2kcod(f);
if (c == -1)
return NULL;
return kcod2pstr(c, seq);
}
#endif
/* fnc2engl: translate a function pointer to the english name for
that function
*/
#if OPT_EVAL || OPT_REBIND
static const NTAB *
fnc2ntab(const CMDFUNC *cfp)
{
register const NTAB *nptr; /* pointer into the name table */
register const NTAB *shortnptr = NULL; /* pointer into the name table */
/* skim through the table, looking for a match */
for (nptr = nametbl; nptr->n_cmd; nptr++) {
if (nptr->n_cmd == cfp) {
/* if it's a long name, return it */
if ((int)strlen(nptr->n_name) > SHORT_CMD_LEN)
return nptr;
/* remember the first short name, in case there's
no long name */
if (!shortnptr)
shortnptr = nptr;
}
}
if (shortnptr)
return shortnptr;
return NULL;
}
static char *
fnc2engl(const CMDFUNC *cfp)
{
register const NTAB *nptr = fnc2ntab(cfp);
return nptr ? nptr->n_name : 0;
}
#endif
/* engl2fnc: match name to a function in the names table
translate english name to function pointer
return any match or NULL if none
*/
#define BINARY_SEARCH_IS_BROKEN 0
#if OPT_NAMEBST
const CMDFUNC *
engl2fnc(const char *fname) /* name to attempt to match */
{
BI_NODE *n = btree_pmatch(BI_RIGHT(&namebst.head), TRUE, fname);
if (n == NULL) return NULL;
else return n->value.n_cmd;
}
#else
#if BINARY_SEARCH_IS_BROKEN /* then use the old linear look-up */
const CMDFUNC *
engl2fnc(const char *fname) /* name to attempt to match */
{
register NTAB *nptr; /* pointer to entry in name binding table */
register SIZE_T len = strlen(fname);
if (len != 0) { /* scan through the table, returning any match */
nptr = nametbl;
while (nptr->n_cmd != NULL) {
if (strncmp(fname, nptr->n_name, len) == 0)
return nptr->n_cmd;
++nptr;
}
}
return NULL;
}
#else
/* this runs 10 times faster for 'nametbl[]' */
const CMDFUNC *
engl2fnc(const char *fname) /* name to attempt to match */
{
int lo, hi, cur;
int r;
register SIZE_T len = strlen(fname);
if (len == 0)
return NULL;
/* scan through the table, returning any match */
lo = 0;
hi = nametblsize - 2; /* don't want last entry -- it's NULL */
while (lo <= hi) {
cur = (lo + hi) >> 1;
if ((r = strncmp(fname, nametbl[cur].n_name, len)) == 0) {
/* Now find earliest matching entry */
while (cur > lo
&& strncmp(fname, nametbl[cur-1].n_name, len) == 0)
cur--;
return nametbl[cur].n_cmd;
} else if (r > 0) {
lo = cur+1;
} else {
hi = cur-1;
}
}
return NULL;
}
#endif /* binary vs linear */
#endif /* OPT_NAMEBST */
/* prc2kcod: translate printable code to 10 bit keycode */
#if OPT_EVAL || OPT_REBIND
static int
prc2kcod(
const char *kk) /* name of key to translate to Command key form */
{
register UINT c; /* key sequence to return */
register UINT pref = 0; /* key prefixes */
register int len = strlen(kk);
register const UCHAR *k = (const UCHAR *)kk;
if (len > 3 && *(k+2) == '-') {
if (*k == '^') {
if (isCntrl(cntl_a) && *(k+1) == toalpha(cntl_a))
pref = CTLA;
if (isCntrl(cntl_x) && *(k+1) == toalpha(cntl_x))
pref = CTLX;
if (isCntrl(poundc) && *(k+1) == toalpha(poundc))
pref = SPEC;
} else if (!strncmp((const char *)k, "FN", (SIZE_T)2)) {
pref = SPEC;
}
if (pref != 0)
k += 3;
} else if (len > 2 && !strncmp((const char *)k, "M-", (SIZE_T)2)) {
pref = HIGHBIT;
k += 2;
} else if (len > 1) {
if (*k == cntl_a)
pref = CTLA;
else if (*k == cntl_x)
pref = CTLX;
else if (*k == poundc)
pref = SPEC;
if (pref != 0) {
k++;
if (len > 2 && *k == '-')
k++;
}
}
/* a control char? */
if (*k == '^' && *(k+1) != EOS) {
c = *(k+1);
if (isLower(c)) c = toUpper(c);
c = tocntrl(c);
k += 2;
} else { /* any single char, control or not */
c = *k++;
}
if (*k != EOS) /* we should have eaten the whole thing */
return -1;
return (int)(pref|c);
}
#endif
#if OPT_EVAL
/* translate printable code (like "M-r") to english command name */
const char *
prc2engl( /* string key name to binding name.... */
const char *skey) /* name of key to get binding for */
{
const char *bindname;
int c;
c = prc2kcod(skey);
if (c < 0)
return "ERROR";
bindname = fnc2engl(kcod2fnc(c));
if (bindname == NULL)
bindname = "ERROR";
return bindname;
}
#endif
/*
* Get an english command name from the user
*/
char *
kbd_engl(
const char *prompt, /* null pointer to splice calls */
char *buffer)
{
if (kbd_engl_stat(prompt, buffer, 0) == TRUE)
return buffer;
return NULL;
}
/* sound the alarm! */
void
kbd_alarm(void)
{
TRACE(("BEEP\n"))
if (global_g_val(GMDERRORBELLS)) {
TTbeep();
TTflush();
}
warnings++;
}
/* put a character to the keyboard-prompt */
void
kbd_putc(int c)
{
BUFFER *savebp;
WINDOW *savewp;
MARK savemk;
beginDisplay();
savebp = curbp;
savewp = curwp;
curbp = bminip;
curwp = wminip;
savemk = MK;
MK = DOT;
if ((kbd_expand <= 0) && isreturn(c)) {
kbd_erase_to_end(0);
} else {
if ((kbd_expand < 0) && (c == '\t')) {
(void)linsert(1,' ');
} else {
(void)linsert(1,c);
}
if (! is_header_line(DOT,curbp) && !is_at_end_of_line(DOT))
forwchar(TRUE,1); /* END OF LINE HACK */
#ifdef DEBUG
TRACE(("mini:%2d:%.*s\n", llength(DOT.l), llength(DOT.l), DOT.l->l_text));
#endif
}
curbp = savebp;
curwp = savewp;
MK = savemk;
endofDisplay();
}
/* put a string to the keyboard-prompt */
void
kbd_puts(const char *s)
{
while (*s)
kbd_putc(*s++);
}
/* erase a character from the display by wiping it out */
void
kbd_erase(void)
{
BUFFER *savebp;
WINDOW *savewp;
MARK savemk;
if (!disinp)
return;
beginDisplay();
savebp = curbp;
savewp = curwp;
curbp = bminip;
curwp = wminip;
savemk = MK;
MK = DOT;
if (DOT.o > 0) {
DOT.o -= 1;
ldelete(1, FALSE);
}
#ifdef DEBUG
TRACE(("MINI:%2d:%.*s\n", llength(DOT.l), llength(DOT.l), DOT.l->l_text));
#endif
curbp = savebp;
curwp = savewp;
MK = savemk;
endofDisplay();
}
void
kbd_erase_to_end(int column)
{
BUFFER *savebp;
WINDOW *savewp;
MARK savemk;
if (!disinp)
return;
beginDisplay();
savebp = curbp;
savewp = curwp;
curbp = bminip;
curwp = wminip;
savemk = MK;
MK = DOT;
if (llength(DOT.l) > 0) {
DOT.o = column;
ldelete(llength(DOT.l) - DOT.o, FALSE);
TRACE(("NULL:%2d:%.*s\n", llength(DOT.l), llength(DOT.l), DOT.l->l_text));
}
curbp = savebp;
curwp = savewp;
MK = savemk;
endofDisplay();
}
#if OPT_CASELESS
static int
cs_strcmp(
int case_insensitive,
const char *s1,
const char *s2)
{
if (case_insensitive)
return stricmp(s1, s2);
return strcmp(s1, s2);
}
static int
cs_strncmp(
int case_insensitive,
const char *s1,
const char *s2,
SIZE_T n)
{
if (case_insensitive)
return strnicmp(s1, s2, n);
return strncmp(s1, s2, n);
}
#endif /* OPT_CASELESS */
/* definitions for name-completion */
#define NEXT_DATA(p) ((p)+size_entry)
#define PREV_DATA(p) ((p)-size_entry)
#ifdef lint
static /*ARGSUSED*/
const char * THIS_NAME(const char *p) { return 0; }
#else
#define THIS_NAME(p) (*TYPECAST(const char *const,p))
#endif
#define NEXT_NAME(p) THIS_NAME(NEXT_DATA(p))
/*
* Scan down until we no longer match the current input, or reach the end of
* the symbol table.
*/
/*ARGSUSED*/
static const char *
skip_partial(
int case_insensitive GCC_UNUSED,
char *buf,
SIZE_T len,
const char *table,
SIZE_T size_entry)
{
register const char * next = NEXT_DATA(table);
register const char * sp;
while ((sp = THIS_NAME(next)) != 0) {
if (StrNcmp(buf, sp, len) != 0)
break;
next = NEXT_DATA(next);
}
return next;
}
/*
* Shows a partial-match. This is invoked in the symbol table at a partial
* match, and the user wants to know what characters could be typed next.
* If there is more than one possibility, they are shown in square-brackets.
* If there is only one possibility, it is shown in curly-braces.
*/
static void
show_partial(
int case_insensitive,
char *buf,
SIZE_T len,
const char *table,
SIZE_T size_entry)
{
register const char *next = skip_partial(case_insensitive, buf, len, table, size_entry);
register const char *last = PREV_DATA(next);
register int c;
if (THIS_NAME(table)[len] == THIS_NAME(last)[len]) {
kbd_putc('{');
while ((c = THIS_NAME(table)[len]) != 0) {
if (c == THIS_NAME(last)[len]) {
kbd_putc(c);
len++;
} else
break;
}
kbd_putc('}');
}
if (next != NEXT_DATA(table)) {
c = TESTC; /* shouldn't be in the table! */
kbd_putc('[');
while (table != next) {
register const char *sp = THIS_NAME(table);
if (c != sp[len]) {
c = sp[len];
kbd_putc(c ? c : '$');
}
table = NEXT_DATA(table);
}
kbd_putc(']');
}
kbd_flush();
}
#if OPT_POPUPCHOICE
/*
* makecmpllist is called from liststuff to display the possible completions.
*/
struct compl_rec {
char *buf;
SIZE_T len;
const char *table;
SIZE_T size_entry;
};
#ifdef lint
#define c2ComplRec(c) ((struct compl_rec *)0)
#else
#define c2ComplRec(c) ((struct compl_rec *)c)
#endif
/*ARGSUSED*/
static void
makecmpllist(
int case_insensitive,
void *cinfop)
{
char * buf = c2ComplRec(cinfop)->buf;
SIZE_T len = c2ComplRec(cinfop)->len;
const char * first = c2ComplRec(cinfop)->table;
SIZE_T size_entry = c2ComplRec(cinfop)->size_entry;
register const char *last = skip_partial(case_insensitive, buf, len, first, size_entry);
register const char *p;
SIZE_T maxlen;
int slashcol;
int cmpllen;
int cmplcols;
int cmplrows;
int nentries;
int i, j;
for (p = NEXT_DATA(first), maxlen = strlen(THIS_NAME(first));
p != last;
p = NEXT_DATA(p)) {
SIZE_T l = strlen(THIS_NAME(p));
if (l > maxlen)
maxlen = l;
}
slashcol = (int)(pathleaf(buf) - buf);
if (slashcol != 0) {
char b[NLINE];
(void)strncpy(b, buf, (SIZE_T)slashcol);
(void)strncpy(&b[slashcol], &(THIS_NAME(first))[slashcol],
(len-slashcol));
b[slashcol+(len-slashcol)] = EOS;
bprintf("Completions prefixed by %s:\n", b);
}
cmplcols = term.t_ncol / (maxlen - slashcol + 1);
if (cmplcols == 0)
cmplcols = 1;
nentries = (int)(last - first) / size_entry;
cmplrows = nentries / cmplcols;
cmpllen = term.t_ncol / cmplcols;
if (cmplrows * cmplcols < nentries)
cmplrows++;
for (i = 0; i < cmplrows; i++) {
for (j = 0; j < cmplcols; j++) {
int idx = cmplrows * j + i;
if (idx < nentries) {
const char *s = THIS_NAME(first+(idx*size_entry))+slashcol;
if (j == cmplcols-1)
bprintf("%s\n", s);
else
bprintf("%*s", cmpllen, s);
}
else {
bprintf("\n");
break;
}
}
}
}
/*
* Pop up a window and show the possible completions.
*/
static void
show_completions(
int case_insensitive,
char *buf,
SIZE_T len,
const char *table,
SIZE_T size_entry)
{
struct compl_rec cinfo;
BUFFER *bp;
int alreadypopped = 0;
/*
* Find out if completions buffer exists; so we can take the time to
* shrink/grow the window to the latest size.
*/
if ((bp = find_b_name(COMPLETIONS_BufName)) != NULL) {
alreadypopped = (bp->b_nwnd != 0);
}
cinfo.buf = buf;
cinfo.len = len;
cinfo.table = table;
cinfo.size_entry = size_entry;
liststuff(COMPLETIONS_BufName, FALSE, makecmpllist, case_insensitive, (void *) &cinfo);
if (alreadypopped)
shrinkwrap();
(void)update(TRUE);
}
/*
* Scroll the completions window wrapping around back to the beginning
* of the buffer once it has been completely scrolled. If the completions
* buffer is missing for some reason, we will call show_completions to pop
* it (back) up.
*/
static void
scroll_completions(
int case_insensitive,
char *buf,
SIZE_T len,
const char *table,
SIZE_T size_entry)
{
BUFFER *bp = find_b_name(COMPLETIONS_BufName);
if (bp == NULL)
show_completions(case_insensitive, buf, len, table, size_entry);
else {
LINEPTR lp;
swbuffer(bp);
(void)gotoeos(FALSE, 1);
lp = DOT.l;
(void)forwhpage(FALSE, 1);
if (lp == DOT.l)
(void)gotobob(FALSE, 0);
(void)update(TRUE);
}
}
void
popdown_completions(void)
{
BUFFER *bp;
if ((bp = find_b_name(COMPLETIONS_BufName)) != NULL)
zotwp(bp);
}
#endif /* OPT_POPUPCHOICE */
/*
* Attempt to partial-complete the string, char at a time
*/
static SIZE_T
fill_partial(
int case_insensitive GCC_UNUSED,
char *buf,
SIZE_T pos,
const char *first,
const char *last,
SIZE_T size_entry)
{
register const char *p;
register SIZE_T n = pos;
const char *this_name = THIS_NAME(first);
#if 0 /* case insensitive reply correction doesn't work reliably yet */
if (!clexec && case_insensitive) {
int spos = pos;
while (spos > 0 && buf[spos - 1] != SLASHC) {
kbd_erase();
spos--;
}
while (spos < pos) {
kbd_putc(this_name[spos]);
spos++;
}
}
#endif
for_ever {
buf[n] = this_name[n]; /* add the next char in */
buf[n+1] = EOS;
/* scan through the candidates */
for (p = NEXT_DATA(first); p != last; p = NEXT_DATA(p)) {
if (StrNcmp(&THIS_NAME(p)[n], &buf[n], 1) != 0) {
buf[n] = EOS;
if (n == pos
#if OPT_POPUPCHOICE
# if OPT_ENUM_MODES
&& !global_g_val(GVAL_POPUP_CHOICES)
# else
&& !global_g_val(GMDPOPUP_CHOICES)
# endif
#endif
)
kbd_alarm();
kbd_flush(); /* force out alarm or partial completion */
return n;
}
}
if (!clexec)
kbd_putc(buf[n]); /* add the character */
n++;
}
}
static int testcol; /* records the column when TESTC is decoded */
#if OPT_POPUPCHOICE
/*
* cmplcol is used to record the column number (on the message line) after
* name completion. Its value is used to decide whether or not to display
* a completion list if the name completion character (tab) is pressed
* twice in succession. Once the completion list has been displayed, its
* value will be changed to the additive inverse of the column number in
* order to determine whether to scroll if tab is pressed yet again. We
* assume that 0 will never be a valid column number. So long as we always
* display some sort of prompt prior to reading from the message line, this
* is a good assumption.
*/
static int cmplcol = 0;
#endif
/*
* Initializes the name-completion logic
*/
void
kbd_init(void)
{
testcol = -1;
}
/*
* Returns the current length of the minibuffer
*/
int
kbd_length(void)
{
if (wminip != 0
&& wminip->w_dot.l != 0
&& llength(wminip->w_dot.l) > 0)
return llength(wminip->w_dot.l);
return 0;
}
/*
* Erases the display that was shown in response to TESTC
*/
void
kbd_unquery(void)
{
beginDisplay();
#if OPT_POPUPCHOICE
if (cmplcol != kbd_length() && -cmplcol != kbd_length())
cmplcol = 0;
#endif
if (testcol >= 0) {
while (kbd_length() > testcol)
kbd_erase();
kbd_flush();
testcol = -1;
}
endofDisplay();
}
/*
* This is invoked to find the closest name to complete from the current buffer
* contents.
*/
int
kbd_complete(
int case_insensitive,
int c, /* TESTC, NAMEC or isreturn() */
char *buf,
unsigned *pos,
const char *table,
SIZE_T size_entry)
{
register SIZE_T cpos = *pos;
register const char *nbp; /* first ptr to entry in name binding table */
int status = FALSE;
#if OPT_POPUPCHOICE
# if OPT_ENUM_MODES
int gvalpopup_choices = global_g_val(GVAL_POPUP_CHOICES);
# else
int gvalpopup_choices = global_g_val(GMDPOPUP_CHOICES);
# endif
#endif
kbd_init(); /* nothing to erase */
buf[cpos] = EOS; /* terminate it for us */
nbp = table; /* scan for matches */
while (THIS_NAME(nbp) != NULL) {
if (StrNcmp(buf, THIS_NAME(nbp), strlen(buf)) == 0) {
testcol = kbd_length();
/* a possible match! exact? no more than one? */
#if OPT_POPUPCHOICE
if (!clexec && c == NAMEC && cmplcol == -kbd_length()) {
scroll_completions(case_insensitive, buf, cpos, nbp, size_entry);
return FALSE;
}
#endif
if (c == TESTC) {
show_partial(case_insensitive, buf, cpos, nbp, size_entry);
}
else if (Strcmp(buf, THIS_NAME(nbp)) == 0 || /* exact? */
NEXT_NAME(nbp) == NULL ||
StrNcmp(buf, NEXT_NAME(nbp), strlen(buf)) != 0)
{
/* exact or only one like it. print it */
if (!clexec) {
#if 0 /* case insensitive reply correction doesn't work reliably yet */
if (case_insensitive) {
int spos = cpos;
while (spos > 0 && buf[spos - 1] != SLASHC) {
kbd_erase();
spos--;
}
kbd_puts(THIS_NAME(nbp) + spos);
}
else
#endif
kbd_puts(THIS_NAME(nbp) + cpos);
kbd_flush();
testcol = kbd_length();
}
if (c != NAMEC) /* put it back */
unkeystroke(c);
/* return complete name */
(void)strncpy0(buf, THIS_NAME(nbp),
(SIZE_T)(NLINE - 1));
*pos = strlen(buf);
#if OPT_POPUPCHOICE
if (gvalpopup_choices != POPUP_CHOICES_OFF
&& !clexec && (c == NAMEC))
status = FALSE;
else
#endif
status = TRUE;
}
else {
/* try for a partial match against the list */
*pos = fill_partial(case_insensitive, buf, cpos, nbp,
skip_partial(case_insensitive, buf, cpos, nbp, size_entry),
size_entry);
testcol = kbd_length();
}
#if OPT_POPUPCHOICE
# if OPT_ENUM_MODES
if (!clexec
&& gvalpopup_choices != POPUP_CHOICES_OFF
&& c == NAMEC
&& *pos == cpos) {
if (gvalpopup_choices == POPUP_CHOICES_IMMED
|| cmplcol == kbd_length()) {
show_completions(case_insensitive, buf, cpos, nbp, size_entry);
cmplcol = -kbd_length();
}
else
cmplcol = kbd_length();
}
else
cmplcol = 0;
# else
if (!clexec && gvalpopup_choices
&& c == NAMEC && *pos == cpos) {
show_completions(case_insensitive, buf, cpos, nbp, size_entry);
cmplcol = -kbd_length();
}
else
cmplcol = 0;
# endif
#endif
return status;
}
nbp = NEXT_DATA(nbp);
}
#if OPT_POPUPCHOICE
cmplcol = 0;
#endif
kbd_alarm(); /* no match */
buf[*pos = cpos] = EOS;
return FALSE;
}
/*
* Test a buffer to see if it looks like a shift-command, which may have
* repeated characters (but they must all be the same).
*/
static int
is_shift_cmd(
const char *buffer,
unsigned cpos)
{
register int c = *buffer;
if (isRepeatable(c)) {
while (--cpos != 0)
if (*(++buffer) != c)
return FALSE;
return TRUE;
}
return FALSE;
}
/*
* The following mess causes the command to terminate if:
*
* we've got the eolchar
* -or-
* we're in the first few chars and we're switching from punctuation
* (i.e., delimiters) to non-punctuation (i.e., characters that are part
* of command-names), or vice-versa. oh yeah -- '-' isn't punctuation
* today, and '!' isn't either, in one direction, at any rate.
* All this allows things like:
* : e#
* : e!%
* : !ls
* : q!
* : up-line
* to work properly.
*
* If we pass this "if" with c != NAMEC, then c is ungotten below,
* so it can be picked up by the commands argument getter later.
*/
#define ismostpunct(c) (isPunct(c) && (c) != '-')
static int
eol_command(
const char * buffer,
unsigned cpos,
int c,
int eolchar)
{
/*
* Handle special case of repeated-character implying repeat-count
*/
if (is_shift_cmd(buffer, cpos) && (c == *buffer))
return TRUE;
/*
* Shell-commands aren't complete until the line is complete.
*/
if ((cpos != 0) && isShellOrPipe(buffer))
return isreturn(c);
return (c == eolchar)
|| (
cpos != 0 && cpos < 3
&&(
(!ismostpunct(c)
&& ismostpunct(buffer[cpos-1])
)
|| ((c != '!' && ismostpunct(c))
&& (buffer[cpos-1] == '!' || !ismostpunct(buffer[cpos-1]))
)
)
);
}
/*
* This procedure is invoked from 'kbd_string()' to setup the command-name
* completion and query displays.
*/
static int
cmd_complete(
int c,
char *buf,
unsigned *pos)
{
register int status;
#if OPT_HISTORY
/*
* If the user scrolled back in 'edithistory()', the text may be a
* repeated-shift command, which won't match the command-table (e.g.,
* ">>>").
*/
if ((*pos > 1) && is_shift_cmd(buf, *pos)) {
unsigned len = 1;
char tmp[NLINE];
tmp[0] = *buf;
tmp[1] = EOS;
status = cmd_complete(c, tmp, &len);
} else
#endif
if ((*pos != 0) && isShellOrPipe(buf)) {
#if COMPLETE_FILES
status = shell_complete(c, buf, pos);
#else
status = isreturn(c);
if (c != NAMEC)
unkeystroke(c);
#endif
} else {
status = kbd_complete_bst(FALSE, c, buf, pos);
}
return status;
}
int
kbd_engl_stat(const char *prompt, char *buffer, int stated)
{
int kbd_flags = KBD_EXPCMD|KBD_NULLOK|((NAMEC != ' ') ? 0 : KBD_MAYBEC);
int code;
static TBUFF *temp;
ALLOC_T len = NLINE;
tb_scopy(&temp, "");
#if COMPLETE_FILES
init_filec(FILECOMPLETION_BufName);
#endif
kbd_flags |= stated;
code = kbd_reply(
prompt, /* no-prompt => splice */
&temp, /* in/out buffer */
eol_command,
' ', /* eolchar */
kbd_flags, /* allow blank-return */
cmd_complete);
if (len > tb_length(temp))
len = tb_length(temp);
strncpy0(buffer, tb_values(temp), len);
return code;
}
#if OPT_NAMEBST
int
insert_namebst(const char *name, const CMDFUNC *cmd, int ro)
{
BI_DATA temp, *p;
if ((p = btree_search(&namebst, name)) != 0)
{
if ((p->n_flags & NBST_READONLY) && !ro)
{
mlforce("[Cannot redefine %s]", name);
return FALSE;
}
if (!delete_namebst(name, TRUE))
return FALSE;
}
temp.bi_key = name;
temp.n_cmd = cmd;
temp.n_flags = ro ? NBST_READONLY : 0;
return (btree_insert(&namebst, &temp) != 0);
}
/*
* Lookup a name in the binary-search tree, remove it if found
*/
int
delete_namebst(const char *name, int release)
{
BI_DATA *p = btree_search(&namebst, name);
/* not a named procedure */
if (!p)
return TRUE;
if (p->n_flags & NBST_READONLY)
{
mlforce("BUG: btree entry %s is readonly", name);
return FALSE;
}
/* we may have to free some stuff */
if (p && release)
{
int i;
int redo;
/* remove ascii bindings */
for (i = 0; i < N_chars; i++)
if (asciitbl[i] == p->n_cmd)
asciitbl[i] = 0;
/* then look in the multi-key table */
#if OPT_REBIND
do {
register KBIND *kbp;
redo = FALSE;
for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link)
if (kbp->k_cmd == p->n_cmd)
{
unbindchar(kbp->k_code);
redo = TRUE;
break;
}
} while (redo);
#endif
do {
register KBIND *kbp;
redo = FALSE;
for (kbp = kbindtbl; kbp->k_cmd; kbp++)
if (kbp->k_cmd == p->n_cmd)
{
unbindchar(kbp->k_code);
redo = TRUE;
break;
}
} while (redo);
/* free stuff */
#if OPT_PERL
if (p->n_cmd->c_flags & CMD_PERL)
perl_free_sub(CMD_U_PERL(p->n_cmd));
#endif
free(TYPECAST(char,p->n_cmd->c_help));
free(TYPECAST(char,p->n_cmd));
}
return btree_delete(&namebst, name);
}
/*
* If we're renaming a procedure to another "procedure" name (i.e., bracketed),
* rename it in the name-completion table. Otherwise, simply remove it from the
* name-completions.
*/
int
rename_namebst(const char *oldname, const char *newname)
{
BI_DATA *prior;
char name[NBUFN];
/* not a named procedure */
if (!(prior = btree_search(&namebst, oldname)))
return TRUE;
/* remove the entry if the new name is not a procedure (bracketed) */
if (!is_scratchname(newname))
return delete_namebst(oldname, TRUE);
/* add the new name */
strip_brackets(name, newname);
if ((insert_namebst(name, prior->n_cmd,
prior->n_flags & NBST_READONLY)) != TRUE)
return FALSE;
/* delete the old (but don't free the data) */
return delete_namebst(oldname, FALSE);
}
int
search_namebst(const char *name)
{
return (btree_search(&namebst, name) != 0);
}
/*
* Build the initial name binary search tree. Since the nametbl is sorted we
* do this in a binary-search manner to get a balanced tree.
*/
void
build_namebst(const NTAB *nptr, int lo, int hi)
{
for (; lo < hi; lo++)
if (!insert_namebst(nptr[lo].n_name, nptr[lo].n_cmd, TRUE))
tidy_exit(BADEXIT);
}
/*
* This is invoked to find the closest name to complete from the current buffer
* contents.
*/
static int
kbd_complete_bst(
int case_insensitive GCC_UNUSED,
int c, /* TESTC, NAMEC or isreturn() */
char *buf,
unsigned *pos)
{
register unsigned cpos = *pos;
int status = FALSE;
const char **nptr;
kbd_init(); /* nothing to erase */
buf[cpos] = EOS; /* terminate it for us */
if ((nptr = btree_parray(&namebst, buf, cpos)) != 0) {
status = kbd_complete(FALSE, c, buf, pos, (char *)nptr, sizeof(*nptr));
free((char *)nptr);
} else
kbd_alarm();
return status;
}
#endif /* OPT_NAMEBST */
#if OPT_MENUS
/* FIXME: reuse logic from makefuncdesc() */
char *give_accelerator ( char *bname )
{
size_t i, n;
register KBIND *kbp;
const CMDFUNC *cmd;
static char outseq[NLINE];
for (n=0; nametbl[n].n_name != 0; n++)
{
if (!strcmp(nametbl[n].n_name, bname))
{
cmd = nametbl[n].n_cmd;
outseq[0] = '\0';
for (i = 0; i < N_chars; i++)
{
if (asciitbl[i] == cmd)
convert_kcode(i, outseq);
}
for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
if (kbp->k_cmd == cmd)
convert_kcode(kbp->k_code, outseq);
}
#if 0
for (kbp = kbindtbl; kbp->k_cmd; kbp++)
if (kbp->k_cmd == cmd)
convert_kcode(kbp->k_code, outseq);
#endif
/* Replace \t by ' ' */
for (i=0; i<strlen(outseq); i++)
{
if (outseq[i] == '\t')
outseq[i] = ' ';
}
return outseq;
}
}
return NULL;
}
#endif /* OPT_MENUS */
#if NO_LEAKS
void
bind_leaks(void)
{
#if OPT_REBIND
while (KeyBindings != kbindtbl) {
KBIND *kbp = KeyBindings;
KeyBindings = kbp->k_link;
free((char *)kbp);
}
#endif
#if OPT_NAMEBST
btree_freeup(&namebst);
#endif
}
#endif /* NO_LEAKS */