159243Sobrien/* 259243Sobrien * tc.bind.c: Key binding functions 359243Sobrien */ 459243Sobrien/*- 559243Sobrien * Copyright (c) 1980, 1991 The Regents of the University of California. 659243Sobrien * All rights reserved. 759243Sobrien * 859243Sobrien * Redistribution and use in source and binary forms, with or without 959243Sobrien * modification, are permitted provided that the following conditions 1059243Sobrien * are met: 1159243Sobrien * 1. Redistributions of source code must retain the above copyright 1259243Sobrien * notice, this list of conditions and the following disclaimer. 1359243Sobrien * 2. Redistributions in binary form must reproduce the above copyright 1459243Sobrien * notice, this list of conditions and the following disclaimer in the 1559243Sobrien * documentation and/or other materials provided with the distribution. 16100616Smp * 3. Neither the name of the University nor the names of its contributors 1759243Sobrien * may be used to endorse or promote products derived from this software 1859243Sobrien * without specific prior written permission. 1959243Sobrien * 2059243Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 2159243Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2259243Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2359243Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 2459243Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2559243Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2659243Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2759243Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2859243Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2959243Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3059243Sobrien * SUCH DAMAGE. 3159243Sobrien */ 3259243Sobrien#include "sh.h" 3359243Sobrien#include "ed.h" 3459243Sobrien#include "ed.defns.h" 3559243Sobrien 36167465Smpstatic void printkey (const KEYCMD *, CStr *); 37167465Smpstatic KEYCMD parsecmd (Char *); 38167465Smpstatic void bad_spec (const Char *); 39167465Smpstatic CStr *parsestring (const Char *, CStr *); 40167465Smpstatic CStr *parsebind (const Char *, CStr *); 41167465Smpstatic void print_all_keys (void); 42167465Smpstatic void printkeys (KEYCMD *, int, int); 43167465Smpstatic void bindkey_usage (void); 44167465Smpstatic void list_functions (void); 4559243Sobrien 4659243Sobrienextern int MapsAreInited; 4759243Sobrien 4859243Sobrien 4959243Sobrien 5059243Sobrien 5159243Sobrien/*ARGSUSED*/ 5259243Sobrienvoid 53167465Smpdobindkey(Char **v, struct command *c) 5459243Sobrien{ 5559243Sobrien KEYCMD *map; 56145479Smp int ntype, no, removeb, key, bindk; 5759243Sobrien Char *par; 5859243Sobrien Char p; 5959243Sobrien KEYCMD cmd; 6059243Sobrien CStr in; 6159243Sobrien CStr out; 6259243Sobrien uChar ch; 6359243Sobrien 6459243Sobrien USE(c); 6559243Sobrien if (!MapsAreInited) 6659243Sobrien ed_InitMaps(); 6759243Sobrien 6859243Sobrien map = CcKeyMap; 6959243Sobrien ntype = XK_CMD; 70145479Smp key = removeb = bindk = 0; 7159243Sobrien for (no = 1, par = v[no]; 7259243Sobrien par != NULL && (*par++ & CHAR) == '-'; no++, par = v[no]) { 7359243Sobrien if ((p = (*par & CHAR)) == '-') { 7459243Sobrien no++; 7559243Sobrien break; 7659243Sobrien } 7759243Sobrien else 7859243Sobrien switch (p) { 7959243Sobrien case 'b': 80145479Smp bindk = 1; 8159243Sobrien break; 8259243Sobrien case 'k': 8359243Sobrien key = 1; 8459243Sobrien break; 8559243Sobrien case 'a': 8659243Sobrien map = CcAltMap; 8759243Sobrien break; 8859243Sobrien case 's': 8959243Sobrien ntype = XK_STR; 9059243Sobrien break; 9159243Sobrien case 'c': 9259243Sobrien ntype = XK_EXE; 9359243Sobrien break; 9459243Sobrien case 'r': 95145479Smp removeb = 1; 9659243Sobrien break; 9759243Sobrien case 'v': 9859243Sobrien ed_InitVIMaps(); 9959243Sobrien return; 10059243Sobrien case 'e': 10159243Sobrien ed_InitEmacsMaps(); 10259243Sobrien return; 10359243Sobrien case 'd': 10459243Sobrien#ifdef VIDEFAULT 10559243Sobrien ed_InitVIMaps(); 10659243Sobrien#else /* EMACSDEFAULT */ 10759243Sobrien ed_InitEmacsMaps(); 10859243Sobrien#endif /* VIDEFAULT */ 10959243Sobrien return; 11059243Sobrien case 'l': 11159243Sobrien list_functions(); 11259243Sobrien return; 11359243Sobrien default: 11459243Sobrien bindkey_usage(); 11559243Sobrien return; 11659243Sobrien } 11759243Sobrien } 11859243Sobrien 11959243Sobrien if (!v[no]) { 12059243Sobrien print_all_keys(); 12159243Sobrien return; 12259243Sobrien } 12359243Sobrien 12459243Sobrien if (key) { 12559243Sobrien if (!IsArrowKey(v[no])) 12659243Sobrien xprintf(CGETS(20, 1, "Invalid key name `%S'\n"), v[no]); 127167465Smp in.buf = Strsave(v[no++]); 12859243Sobrien in.len = Strlen(in.buf); 12959243Sobrien } 13059243Sobrien else { 131145479Smp if (bindk) { 13259243Sobrien if (parsebind(v[no++], &in) == NULL) 13359243Sobrien return; 13459243Sobrien } 13559243Sobrien else { 13659243Sobrien if (parsestring(v[no++], &in) == NULL) 13759243Sobrien return; 13859243Sobrien } 13959243Sobrien } 140167465Smp cleanup_push(in.buf, xfree); 14159243Sobrien 142145479Smp#ifndef WINNT_NATIVE 143145479Smp if (in.buf[0] > 0xFF) { 144145479Smp bad_spec(in.buf); 145167465Smp cleanup_until(in.buf); 146145479Smp return; 147145479Smp } 148145479Smp#endif 14959243Sobrien ch = (uChar) in.buf[0]; 15059243Sobrien 151145479Smp if (removeb) { 152167465Smp if (key) 15359243Sobrien (void) ClearArrowKeys(&in); 154167465Smp else if (in.len > 1) { 15559243Sobrien (void) DeleteXkey(&in); 15659243Sobrien } 15759243Sobrien else if (map[ch] == F_XKEY) { 15859243Sobrien (void) DeleteXkey(&in); 15959243Sobrien map[ch] = F_UNASSIGNED; 16059243Sobrien } 16159243Sobrien else { 16259243Sobrien map[ch] = F_UNASSIGNED; 16359243Sobrien } 164167465Smp cleanup_until(in.buf); 16559243Sobrien return; 16659243Sobrien } 16759243Sobrien if (!v[no]) { 16859243Sobrien if (key) 16959243Sobrien PrintArrowKeys(&in); 17059243Sobrien else 17159243Sobrien printkey(map, &in); 172167465Smp cleanup_until(in.buf); 17359243Sobrien return; 17459243Sobrien } 17559243Sobrien if (v[no + 1]) { 17659243Sobrien bindkey_usage(); 177167465Smp cleanup_until(in.buf); 17859243Sobrien return; 17959243Sobrien } 18059243Sobrien switch (ntype) { 18159243Sobrien case XK_STR: 18259243Sobrien case XK_EXE: 183167465Smp if (parsestring(v[no], &out) == NULL) { 184167465Smp cleanup_until(in.buf); 18559243Sobrien return; 186167465Smp } 187167465Smp cleanup_push(out.buf, xfree); 18859243Sobrien if (key) { 18959243Sobrien if (SetArrowKeys(&in, XmapStr(&out), ntype) == -1) 190145479Smp xprintf(CGETS(20, 2, "Bad key name: %S\n"), in.buf); 191167465Smp else 192167465Smp cleanup_ignore(out.buf); 19359243Sobrien } 19459243Sobrien else 19559243Sobrien AddXkey(&in, XmapStr(&out), ntype); 19659243Sobrien map[ch] = F_XKEY; 19759243Sobrien break; 19859243Sobrien case XK_CMD: 199167465Smp if ((cmd = parsecmd(v[no])) == 0) { 200167465Smp cleanup_until(in.buf); 20159243Sobrien return; 202167465Smp } 20359243Sobrien if (key) 20459243Sobrien (void) SetArrowKeys(&in, XmapCmd((int) cmd), ntype); 20559243Sobrien else { 20659243Sobrien if (in.len > 1) { 20759243Sobrien AddXkey(&in, XmapCmd((int) cmd), ntype); 20859243Sobrien map[ch] = F_XKEY; 20959243Sobrien } 21059243Sobrien else { 21159243Sobrien ClearXkey(map, &in); 21259243Sobrien map[ch] = cmd; 21359243Sobrien } 21459243Sobrien } 21559243Sobrien break; 21659243Sobrien default: 21759243Sobrien abort(); 21859243Sobrien break; 21959243Sobrien } 220167465Smp cleanup_until(in.buf); 22159243Sobrien if (key) 22259243Sobrien BindArrowKeys(); 22359243Sobrien} 22459243Sobrien 22559243Sobrienstatic void 226167465Smpprintkey(const KEYCMD *map, CStr *in) 22759243Sobrien{ 228145479Smp struct KeyFuncs *fp; 22959243Sobrien 23059243Sobrien if (in->len < 2) { 231167465Smp unsigned char *unparsed; 232167465Smp 233167465Smp unparsed = unparsestring(in, STRQQ); 234167465Smp cleanup_push(unparsed, xfree); 23559243Sobrien for (fp = FuncNames; fp->name; fp++) { 23659243Sobrien if (fp->func == map[(uChar) *(in->buf)]) { 237167465Smp xprintf("%s\t->\t%s\n", unparsed, fp->name); 23859243Sobrien } 23959243Sobrien } 240167465Smp cleanup_until(unparsed); 24159243Sobrien } 242167465Smp else 24359243Sobrien PrintXkey(in); 24459243Sobrien} 24559243Sobrien 24659243Sobrienstatic KEYCMD 247167465Smpparsecmd(Char *str) 24859243Sobrien{ 249145479Smp struct KeyFuncs *fp; 25059243Sobrien 25159243Sobrien for (fp = FuncNames; fp->name; fp++) { 25259243Sobrien if (strcmp(short2str(str), fp->name) == 0) { 25359243Sobrien return (KEYCMD) fp->func; 25459243Sobrien } 25559243Sobrien } 25659243Sobrien xprintf(CGETS(20, 3, "Bad command name: %S\n"), str); 25759243Sobrien return 0; 25859243Sobrien} 25959243Sobrien 26059243Sobrien 26159243Sobrienstatic void 262167465Smpbad_spec(const Char *str) 26359243Sobrien{ 26459243Sobrien xprintf(CGETS(20, 4, "Bad key spec %S\n"), str); 26559243Sobrien} 26659243Sobrien 26759243Sobrienstatic CStr * 268167465Smpparsebind(const Char *s, CStr *str) 26959243Sobrien{ 270167465Smp struct Strbuf b = Strbuf_INIT; 27159243Sobrien 272167465Smp cleanup_push(&b, Strbuf_cleanup); 27359243Sobrien if (Iscntrl(*s)) { 274167465Smp Strbuf_append1(&b, *s); 275167465Smp goto end; 27659243Sobrien } 27759243Sobrien 27859243Sobrien switch (*s) { 27959243Sobrien case '^': 28059243Sobrien s++; 28169408Sache#ifdef IS_ASCII 282167465Smp Strbuf_append1(&b, (*s == '?') ? '\177' : ((*s & CHAR) & 0237)); 28369408Sache#else 284167465Smp Strbuf_append1(&b, (*s == '?') ? CTL_ESC('\177') 285167465Smp : _toebcdic[_toascii[*s & CHAR] & 0237]); 28669408Sache#endif 28759243Sobrien break; 28859243Sobrien 28959243Sobrien case 'F': 29059243Sobrien case 'M': 29159243Sobrien case 'X': 29259243Sobrien case 'C': 29369408Sache#ifdef WINNT_NATIVE 29459243Sobrien case 'N': 29569408Sache#endif /* WINNT_NATIVE */ 296167465Smp if (s[1] != '-' || s[2] == '\0') 297167465Smp goto bad_spec; 29859243Sobrien s += 2; 29959243Sobrien switch (s[-2]) { 30059243Sobrien case 'F': case 'f': /* Turn into ^[str */ 301167465Smp Strbuf_append1(&b, CTL_ESC('\033')); 302167465Smp Strbuf_append(&b, s); 30359243Sobrien break; 30459243Sobrien 30559243Sobrien case 'C': case 'c': /* Turn into ^c */ 30669408Sache#ifdef IS_ASCII 307167465Smp Strbuf_append1(&b, (*s == '?') ? '\177' : ((*s & CHAR) & 0237)); 30869408Sache#else 309167465Smp Strbuf_append1(&b, (*s == '?') ? CTL_ESC('\177') 310167465Smp : _toebcdic[_toascii[*s & CHAR] & 0237]); 31169408Sache#endif 31259243Sobrien break; 31359243Sobrien 31459243Sobrien case 'X' : case 'x': /* Turn into ^Xc */ 31569408Sache#ifdef IS_ASCII 316167465Smp Strbuf_append1(&b, 'X' & 0237); 31769408Sache#else 318167465Smp Strbuf_append1(&b, _toebcdic[_toascii['X'] & 0237]); 31969408Sache#endif 320167465Smp Strbuf_append1(&b, *s); 32159243Sobrien break; 32259243Sobrien 32359243Sobrien case 'M' : case 'm': /* Turn into 0x80|c */ 32459243Sobrien if (!NoNLSRebind) { 325167465Smp Strbuf_append1(&b, CTL_ESC('\033')); 326167465Smp Strbuf_append1(&b, *s); 32759243Sobrien } else { 32869408Sache#ifdef IS_ASCII 329167465Smp Strbuf_append1(&b, *s | 0x80); 33069408Sache#else 331167465Smp Strbuf_append1(&b, _toebcdic[_toascii[*s] | 0x80]); 33269408Sache#endif 33359243Sobrien } 33459243Sobrien break; 33569408Sache#ifdef WINNT_NATIVE 33659243Sobrien case 'N' : case 'n': /* NT */ 33759243Sobrien { 33859243Sobrien Char bnt; 33959243Sobrien 34059243Sobrien bnt = nt_translate_bindkey(s); 34159243Sobrien if (bnt != 0) 342167465Smp Strbuf_append1(&b, bnt); 34359243Sobrien else 34459243Sobrien bad_spec(s); 34559243Sobrien } 34659243Sobrien break; 34769408Sache#endif /* WINNT_NATIVE */ 34859243Sobrien 34959243Sobrien default: 35059243Sobrien abort(); 35159243Sobrien } 35259243Sobrien break; 35359243Sobrien 35459243Sobrien default: 355167465Smp goto bad_spec; 35659243Sobrien } 35759243Sobrien 358167465Smp end: 359167465Smp cleanup_ignore(&b); 360167465Smp cleanup_until(&b); 361167465Smp Strbuf_terminate(&b); 362167465Smp str->buf = xrealloc(b.s, (b.len + 1) * sizeof (*str->buf)); 363167465Smp str->len = b.len; 36459243Sobrien return str; 365167465Smp 366167465Smp bad_spec: 367167465Smp bad_spec(s); 368167465Smp cleanup_until(&b); 369167465Smp return NULL; 37059243Sobrien} 37159243Sobrien 37259243Sobrien 37359243Sobrienstatic CStr * 374167465Smpparsestring(const Char *str, CStr *buf) 37559243Sobrien{ 376167465Smp struct Strbuf b = Strbuf_INIT; 37759243Sobrien const Char *p; 378145479Smp eChar es; 37959243Sobrien 38059243Sobrien if (*str == 0) { 381195609Smp xprintf("%s", CGETS(20, 5, "Null string specification\n")); 38259243Sobrien return NULL; 38359243Sobrien } 38459243Sobrien 385167465Smp cleanup_push(&b, Strbuf_cleanup); 38659243Sobrien for (p = str; *p != 0; p++) { 38759243Sobrien if ((*p & CHAR) == '\\' || (*p & CHAR) == '^') { 388167465Smp if ((es = parseescape(&p)) == CHAR_ERR) { 389167465Smp cleanup_until(&b); 39059243Sobrien return 0; 391167465Smp } else 392167465Smp Strbuf_append1(&b, es); 39359243Sobrien } 39459243Sobrien else 395167465Smp Strbuf_append1(&b, *p & CHAR); 39659243Sobrien } 397167465Smp cleanup_ignore(&b); 398167465Smp cleanup_until(&b); 399167465Smp Strbuf_terminate(&b); 400167465Smp buf->buf = xrealloc(b.s, (b.len + 1) * sizeof (*buf->buf)); 401167465Smp buf->len = b.len; 40259243Sobrien return buf; 40359243Sobrien} 40459243Sobrien 40559243Sobrienstatic void 406167465Smpprint_all_keys(void) 40759243Sobrien{ 40859243Sobrien int prev, i; 40959243Sobrien CStr nilstr; 41059243Sobrien nilstr.buf = NULL; 41159243Sobrien nilstr.len = 0; 41259243Sobrien 41359243Sobrien 414195609Smp xprintf("%s", CGETS(20, 6, "Standard key bindings\n")); 41559243Sobrien prev = 0; 41659243Sobrien for (i = 0; i < 256; i++) { 41759243Sobrien if (CcKeyMap[prev] == CcKeyMap[i]) 41859243Sobrien continue; 41959243Sobrien printkeys(CcKeyMap, prev, i - 1); 42059243Sobrien prev = i; 42159243Sobrien } 42259243Sobrien printkeys(CcKeyMap, prev, i - 1); 42359243Sobrien 424195609Smp xprintf("%s", CGETS(20, 7, "Alternative key bindings\n")); 42559243Sobrien prev = 0; 42659243Sobrien for (i = 0; i < 256; i++) { 42759243Sobrien if (CcAltMap[prev] == CcAltMap[i]) 42859243Sobrien continue; 42959243Sobrien printkeys(CcAltMap, prev, i - 1); 43059243Sobrien prev = i; 43159243Sobrien } 43259243Sobrien printkeys(CcAltMap, prev, i - 1); 433195609Smp xprintf("%s", CGETS(20, 8, "Multi-character bindings\n")); 43459243Sobrien PrintXkey(NULL); /* print all Xkey bindings */ 435195609Smp xprintf("%s", CGETS(20, 9, "Arrow key bindings\n")); 43659243Sobrien PrintArrowKeys(&nilstr); 43759243Sobrien} 43859243Sobrien 43959243Sobrienstatic void 440167465Smpprintkeys(KEYCMD *map, int first, int last) 44159243Sobrien{ 442145479Smp struct KeyFuncs *fp; 44359243Sobrien Char firstbuf[2], lastbuf[2]; 44459243Sobrien CStr fb, lb; 445167465Smp unsigned char *unparsed; 44659243Sobrien fb.buf = firstbuf; 44759243Sobrien lb.buf = lastbuf; 44859243Sobrien 44959243Sobrien firstbuf[0] = (Char) first; 45059243Sobrien firstbuf[1] = 0; 45159243Sobrien lastbuf[0] = (Char) last; 45259243Sobrien lastbuf[1] = 0; 45359243Sobrien fb.len = 1; 45459243Sobrien lb.len = 1; 45559243Sobrien 456167465Smp unparsed = unparsestring(&fb, STRQQ); 457167465Smp cleanup_push(unparsed, xfree); 45859243Sobrien if (map[first] == F_UNASSIGNED) { 45959243Sobrien if (first == last) 460167465Smp xprintf(CGETS(20, 10, "%-15s-> is undefined\n"), unparsed); 461167465Smp cleanup_until(unparsed); 46259243Sobrien return; 46359243Sobrien } 46459243Sobrien 46559243Sobrien for (fp = FuncNames; fp->name; fp++) { 46659243Sobrien if (fp->func == map[first]) { 467167465Smp if (first == last) 468167465Smp xprintf("%-15s-> %s\n", unparsed, fp->name); 46959243Sobrien else { 470167465Smp unsigned char *p; 471167465Smp 472167465Smp p = unparsestring(&lb, STRQQ); 473167465Smp cleanup_push(p, xfree); 474167465Smp xprintf("%-4s to %-7s-> %s\n", unparsed, p, fp->name); 47559243Sobrien } 476167465Smp cleanup_until(unparsed); 47759243Sobrien return; 47859243Sobrien } 47959243Sobrien } 480167465Smp xprintf(CGETS(20, 11, "BUG!!! %s isn't bound to anything.\n"), unparsed); 481167465Smp if (map == CcKeyMap) 48259243Sobrien xprintf("CcKeyMap[%d] == %d\n", first, CcKeyMap[first]); 483167465Smp else 48459243Sobrien xprintf("CcAltMap[%d] == %d\n", first, CcAltMap[first]); 485167465Smp cleanup_until(unparsed); 48659243Sobrien} 48759243Sobrien 48859243Sobrienstatic void 489167465Smpbindkey_usage(void) 49059243Sobrien{ 491195609Smp xprintf("%s", CGETS(20, 12, 49259243Sobrien "Usage: bindkey [options] [--] [KEY [COMMAND]]\n")); 493195609Smp xprintf("%s", CGETS(20, 13, 49459243Sobrien " -a list or bind KEY in alternative key map\n")); 495195609Smp xprintf("%s", CGETS(20, 14, 49659243Sobrien " -b interpret KEY as a C-, M-, F- or X- key name\n")); 497195609Smp xprintf("%s", CGETS(20, 15, 49859243Sobrien " -s interpret COMMAND as a literal string to be output\n")); 499195609Smp xprintf("%s", CGETS(20, 16, 50059243Sobrien " -c interpret COMMAND as a builtin or external command\n")); 501195609Smp xprintf("%s", CGETS(20, 17, 50259243Sobrien " -v bind all keys to vi bindings\n")); 503195609Smp xprintf("%s", CGETS(20, 18, 50459243Sobrien " -e bind all keys to emacs bindings\n")); 505316957Sdchagin xprintf(CGETS(20, 19, 506316957Sdchagin " -d bind all keys to default editor's bindings (%s)\n"), 507316957Sdchagin#ifdef VIDEFAULT 508316957Sdchagin "vi" 509316957Sdchagin#else /* EMACSDEFAULT */ 510316957Sdchagin "emacs" 511316957Sdchagin#endif /* VIDEFAULT */ 512316957Sdchagin ); 513195609Smp xprintf("%s", CGETS(20, 20, 51459243Sobrien " -l list editor commands with descriptions\n")); 515195609Smp xprintf("%s", CGETS(20, 21, 51659243Sobrien " -r remove KEY's binding\n")); 517195609Smp xprintf("%s", CGETS(20, 22, 51859243Sobrien " -k interpret KEY as a symbolic arrow-key name\n")); 519195609Smp xprintf("%s", CGETS(20, 23, 52059243Sobrien " -- force a break from option processing\n")); 521195609Smp xprintf("%s", CGETS(20, 24, 52259243Sobrien " -u (or any invalid option) this message\n")); 52359243Sobrien xprintf("\n"); 524195609Smp xprintf("%s", CGETS(20, 25, 52559243Sobrien "Without KEY or COMMAND, prints all bindings\n")); 526195609Smp xprintf("%s", CGETS(20, 26, 52759243Sobrien "Without COMMAND, prints the binding for KEY.\n")); 52859243Sobrien} 52959243Sobrien 53059243Sobrienstatic void 531167465Smplist_functions(void) 53259243Sobrien{ 533145479Smp struct KeyFuncs *fp; 53459243Sobrien 53559243Sobrien for (fp = FuncNames; fp->name; fp++) { 53659243Sobrien xprintf("%s\n %s\n", fp->name, fp->desc); 53759243Sobrien } 53859243Sobrien} 539