1195609Smp/* $Header: /p/tcsh/cvsroot/tcsh/tc.bind.c,v 3.45 2009/06/25 21:15:37 christos Exp $ */ 259243Sobrien/* 359243Sobrien * tc.bind.c: Key binding functions 459243Sobrien */ 559243Sobrien/*- 659243Sobrien * Copyright (c) 1980, 1991 The Regents of the University of California. 759243Sobrien * All rights reserved. 859243Sobrien * 959243Sobrien * Redistribution and use in source and binary forms, with or without 1059243Sobrien * modification, are permitted provided that the following conditions 1159243Sobrien * are met: 1259243Sobrien * 1. Redistributions of source code must retain the above copyright 1359243Sobrien * notice, this list of conditions and the following disclaimer. 1459243Sobrien * 2. Redistributions in binary form must reproduce the above copyright 1559243Sobrien * notice, this list of conditions and the following disclaimer in the 1659243Sobrien * documentation and/or other materials provided with the distribution. 17100616Smp * 3. Neither the name of the University nor the names of its contributors 1859243Sobrien * may be used to endorse or promote products derived from this software 1959243Sobrien * without specific prior written permission. 2059243Sobrien * 2159243Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 2259243Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2359243Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2459243Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 2559243Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2659243Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2759243Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2859243Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2959243Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3059243Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3159243Sobrien * SUCH DAMAGE. 3259243Sobrien */ 3359243Sobrien#include "sh.h" 3459243Sobrien 35195609SmpRCSID("$tcsh: tc.bind.c,v 3.45 2009/06/25 21:15:37 christos Exp $") 3659243Sobrien 3759243Sobrien#include "ed.h" 3859243Sobrien#include "ed.defns.h" 3959243Sobrien 40167465Smpstatic void printkey (const KEYCMD *, CStr *); 41167465Smpstatic KEYCMD parsecmd (Char *); 42167465Smpstatic void bad_spec (const Char *); 43167465Smpstatic CStr *parsestring (const Char *, CStr *); 44167465Smpstatic CStr *parsebind (const Char *, CStr *); 45167465Smpstatic void print_all_keys (void); 46167465Smpstatic void printkeys (KEYCMD *, int, int); 47167465Smpstatic void bindkey_usage (void); 48167465Smpstatic void list_functions (void); 4959243Sobrien 5059243Sobrienextern int MapsAreInited; 5159243Sobrien 5259243Sobrien 5359243Sobrien 5459243Sobrien 5559243Sobrien/*ARGSUSED*/ 5659243Sobrienvoid 57167465Smpdobindkey(Char **v, struct command *c) 5859243Sobrien{ 5959243Sobrien KEYCMD *map; 60145479Smp int ntype, no, removeb, key, bindk; 6159243Sobrien Char *par; 6259243Sobrien Char p; 6359243Sobrien KEYCMD cmd; 6459243Sobrien CStr in; 6559243Sobrien CStr out; 6659243Sobrien uChar ch; 6759243Sobrien 6859243Sobrien USE(c); 6959243Sobrien if (!MapsAreInited) 7059243Sobrien ed_InitMaps(); 7159243Sobrien 7259243Sobrien map = CcKeyMap; 7359243Sobrien ntype = XK_CMD; 74145479Smp key = removeb = bindk = 0; 7559243Sobrien for (no = 1, par = v[no]; 7659243Sobrien par != NULL && (*par++ & CHAR) == '-'; no++, par = v[no]) { 7759243Sobrien if ((p = (*par & CHAR)) == '-') { 7859243Sobrien no++; 7959243Sobrien break; 8059243Sobrien } 8159243Sobrien else 8259243Sobrien switch (p) { 8359243Sobrien case 'b': 84145479Smp bindk = 1; 8559243Sobrien break; 8659243Sobrien case 'k': 8759243Sobrien key = 1; 8859243Sobrien break; 8959243Sobrien case 'a': 9059243Sobrien map = CcAltMap; 9159243Sobrien break; 9259243Sobrien case 's': 9359243Sobrien ntype = XK_STR; 9459243Sobrien break; 9559243Sobrien case 'c': 9659243Sobrien ntype = XK_EXE; 9759243Sobrien break; 9859243Sobrien case 'r': 99145479Smp removeb = 1; 10059243Sobrien break; 10159243Sobrien case 'v': 10259243Sobrien ed_InitVIMaps(); 10359243Sobrien return; 10459243Sobrien case 'e': 10559243Sobrien ed_InitEmacsMaps(); 10659243Sobrien return; 10759243Sobrien case 'd': 10859243Sobrien#ifdef VIDEFAULT 10959243Sobrien ed_InitVIMaps(); 11059243Sobrien#else /* EMACSDEFAULT */ 11159243Sobrien ed_InitEmacsMaps(); 11259243Sobrien#endif /* VIDEFAULT */ 11359243Sobrien return; 11459243Sobrien case 'l': 11559243Sobrien list_functions(); 11659243Sobrien return; 11759243Sobrien default: 11859243Sobrien bindkey_usage(); 11959243Sobrien return; 12059243Sobrien } 12159243Sobrien } 12259243Sobrien 12359243Sobrien if (!v[no]) { 12459243Sobrien print_all_keys(); 12559243Sobrien return; 12659243Sobrien } 12759243Sobrien 12859243Sobrien if (key) { 12959243Sobrien if (!IsArrowKey(v[no])) 13059243Sobrien xprintf(CGETS(20, 1, "Invalid key name `%S'\n"), v[no]); 131167465Smp in.buf = Strsave(v[no++]); 13259243Sobrien in.len = Strlen(in.buf); 13359243Sobrien } 13459243Sobrien else { 135145479Smp if (bindk) { 13659243Sobrien if (parsebind(v[no++], &in) == NULL) 13759243Sobrien return; 13859243Sobrien } 13959243Sobrien else { 14059243Sobrien if (parsestring(v[no++], &in) == NULL) 14159243Sobrien return; 14259243Sobrien } 14359243Sobrien } 144167465Smp cleanup_push(in.buf, xfree); 14559243Sobrien 146145479Smp#ifndef WINNT_NATIVE 147145479Smp if (in.buf[0] > 0xFF) { 148145479Smp bad_spec(in.buf); 149167465Smp cleanup_until(in.buf); 150145479Smp return; 151145479Smp } 152145479Smp#endif 15359243Sobrien ch = (uChar) in.buf[0]; 15459243Sobrien 155145479Smp if (removeb) { 156167465Smp if (key) 15759243Sobrien (void) ClearArrowKeys(&in); 158167465Smp else if (in.len > 1) { 15959243Sobrien (void) DeleteXkey(&in); 16059243Sobrien } 16159243Sobrien else if (map[ch] == F_XKEY) { 16259243Sobrien (void) DeleteXkey(&in); 16359243Sobrien map[ch] = F_UNASSIGNED; 16459243Sobrien } 16559243Sobrien else { 16659243Sobrien map[ch] = F_UNASSIGNED; 16759243Sobrien } 168167465Smp cleanup_until(in.buf); 16959243Sobrien return; 17059243Sobrien } 17159243Sobrien if (!v[no]) { 17259243Sobrien if (key) 17359243Sobrien PrintArrowKeys(&in); 17459243Sobrien else 17559243Sobrien printkey(map, &in); 176167465Smp cleanup_until(in.buf); 17759243Sobrien return; 17859243Sobrien } 17959243Sobrien if (v[no + 1]) { 18059243Sobrien bindkey_usage(); 181167465Smp cleanup_until(in.buf); 18259243Sobrien return; 18359243Sobrien } 18459243Sobrien switch (ntype) { 18559243Sobrien case XK_STR: 18659243Sobrien case XK_EXE: 187167465Smp if (parsestring(v[no], &out) == NULL) { 188167465Smp cleanup_until(in.buf); 18959243Sobrien return; 190167465Smp } 191167465Smp cleanup_push(out.buf, xfree); 19259243Sobrien if (key) { 19359243Sobrien if (SetArrowKeys(&in, XmapStr(&out), ntype) == -1) 194145479Smp xprintf(CGETS(20, 2, "Bad key name: %S\n"), in.buf); 195167465Smp else 196167465Smp cleanup_ignore(out.buf); 19759243Sobrien } 19859243Sobrien else 19959243Sobrien AddXkey(&in, XmapStr(&out), ntype); 20059243Sobrien map[ch] = F_XKEY; 20159243Sobrien break; 20259243Sobrien case XK_CMD: 203167465Smp if ((cmd = parsecmd(v[no])) == 0) { 204167465Smp cleanup_until(in.buf); 20559243Sobrien return; 206167465Smp } 20759243Sobrien if (key) 20859243Sobrien (void) SetArrowKeys(&in, XmapCmd((int) cmd), ntype); 20959243Sobrien else { 21059243Sobrien if (in.len > 1) { 21159243Sobrien AddXkey(&in, XmapCmd((int) cmd), ntype); 21259243Sobrien map[ch] = F_XKEY; 21359243Sobrien } 21459243Sobrien else { 21559243Sobrien ClearXkey(map, &in); 21659243Sobrien map[ch] = cmd; 21759243Sobrien } 21859243Sobrien } 21959243Sobrien break; 22059243Sobrien default: 22159243Sobrien abort(); 22259243Sobrien break; 22359243Sobrien } 224167465Smp cleanup_until(in.buf); 22559243Sobrien if (key) 22659243Sobrien BindArrowKeys(); 22759243Sobrien} 22859243Sobrien 22959243Sobrienstatic void 230167465Smpprintkey(const KEYCMD *map, CStr *in) 23159243Sobrien{ 232145479Smp struct KeyFuncs *fp; 23359243Sobrien 23459243Sobrien if (in->len < 2) { 235167465Smp unsigned char *unparsed; 236167465Smp 237167465Smp unparsed = unparsestring(in, STRQQ); 238167465Smp cleanup_push(unparsed, xfree); 23959243Sobrien for (fp = FuncNames; fp->name; fp++) { 24059243Sobrien if (fp->func == map[(uChar) *(in->buf)]) { 241167465Smp xprintf("%s\t->\t%s\n", unparsed, fp->name); 24259243Sobrien } 24359243Sobrien } 244167465Smp cleanup_until(unparsed); 24559243Sobrien } 246167465Smp else 24759243Sobrien PrintXkey(in); 24859243Sobrien} 24959243Sobrien 25059243Sobrienstatic KEYCMD 251167465Smpparsecmd(Char *str) 25259243Sobrien{ 253145479Smp struct KeyFuncs *fp; 25459243Sobrien 25559243Sobrien for (fp = FuncNames; fp->name; fp++) { 25659243Sobrien if (strcmp(short2str(str), fp->name) == 0) { 25759243Sobrien return (KEYCMD) fp->func; 25859243Sobrien } 25959243Sobrien } 26059243Sobrien xprintf(CGETS(20, 3, "Bad command name: %S\n"), str); 26159243Sobrien return 0; 26259243Sobrien} 26359243Sobrien 26459243Sobrien 26559243Sobrienstatic void 266167465Smpbad_spec(const Char *str) 26759243Sobrien{ 26859243Sobrien xprintf(CGETS(20, 4, "Bad key spec %S\n"), str); 26959243Sobrien} 27059243Sobrien 27159243Sobrienstatic CStr * 272167465Smpparsebind(const Char *s, CStr *str) 27359243Sobrien{ 274167465Smp struct Strbuf b = Strbuf_INIT; 27559243Sobrien 276167465Smp cleanup_push(&b, Strbuf_cleanup); 27759243Sobrien if (Iscntrl(*s)) { 278167465Smp Strbuf_append1(&b, *s); 279167465Smp goto end; 28059243Sobrien } 28159243Sobrien 28259243Sobrien switch (*s) { 28359243Sobrien case '^': 28459243Sobrien s++; 28569408Sache#ifdef IS_ASCII 286167465Smp Strbuf_append1(&b, (*s == '?') ? '\177' : ((*s & CHAR) & 0237)); 28769408Sache#else 288167465Smp Strbuf_append1(&b, (*s == '?') ? CTL_ESC('\177') 289167465Smp : _toebcdic[_toascii[*s & CHAR] & 0237]); 29069408Sache#endif 29159243Sobrien break; 29259243Sobrien 29359243Sobrien case 'F': 29459243Sobrien case 'M': 29559243Sobrien case 'X': 29659243Sobrien case 'C': 29769408Sache#ifdef WINNT_NATIVE 29859243Sobrien case 'N': 29969408Sache#endif /* WINNT_NATIVE */ 300167465Smp if (s[1] != '-' || s[2] == '\0') 301167465Smp goto bad_spec; 30259243Sobrien s += 2; 30359243Sobrien switch (s[-2]) { 30459243Sobrien case 'F': case 'f': /* Turn into ^[str */ 305167465Smp Strbuf_append1(&b, CTL_ESC('\033')); 306167465Smp Strbuf_append(&b, s); 30759243Sobrien break; 30859243Sobrien 30959243Sobrien case 'C': case 'c': /* Turn into ^c */ 31069408Sache#ifdef IS_ASCII 311167465Smp Strbuf_append1(&b, (*s == '?') ? '\177' : ((*s & CHAR) & 0237)); 31269408Sache#else 313167465Smp Strbuf_append1(&b, (*s == '?') ? CTL_ESC('\177') 314167465Smp : _toebcdic[_toascii[*s & CHAR] & 0237]); 31569408Sache#endif 31659243Sobrien break; 31759243Sobrien 31859243Sobrien case 'X' : case 'x': /* Turn into ^Xc */ 31969408Sache#ifdef IS_ASCII 320167465Smp Strbuf_append1(&b, 'X' & 0237); 32169408Sache#else 322167465Smp Strbuf_append1(&b, _toebcdic[_toascii['X'] & 0237]); 32369408Sache#endif 324167465Smp Strbuf_append1(&b, *s); 32559243Sobrien break; 32659243Sobrien 32759243Sobrien case 'M' : case 'm': /* Turn into 0x80|c */ 32859243Sobrien if (!NoNLSRebind) { 329167465Smp Strbuf_append1(&b, CTL_ESC('\033')); 330167465Smp Strbuf_append1(&b, *s); 33159243Sobrien } else { 33269408Sache#ifdef IS_ASCII 333167465Smp Strbuf_append1(&b, *s | 0x80); 33469408Sache#else 335167465Smp Strbuf_append1(&b, _toebcdic[_toascii[*s] | 0x80]); 33669408Sache#endif 33759243Sobrien } 33859243Sobrien break; 33969408Sache#ifdef WINNT_NATIVE 34059243Sobrien case 'N' : case 'n': /* NT */ 34159243Sobrien { 34259243Sobrien Char bnt; 34359243Sobrien 34459243Sobrien bnt = nt_translate_bindkey(s); 34559243Sobrien if (bnt != 0) 346167465Smp Strbuf_append1(&b, bnt); 34759243Sobrien else 34859243Sobrien bad_spec(s); 34959243Sobrien } 35059243Sobrien break; 35169408Sache#endif /* WINNT_NATIVE */ 35259243Sobrien 35359243Sobrien default: 35459243Sobrien abort(); 35559243Sobrien } 35659243Sobrien break; 35759243Sobrien 35859243Sobrien default: 359167465Smp goto bad_spec; 36059243Sobrien } 36159243Sobrien 362167465Smp end: 363167465Smp cleanup_ignore(&b); 364167465Smp cleanup_until(&b); 365167465Smp Strbuf_terminate(&b); 366167465Smp str->buf = xrealloc(b.s, (b.len + 1) * sizeof (*str->buf)); 367167465Smp str->len = b.len; 36859243Sobrien return str; 369167465Smp 370167465Smp bad_spec: 371167465Smp bad_spec(s); 372167465Smp cleanup_until(&b); 373167465Smp return NULL; 37459243Sobrien} 37559243Sobrien 37659243Sobrien 37759243Sobrienstatic CStr * 378167465Smpparsestring(const Char *str, CStr *buf) 37959243Sobrien{ 380167465Smp struct Strbuf b = Strbuf_INIT; 38159243Sobrien const Char *p; 382145479Smp eChar es; 38359243Sobrien 38459243Sobrien if (*str == 0) { 385195609Smp xprintf("%s", CGETS(20, 5, "Null string specification\n")); 38659243Sobrien return NULL; 38759243Sobrien } 38859243Sobrien 389167465Smp cleanup_push(&b, Strbuf_cleanup); 39059243Sobrien for (p = str; *p != 0; p++) { 39159243Sobrien if ((*p & CHAR) == '\\' || (*p & CHAR) == '^') { 392167465Smp if ((es = parseescape(&p)) == CHAR_ERR) { 393167465Smp cleanup_until(&b); 39459243Sobrien return 0; 395167465Smp } else 396167465Smp Strbuf_append1(&b, es); 39759243Sobrien } 39859243Sobrien else 399167465Smp Strbuf_append1(&b, *p & CHAR); 40059243Sobrien } 401167465Smp cleanup_ignore(&b); 402167465Smp cleanup_until(&b); 403167465Smp Strbuf_terminate(&b); 404167465Smp buf->buf = xrealloc(b.s, (b.len + 1) * sizeof (*buf->buf)); 405167465Smp buf->len = b.len; 40659243Sobrien return buf; 40759243Sobrien} 40859243Sobrien 40959243Sobrienstatic void 410167465Smpprint_all_keys(void) 41159243Sobrien{ 41259243Sobrien int prev, i; 41359243Sobrien CStr nilstr; 41459243Sobrien nilstr.buf = NULL; 41559243Sobrien nilstr.len = 0; 41659243Sobrien 41759243Sobrien 418195609Smp xprintf("%s", CGETS(20, 6, "Standard key bindings\n")); 41959243Sobrien prev = 0; 42059243Sobrien for (i = 0; i < 256; i++) { 42159243Sobrien if (CcKeyMap[prev] == CcKeyMap[i]) 42259243Sobrien continue; 42359243Sobrien printkeys(CcKeyMap, prev, i - 1); 42459243Sobrien prev = i; 42559243Sobrien } 42659243Sobrien printkeys(CcKeyMap, prev, i - 1); 42759243Sobrien 428195609Smp xprintf("%s", CGETS(20, 7, "Alternative key bindings\n")); 42959243Sobrien prev = 0; 43059243Sobrien for (i = 0; i < 256; i++) { 43159243Sobrien if (CcAltMap[prev] == CcAltMap[i]) 43259243Sobrien continue; 43359243Sobrien printkeys(CcAltMap, prev, i - 1); 43459243Sobrien prev = i; 43559243Sobrien } 43659243Sobrien printkeys(CcAltMap, prev, i - 1); 437195609Smp xprintf("%s", CGETS(20, 8, "Multi-character bindings\n")); 43859243Sobrien PrintXkey(NULL); /* print all Xkey bindings */ 439195609Smp xprintf("%s", CGETS(20, 9, "Arrow key bindings\n")); 44059243Sobrien PrintArrowKeys(&nilstr); 44159243Sobrien} 44259243Sobrien 44359243Sobrienstatic void 444167465Smpprintkeys(KEYCMD *map, int first, int last) 44559243Sobrien{ 446145479Smp struct KeyFuncs *fp; 44759243Sobrien Char firstbuf[2], lastbuf[2]; 44859243Sobrien CStr fb, lb; 449167465Smp unsigned char *unparsed; 45059243Sobrien fb.buf = firstbuf; 45159243Sobrien lb.buf = lastbuf; 45259243Sobrien 45359243Sobrien firstbuf[0] = (Char) first; 45459243Sobrien firstbuf[1] = 0; 45559243Sobrien lastbuf[0] = (Char) last; 45659243Sobrien lastbuf[1] = 0; 45759243Sobrien fb.len = 1; 45859243Sobrien lb.len = 1; 45959243Sobrien 460167465Smp unparsed = unparsestring(&fb, STRQQ); 461167465Smp cleanup_push(unparsed, xfree); 46259243Sobrien if (map[first] == F_UNASSIGNED) { 46359243Sobrien if (first == last) 464167465Smp xprintf(CGETS(20, 10, "%-15s-> is undefined\n"), unparsed); 465167465Smp cleanup_until(unparsed); 46659243Sobrien return; 46759243Sobrien } 46859243Sobrien 46959243Sobrien for (fp = FuncNames; fp->name; fp++) { 47059243Sobrien if (fp->func == map[first]) { 471167465Smp if (first == last) 472167465Smp xprintf("%-15s-> %s\n", unparsed, fp->name); 47359243Sobrien else { 474167465Smp unsigned char *p; 475167465Smp 476167465Smp p = unparsestring(&lb, STRQQ); 477167465Smp cleanup_push(p, xfree); 478167465Smp xprintf("%-4s to %-7s-> %s\n", unparsed, p, fp->name); 47959243Sobrien } 480167465Smp cleanup_until(unparsed); 48159243Sobrien return; 48259243Sobrien } 48359243Sobrien } 484167465Smp xprintf(CGETS(20, 11, "BUG!!! %s isn't bound to anything.\n"), unparsed); 485167465Smp if (map == CcKeyMap) 48659243Sobrien xprintf("CcKeyMap[%d] == %d\n", first, CcKeyMap[first]); 487167465Smp else 48859243Sobrien xprintf("CcAltMap[%d] == %d\n", first, CcAltMap[first]); 489167465Smp cleanup_until(unparsed); 49059243Sobrien} 49159243Sobrien 49259243Sobrienstatic void 493167465Smpbindkey_usage(void) 49459243Sobrien{ 495195609Smp xprintf("%s", CGETS(20, 12, 49659243Sobrien "Usage: bindkey [options] [--] [KEY [COMMAND]]\n")); 497195609Smp xprintf("%s", CGETS(20, 13, 49859243Sobrien " -a list or bind KEY in alternative key map\n")); 499195609Smp xprintf("%s", CGETS(20, 14, 50059243Sobrien " -b interpret KEY as a C-, M-, F- or X- key name\n")); 501195609Smp xprintf("%s", CGETS(20, 15, 50259243Sobrien " -s interpret COMMAND as a literal string to be output\n")); 503195609Smp xprintf("%s", CGETS(20, 16, 50459243Sobrien " -c interpret COMMAND as a builtin or external command\n")); 505195609Smp xprintf("%s", CGETS(20, 17, 50659243Sobrien " -v bind all keys to vi bindings\n")); 507195609Smp xprintf("%s", CGETS(20, 18, 50859243Sobrien " -e bind all keys to emacs bindings\n")); 509195609Smp xprintf("%s", CGETS(20, 19, 51059243Sobrien " -d bind all keys to default editor's bindings\n")); 511195609Smp xprintf("%s", CGETS(20, 20, 51259243Sobrien " -l list editor commands with descriptions\n")); 513195609Smp xprintf("%s", CGETS(20, 21, 51459243Sobrien " -r remove KEY's binding\n")); 515195609Smp xprintf("%s", CGETS(20, 22, 51659243Sobrien " -k interpret KEY as a symbolic arrow-key name\n")); 517195609Smp xprintf("%s", CGETS(20, 23, 51859243Sobrien " -- force a break from option processing\n")); 519195609Smp xprintf("%s", CGETS(20, 24, 52059243Sobrien " -u (or any invalid option) this message\n")); 52159243Sobrien xprintf("\n"); 522195609Smp xprintf("%s", CGETS(20, 25, 52359243Sobrien "Without KEY or COMMAND, prints all bindings\n")); 524195609Smp xprintf("%s", CGETS(20, 26, 52559243Sobrien "Without COMMAND, prints the binding for KEY.\n")); 52659243Sobrien} 52759243Sobrien 52859243Sobrienstatic void 529167465Smplist_functions(void) 53059243Sobrien{ 531145479Smp struct KeyFuncs *fp; 53259243Sobrien 53359243Sobrien for (fp = FuncNames; fp->name; fp++) { 53459243Sobrien xprintf("%s\n %s\n", fp->name, fp->desc); 53559243Sobrien } 53659243Sobrien} 537