This is bind.c in view mode; [Download] [Up]
/* 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: /home/tom/src/vile/RCS/bind.c,v 1.147 1997/03/01 01:33:42 tom Exp $ * */ #include "estruct.h" #include "edef.h" #include "nefunc.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 (char *sourc, char *sub); static int unbindchar ( int c ); static int update_binding_list ( BUFFER *bp ); static void makebindlist (LIST_ARGS); #endif /* OPT_REBIND */ #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 /*----------------------------------------------------------------------------*/ 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, int n) { register BUFFER *bp; /* buffer pointer to help */ const 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'}, {"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, void *ptr) { 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(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, int *pos) { return kbd_complete(FALSE, c, buf, pos, (const char *)&TermChrs[0], sizeof(TermChrs[0])); } static int /*ARGSUSED*/ chr_eol(char * buffer, int cpos, int c, int eolchar) { return isspace(c); } #if OPT_UPBUFF /* ARGSUSED */ static int update_termchrs(BUFFER *bp) { return show_termchrs(FALSE,1); } #endif /* ARGSUSED */ int set_termchrs(int f, int n) { register int s, j; char name[NLINE]; int c; /* get the table-entry */ *name = EOS; if ((s = kbd_reply("Terminal setting: ", name, sizeof(name), chr_eol, ' ', 0, chr_complete)) == TRUE) { j = chr_lookup(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, int n) { 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, int n) { 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(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 = { 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] = (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, int n) { 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)) { asciitbl[c] = NULL; } else { /* search the table to see if the key exists */ if ((kbp = kcode2kbind(c)) == 0) return(FALSE); /* save the pointer and scan to the end of the table */ skbp = kbp; while (kbp->k_cmd != NULL) ++kbp; --kbp; /* backup to the last legit entry */ /* 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 = NULL; } 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 append_to_binding_list; /* ARGSUSED */ static int update_binding_list(BUFFER *bp) { return liststuff(BINDINGLIST_BufName, append_to_binding_list, makebindlist, (int)last_whichcmds, (void *)last_apropos_string); } /* ARGSUSED */ int desbind(int f, int n) { last_apropos_string = (char *)0; last_whichcmds = 0; return update_binding_list((BUFFER *)0); } /* ARGSUSED */ int desmotions(int f, int n) { last_apropos_string = (char *)0; last_whichcmds = MOTION; return update_binding_list((BUFFER *)0); } /* ARGSUSED */ int desopers(int f, int n) { last_apropos_string = (char *)0; last_whichcmds = OPER; return update_binding_list((BUFFER *)0); } /* ARGSUSED */ int desapro(int f, int n) /* 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, int n) /* 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) { return no_such_function(fnp); } 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, int n) /* 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(); if (c < 0) { mlforce("[Not a bindable key-sequence]"); return(FALSE); } } (void)kcod2prc(c, outseq); hst_append(outseq, EOS); /* 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, char *src) { return strcat(strcat(strcpy(dst, "\""), src), "\""); } /* returns the number of columns used by the given string */ static int converted_len(register char *buffer) { register int 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 int cpos = converted_len(buffer); if (cpos & 7) (void)strcat(buffer, "\t"); return strend(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)); } /* 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 = calloc(sizeof(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); } /* 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? */ char *sourc, /* string to search in */ char *sub) /* substring to look for */ { char *sp; /* ptr into source */ char *nxtsp; /* next ptr into source */ 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 */ /* execute the startup file */ int startup( const char *sfname) /* name of startup file */ { const char *fname; /* resulting file name to execute */ /* look up the startup file */ fname = flook(sfname, (FL_HERE|FL_HOME)|FL_READABLE); /* if it isn't around, don't sweat it */ if (fname == NULL) { mlforce("[Can't find startup file %s]",sfname); return(TRUE); } /* otherwise, execute the sucker */ return(dofile(fname)); } /* Look up the existence of a file along the normal or PATH environment variable. Look first in the HOME directory if asked and possible */ const char * flook( const char *fname, /* base file name to search for */ int 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; *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); return bytes2prc(seq, temp + 1, *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]; } #if !SMALLER /* 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 */ } #endif /* 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 : NULL; } #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 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 */ /* 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) == TRUE) return buffer; return NULL; } /* sound the alarm! */ void kbd_alarm(void) { if (global_g_val(GMDERRORBELLS)) { TTbeep(); TTflush(); } warnings++; } /* put a character to the keyboard-prompt, updating 'ttcol' */ void kbd_putc(int c) { beginDisplay; if ((kbd_expand <= 0) && isreturn(c)) { TTputc(c); ttcol = 0; } else if (isprint(c)) { if (ttcol < term.t_ncol-1) /* -1 to avoid auto-wrap problems */ TTputc(c); ttcol++; } else if ((kbd_expand < 0) && (c == '\t')) { kbd_putc(' '); } else { if (c & HIGHBIT) { kbd_putc('\\'); if (global_w_val(WMDNONPRINTOCTAL)) { kbd_putc(((c>>6)&3)+'0'); kbd_putc(((c>>3)&7)+'0'); kbd_putc(((c )&7)+'0'); } else { kbd_putc('x'); kbd_putc(hexdigits[(c>>4) & 0xf]); kbd_putc(hexdigits[(c ) & 0xf]); } } else { kbd_putc('^'); kbd_putc(toalpha(c)); } } 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) { beginDisplay; if (ttcol > 0) { if (--ttcol < term.t_ncol-1) { TTputc('\b'); TTputc(' '); TTputc('\b'); } } else ttcol = 0; 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) (*(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, 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(']'); } TTflush(); } #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 int fill_partial( int case_insensitive, char *buf, SIZE_T pos, const char *first, const char *last, SIZE_T size_entry) { register const char *p; register int 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(); TTflush(); /* 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; } /* * Erases the display that was shown in response to TESTC */ void kbd_unquery(void) { beginDisplay; #if OPT_POPUPCHOICE if (cmplcol != ttcol && -cmplcol != ttcol) cmplcol = 0; #endif if (testcol >= 0) { while (ttcol > testcol) kbd_erase(); TTflush(); 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, int *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 = ttcol; /* a possible match! exact? no more than one? */ #if OPT_POPUPCHOICE if (!clexec && c == NAMEC && cmplcol == -ttcol) { 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); TTflush(); testcol = ttcol; } 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 = ttcol; } #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 == ttcol) { show_completions(case_insensitive, buf, cpos, nbp, size_entry); cmplcol = -ttcol; } else cmplcol = ttcol; } else cmplcol = 0; # else if (!clexec && gvalpopup_choices && c == NAMEC && *pos == cpos) { show_completions(case_insensitive, buf, cpos, nbp, size_entry); cmplcol = -ttcol; } 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( char *buffer, int 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( char * buffer, int 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, int *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)) { int 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(FALSE, c, buf, pos, (const char *)&nametbl[0], sizeof(nametbl[0])); } return status; } int kbd_engl_stat( const char *prompt, char *buffer) { int kbd_flags = KBD_EXPCMD|KBD_NULLOK|((NAMEC != ' ') ? 0 : KBD_MAYBEC); *buffer = EOS; #if COMPLETE_FILES init_filec(FILECOMPLETION_BufName); #endif return kbd_reply( prompt, /* no-prompt => splice */ buffer, /* in/out buffer */ NLINE, /* sizeof(buffer) */ eol_command, ' ', /* eolchar */ kbd_flags, /* allow blank-return */ cmd_complete); } #if NO_LEAKS void bind_leaks(void) { #if OPT_REBIND while (KeyBindings != kbindtbl) { KBIND *kbp = KeyBindings; KeyBindings = kbp->k_link; free((char *)kbp); } #endif } #endif /* NO_LEAKS */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.