Annotation of src/usr.bin/less/lesskey.c, Revision 1.1
1.1 ! etheisen 1: /*
! 2: * Copyright (c) 1984,1985,1989,1994,1995 Mark Nudelman
! 3: * All rights reserved.
! 4: *
! 5: * Redistribution and use in source and binary forms, with or without
! 6: * modification, are permitted provided that the following conditions
! 7: * are met:
! 8: * 1. Redistributions of source code must retain the above copyright
! 9: * notice, this list of conditions and the following disclaimer.
! 10: * 2. Redistributions in binary form must reproduce the above copyright
! 11: * notice in the documentation and/or other materials provided with
! 12: * the distribution.
! 13: *
! 14: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
! 15: * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
! 16: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
! 17: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE
! 18: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
! 19: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
! 20: * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
! 21: * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
! 22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
! 23: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
! 24: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
! 25: */
! 26:
! 27:
! 28: /*
! 29: * lesskey [-o output] [input]
! 30: *
! 31: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
! 32: *
! 33: * Make a .less file.
! 34: * If no input file is specified, standard input is used.
! 35: * If no output file is specified, $HOME/.less is used.
! 36: *
! 37: * The .less file is used to specify (to "less") user-defined
! 38: * key bindings. Basically any sequence of 1 to MAX_CMDLEN
! 39: * keystrokes may be bound to an existing less function.
! 40: *
! 41: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
! 42: *
! 43: * The input file is an ascii file consisting of a
! 44: * sequence of lines of the form:
! 45: * string <whitespace> action [chars] <newline>
! 46: *
! 47: * "string" is a sequence of command characters which form
! 48: * the new user-defined command. The command
! 49: * characters may be:
! 50: * 1. The actual character itself.
! 51: * 2. A character preceded by ^ to specify a
! 52: * control character (e.g. ^X means control-X).
! 53: * 3. A backslash followed by one to three octal digits
! 54: * to specify a character by its octal value.
! 55: * 4. A backslash followed by b, e, n, r or t
! 56: * to specify \b, ESC, \n, \r or \t, respectively.
! 57: * 5. Any character (other than those mentioned above) preceded
! 58: * by a \ to specify the character itself (characters which
! 59: * must be preceded by \ include ^, \, and whitespace.
! 60: * "action" is the name of a "less" action, from the table below.
! 61: * "chars" is an optional sequence of characters which is treated
! 62: * as keyboard input after the command is executed.
! 63: *
! 64: * Blank lines and lines which start with # are ignored,
! 65: * except for the special control lines:
! 66: * #line-edit Signals the beginning of the line-editing
! 67: * keys section.
! 68: * #stop Stops command parsing in less;
! 69: * causes all default keys to be disabled.
! 70: *
! 71: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
! 72: *
! 73: * The output file is a non-ascii file, consisting of a header,
! 74: * one or more sections, and a trailer.
! 75: * Each section begins with a section header, a section length word
! 76: * and the section data. Normally there are three sections:
! 77: * CMD_SECTION Definition of command keys.
! 78: * EDIT_SECTION Definition of editing keys.
! 79: * END_SECTION A special section header, with no
! 80: * length word or section data.
! 81: *
! 82: * Section data consists of zero or more byte sequences of the form:
! 83: * string <0> <action>
! 84: * or
! 85: * string <0> <action|A_EXTRA> chars <0>
! 86: *
! 87: * "string" is the command string.
! 88: * "<0>" is one null byte.
! 89: * "<action>" is one byte containing the action code (the A_xxx value).
! 90: * If action is ORed with A_EXTRA, the action byte is followed
! 91: * by the null-terminated "chars" string.
! 92: *
! 93: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
! 94: */
! 95:
! 96: #include "less.h"
! 97: #include "lesskey.h"
! 98: #include "cmd.h"
! 99:
! 100: struct cmdname
! 101: {
! 102: char *cn_name;
! 103: int cn_action;
! 104: };
! 105:
! 106: struct cmdname cmdnames[] =
! 107: {
! 108: "back-bracket", A_B_BRACKET,
! 109: "back-line", A_B_LINE,
! 110: "back-line-force", A_BF_LINE,
! 111: "back-screen", A_B_SCREEN,
! 112: "back-scroll", A_B_SCROLL,
! 113: "back-search", A_B_SEARCH,
! 114: "back-window", A_B_WINDOW,
! 115: "debug", A_DEBUG,
! 116: "display-flag", A_DISP_OPTION,
! 117: "display-option", A_DISP_OPTION,
! 118: "end", A_GOEND,
! 119: "examine", A_EXAMINE,
! 120: "first-cmd", A_FIRSTCMD,
! 121: "firstcmd", A_FIRSTCMD,
! 122: "flush-repaint", A_FREPAINT,
! 123: "forw-bracket", A_F_BRACKET,
! 124: "forw-forever", A_F_FOREVER,
! 125: "forw-line", A_F_LINE,
! 126: "forw-line-force", A_FF_LINE,
! 127: "forw-screen", A_F_SCREEN,
! 128: "forw-scroll", A_F_SCROLL,
! 129: "forw-search", A_F_SEARCH,
! 130: "forw-window", A_F_WINDOW,
! 131: "goto-end", A_GOEND,
! 132: "goto-line", A_GOLINE,
! 133: "goto-mark", A_GOMARK,
! 134: "help", A_HELP,
! 135: "index-file", A_INDEX_FILE,
! 136: "invalid", A_UINVALID,
! 137: "next-file", A_NEXT_FILE,
! 138: "noaction", A_NOACTION,
! 139: "percent", A_PERCENT,
! 140: "pipe", A_PIPE,
! 141: "prev-file", A_PREV_FILE,
! 142: "quit", A_QUIT,
! 143: "repaint", A_REPAINT,
! 144: "repaint-flush", A_FREPAINT,
! 145: "repeat-search", A_AGAIN_SEARCH,
! 146: "repeat-search-all", A_T_AGAIN_SEARCH,
! 147: "reverse-search", A_REVERSE_SEARCH,
! 148: "reverse-search-all", A_T_REVERSE_SEARCH,
! 149: "set-mark", A_SETMARK,
! 150: "shell", A_SHELL,
! 151: "status", A_STAT,
! 152: "toggle-flag", A_OPT_TOGGLE,
! 153: "toggle-option", A_OPT_TOGGLE,
! 154: "undo-hilite", A_UNDO_SEARCH,
! 155: "version", A_VERSION,
! 156: "visual", A_VISUAL,
! 157: NULL, 0
! 158: };
! 159:
! 160: struct cmdname editnames[] =
! 161: {
! 162: "back-complete", EC_B_COMPLETE,
! 163: "backspace", EC_BACKSPACE,
! 164: "delete", EC_DELETE,
! 165: "down", EC_DOWN,
! 166: "end", EC_END,
! 167: "expand", EC_EXPAND,
! 168: "forw-complete", EC_F_COMPLETE,
! 169: "home", EC_HOME,
! 170: "insert", EC_INSERT,
! 171: "invalid", EC_UINVALID,
! 172: "kill-line", EC_LINEKILL,
! 173: "left", EC_LEFT,
! 174: "literal", EC_LITERAL,
! 175: "right", EC_RIGHT,
! 176: "up", EC_UP,
! 177: "word-backspace", EC_W_BACKSPACE,
! 178: "word-delete", EC_W_DELETE,
! 179: "word-left", EC_W_RIGHT,
! 180: "word-right", EC_W_LEFT,
! 181: NULL, 0
! 182: };
! 183:
! 184: struct table
! 185: {
! 186: struct cmdname *names;
! 187: char *pbuffer;
! 188: char buffer[MAX_USERCMD];
! 189: };
! 190:
! 191: struct table cmdtable;
! 192: struct table edittable;
! 193: struct table *currtable = &cmdtable;
! 194:
! 195: char fileheader[] = {
! 196: C0_LESSKEY_MAGIC,
! 197: C1_LESSKEY_MAGIC,
! 198: C2_LESSKEY_MAGIC,
! 199: C3_LESSKEY_MAGIC
! 200: };
! 201: char filetrailer[] = {
! 202: C0_END_LESSKEY_MAGIC,
! 203: C1_END_LESSKEY_MAGIC,
! 204: C2_END_LESSKEY_MAGIC
! 205: };
! 206: char cmdsection[1] = { CMD_SECTION };
! 207: char editsection[1] = { EDIT_SECTION };
! 208: char endsection[1] = { END_SECTION };
! 209:
! 210: char *infile = NULL;
! 211: char *outfile = NULL ;
! 212:
! 213: int linenum;
! 214: int errors;
! 215:
! 216: extern char version[];
! 217:
! 218: char *
! 219: mkpathname(dirname, filename)
! 220: char *dirname;
! 221: char *filename;
! 222: {
! 223: char *pathname;
! 224:
! 225: pathname = calloc(strlen(dirname) + strlen(filename) + 2, sizeof(char));
! 226: strcpy(pathname, dirname);
! 227: #if MSOFTC || OS2
! 228: strcat(pathname, "\\");
! 229: #else
! 230: strcat(pathname, "/");
! 231: #endif
! 232: strcat(pathname, filename);
! 233: return (pathname);
! 234: }
! 235:
! 236: /*
! 237: * Figure out the name of a default file (in the user's HOME directory).
! 238: */
! 239: char *
! 240: homefile(filename)
! 241: char *filename;
! 242: {
! 243: char *p;
! 244: char *pathname;
! 245:
! 246: if ((p = getenv("HOME")) != NULL && *p != '\0')
! 247: pathname = mkpathname(p, filename);
! 248: #if OS2
! 249: else if ((p = getenv("INIT")) != NULL && *p != '\0')
! 250: pathname = mkpathname(p, filename);
! 251: #endif
! 252: else
! 253: {
! 254: fprintf(stderr, "cannot find $HOME - using current directory\n");
! 255: pathname = mkpathname(".", filename);
! 256: }
! 257: return (pathname);
! 258: }
! 259:
! 260: /*
! 261: * Parse command line arguments.
! 262: */
! 263: void
! 264: parse_args(argc, argv)
! 265: int argc;
! 266: char **argv;
! 267: {
! 268: outfile = NULL;
! 269: while (--argc > 0 && **(++argv) == '-' && argv[0][1] != '\0')
! 270: {
! 271: switch (argv[0][1])
! 272: {
! 273: case 'o':
! 274: outfile = &argv[0][2];
! 275: if (*outfile == '\0')
! 276: {
! 277: if (--argc <= 0)
! 278: usage();
! 279: outfile = *(++argv);
! 280: }
! 281: break;
! 282: case 'V':
! 283: printf("lesskey version %s\n", version);
! 284: exit(0);
! 285: default:
! 286: usage();
! 287: }
! 288: }
! 289: if (argc > 1)
! 290: usage();
! 291: /*
! 292: * Open the input file, or use DEF_LESSKEYINFILE if none specified.
! 293: */
! 294: if (argc > 0)
! 295: infile = *argv;
! 296: else
! 297: infile = homefile(DEF_LESSKEYINFILE);
! 298: }
! 299:
! 300: /*
! 301: * Initialize data structures.
! 302: */
! 303: void
! 304: init_tables()
! 305: {
! 306: cmdtable.names = cmdnames;
! 307: cmdtable.pbuffer = cmdtable.buffer;
! 308:
! 309: edittable.names = editnames;
! 310: edittable.pbuffer = edittable.buffer;
! 311: }
! 312:
! 313: /*
! 314: * Parse one character of a string.
! 315: */
! 316: int
! 317: tchar(pp)
! 318: char **pp;
! 319: {
! 320: register char *p;
! 321: register char ch;
! 322: register int i;
! 323:
! 324: p = *pp;
! 325: switch (*p)
! 326: {
! 327: case '\\':
! 328: ++p;
! 329: switch (*p)
! 330: {
! 331: case '0': case '1': case '2': case '3':
! 332: case '4': case '5': case '6': case '7':
! 333: /*
! 334: * Parse an octal number.
! 335: */
! 336: ch = 0;
! 337: i = 0;
! 338: do
! 339: ch = 8*ch + (*p - '0');
! 340: while (*++p >= '0' && *p <= '7' && ++i < 3);
! 341: *pp = p;
! 342: return (ch);
! 343: case 'b':
! 344: *pp = p+1;
! 345: return ('\r');
! 346: case 'e':
! 347: *pp = p+1;
! 348: return (ESC);
! 349: case 'n':
! 350: *pp = p+1;
! 351: return ('\n');
! 352: case 'r':
! 353: *pp = p+1;
! 354: return ('\r');
! 355: case 't':
! 356: *pp = p+1;
! 357: return ('\t');
! 358: default:
! 359: /*
! 360: * Backslash followed by any other char
! 361: * just means that char.
! 362: */
! 363: *pp = p+1;
! 364: return (*p);
! 365: }
! 366: case '^':
! 367: /*
! 368: * Carat means CONTROL.
! 369: */
! 370: *pp = p+2;
! 371: return (CONTROL(p[1]));
! 372: }
! 373: *pp = p+1;
! 374: return (*p);
! 375: }
! 376:
! 377: /*
! 378: * Skip leading spaces in a string.
! 379: */
! 380: public char *
! 381: skipsp(s)
! 382: register char *s;
! 383: {
! 384: while (*s == ' ' || *s == '\t')
! 385: s++;
! 386: return (s);
! 387: }
! 388:
! 389: /*
! 390: * Skip non-space characters in a string.
! 391: */
! 392: public char *
! 393: skipnsp(s)
! 394: register char *s;
! 395: {
! 396: while (*s != '\0' && *s != ' ' && *s != '\t')
! 397: s++;
! 398: return (s);
! 399: }
! 400:
! 401: /*
! 402: * Clean up an input line:
! 403: * strip off the trailing newline & any trailing # comment.
! 404: */
! 405: char *
! 406: clean_line(s)
! 407: char *s;
! 408: {
! 409: register int i;
! 410:
! 411: s = skipsp(s);
! 412: for (i = 0; s[i] != '\n' && s[i] != '\0'; i++)
! 413: if (s[i] == '#' && (i == 0 || s[i-1] != '\\'))
! 414: break;
! 415: s[i] = '\0';
! 416: return (s);
! 417: }
! 418:
! 419: /*
! 420: * Add a byte to the output command table.
! 421: */
! 422: void
! 423: add_cmd_char(c)
! 424: int c;
! 425: {
! 426: if (currtable->pbuffer >= currtable->buffer + MAX_USERCMD)
! 427: {
! 428: error("too many commands");
! 429: exit(1);
! 430: }
! 431: *(currtable->pbuffer)++ = c;
! 432: }
! 433:
! 434: /*
! 435: * See if we have a special "control" line.
! 436: */
! 437: int
! 438: control_line(s)
! 439: char *s;
! 440: {
! 441: #define PREFIX(str,pat) (strncmp(str,pat,strlen(pat)-1) == 0)
! 442:
! 443: if (PREFIX(s, "#line-edit"))
! 444: {
! 445: currtable = &edittable;
! 446: return (1);
! 447: }
! 448: if (PREFIX(s, "#command"))
! 449: {
! 450: currtable = &cmdtable;
! 451: return (1);
! 452: }
! 453: if (PREFIX(s, "#stop"))
! 454: {
! 455: add_cmd_char('\0');
! 456: add_cmd_char(A_END_LIST);
! 457: return (1);
! 458: }
! 459: return (0);
! 460: }
! 461:
! 462: /*
! 463: * Output some bytes.
! 464: */
! 465: void
! 466: fputbytes(fd, buf, len)
! 467: FILE *fd;
! 468: char *buf;
! 469: int len;
! 470: {
! 471: while (len-- > 0)
! 472: {
! 473: fwrite(buf, sizeof(char), 1, fd);
! 474: buf++;
! 475: }
! 476: }
! 477:
! 478: /*
! 479: * Output an integer, in special KRADIX form.
! 480: */
! 481: void
! 482: fputint(fd, val)
! 483: FILE *fd;
! 484: unsigned int val;
! 485: {
! 486: char c;
! 487:
! 488: if (val >= KRADIX*KRADIX)
! 489: {
! 490: fprintf(stderr, "error: integer too big (%d > %d)\n",
! 491: val, KRADIX*KRADIX);
! 492: exit(1);
! 493: }
! 494: c = val % KRADIX;
! 495: fwrite(&c, sizeof(char), 1, fd);
! 496: c = val / KRADIX;
! 497: fwrite(&c, sizeof(char), 1, fd);
! 498: }
! 499:
! 500: /*
! 501: * Find an action, given the name of the action.
! 502: */
! 503: int
! 504: findaction(actname)
! 505: char *actname;
! 506: {
! 507: int i;
! 508:
! 509: for (i = 0; currtable->names[i].cn_name != NULL; i++)
! 510: if (strcmp(currtable->names[i].cn_name, actname) == 0)
! 511: return (currtable->names[i].cn_action);
! 512: error("unknown action");
! 513: return (A_INVALID);
! 514: }
! 515:
! 516: usage()
! 517: {
! 518: fprintf(stderr, "usage: lesskey [-o output] [input]\n");
! 519: exit(1);
! 520: }
! 521:
! 522: void
! 523: error(s)
! 524: char *s;
! 525: {
! 526: fprintf(stderr, "line %d: %s\n", linenum, s);
! 527: errors++;
! 528: }
! 529:
! 530:
! 531: /*
! 532: * Parse a line from the lesskey file.
! 533: */
! 534: void
! 535: parse_line(line)
! 536: char *line;
! 537: {
! 538: char *p;
! 539: int cmdlen;
! 540: char *actname;
! 541: int action;
! 542: int c;
! 543:
! 544: /*
! 545: * See if it is a control line.
! 546: */
! 547: if (control_line(line))
! 548: return;
! 549: /*
! 550: * Skip leading white space.
! 551: * Replace the final newline with a null byte.
! 552: * Ignore blank lines and comments.
! 553: */
! 554: p = clean_line(line);
! 555: if (*p == '\0')
! 556: return;
! 557:
! 558: /*
! 559: * Parse the command string and store it in the current table.
! 560: */
! 561: cmdlen = 0;
! 562: do
! 563: {
! 564: c = tchar(&p);
! 565: if (++cmdlen > MAX_CMDLEN)
! 566: error("command too long");
! 567: else
! 568: add_cmd_char(c);
! 569: } while (*p != ' ' && *p != '\t' && *p != '\0');
! 570: /*
! 571: * Terminate the command string with a null byte.
! 572: */
! 573: add_cmd_char('\0');
! 574:
! 575: /*
! 576: * Skip white space between the command string
! 577: * and the action name.
! 578: * Terminate the action name with a null byte.
! 579: */
! 580: p = skipsp(p);
! 581: if (*p == '\0')
! 582: {
! 583: error("missing action");
! 584: return;
! 585: }
! 586: actname = p;
! 587: p = skipnsp(p);
! 588: c = *p;
! 589: *p = '\0';
! 590:
! 591: /*
! 592: * Parse the action name and store it in the current table.
! 593: */
! 594: action = findaction(actname);
! 595:
! 596: /*
! 597: * See if an extra string follows the action name.
! 598: */
! 599: *p = c;
! 600: p = skipsp(p);
! 601: if (*p == '\0')
! 602: {
! 603: add_cmd_char(action);
! 604: } else
! 605: {
! 606: /*
! 607: * OR the special value A_EXTRA into the action byte.
! 608: * Put the extra string after the action byte.
! 609: */
! 610: add_cmd_char(action | A_EXTRA);
! 611: while (*p != '\0')
! 612: add_cmd_char(tchar(&p));
! 613: add_cmd_char('\0');
! 614: }
! 615: }
! 616:
! 617: main(argc, argv)
! 618: int argc;
! 619: char *argv[];
! 620: {
! 621: FILE *desc;
! 622: FILE *out;
! 623: char line[200];
! 624:
! 625: /*
! 626: * Process command line arguments.
! 627: */
! 628: parse_args(argc, argv);
! 629: init_tables();
! 630:
! 631: /*
! 632: * Open the input file.
! 633: */
! 634: if (strcmp(infile, "-") == 0)
! 635: desc = stdin;
! 636: else if ((desc = fopen(infile, "r")) == NULL)
! 637: {
! 638: perror(infile);
! 639: exit(1);
! 640: }
! 641:
! 642: /*
! 643: * Read and parse the input file, one line at a time.
! 644: */
! 645: errors = 0;
! 646: linenum = 0;
! 647: while (fgets(line, sizeof(line), desc) != NULL)
! 648: {
! 649: ++linenum;
! 650: parse_line(line);
! 651: }
! 652:
! 653: /*
! 654: * Write the output file.
! 655: * If no output file was specified, use "$HOME/.less"
! 656: */
! 657: if (errors > 0)
! 658: {
! 659: fprintf(stderr, "%d errors; no output produced\n", errors);
! 660: exit(1);
! 661: }
! 662:
! 663: if (outfile == NULL)
! 664: outfile = homefile(LESSKEYFILE);
! 665: if ((out = fopen(outfile, "wb")) == NULL)
! 666: {
! 667: perror(outfile);
! 668: exit(1);
! 669: }
! 670:
! 671: /* File header */
! 672: fputbytes(out, fileheader, sizeof(fileheader));
! 673:
! 674: /* Command key section */
! 675: fputbytes(out, cmdsection, sizeof(cmdsection));
! 676: fputint(out, cmdtable.pbuffer - cmdtable.buffer);
! 677: fputbytes(out, (char *)cmdtable.buffer, cmdtable.pbuffer-cmdtable.buffer);
! 678: /* Edit key section */
! 679: fputbytes(out, editsection, sizeof(editsection));
! 680: fputint(out, edittable.pbuffer - edittable.buffer);
! 681: fputbytes(out, (char *)edittable.buffer, edittable.pbuffer-edittable.buffer);
! 682:
! 683: /* File trailer */
! 684: fputbytes(out, endsection, sizeof(endsection));
! 685: fputbytes(out, filetrailer, sizeof(filetrailer));
! 686: exit(0);
! 687: }