1 /* 2 * Copyright (C) 1984-2002 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information about less, or for information on how to 8 * contact the author, see the README file. 9 */ 10 11 12 /* 13 * lesskey [-o output] [input] 14 * 15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 16 * 17 * Make a .less file. 18 * If no input file is specified, standard input is used. 19 * If no output file is specified, $HOME/.less is used. 20 * 21 * The .less file is used to specify (to "less") user-defined 22 * key bindings. Basically any sequence of 1 to MAX_CMDLEN 23 * keystrokes may be bound to an existing less function. 24 * 25 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 26 * 27 * The input file is an ascii file consisting of a 28 * sequence of lines of the form: 29 * string <whitespace> action [chars] <newline> 30 * 31 * "string" is a sequence of command characters which form 32 * the new user-defined command. The command 33 * characters may be: 34 * 1. The actual character itself. 35 * 2. A character preceded by ^ to specify a 36 * control character (e.g. ^X means control-X). 37 * 3. A backslash followed by one to three octal digits 38 * to specify a character by its octal value. 39 * 4. A backslash followed by b, e, n, r or t 40 * to specify \b, ESC, \n, \r or \t, respectively. 41 * 5. Any character (other than those mentioned above) preceded 42 * by a \ to specify the character itself (characters which 43 * must be preceded by \ include ^, \, and whitespace. 44 * "action" is the name of a "less" action, from the table below. 45 * "chars" is an optional sequence of characters which is treated 46 * as keyboard input after the command is executed. 47 * 48 * Blank lines and lines which start with # are ignored, 49 * except for the special control lines: 50 * #command Signals the beginning of the command 51 * keys section. 52 * #line-edit Signals the beginning of the line-editing 53 * keys section. 54 * #env Signals the beginning of the environment 55 * variable section. 56 * #stop Stops command parsing in less; 57 * causes all default keys to be disabled. 58 * 59 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 60 * 61 * The output file is a non-ascii file, consisting of a header, 62 * one or more sections, and a trailer. 63 * Each section begins with a section header, a section length word 64 * and the section data. Normally there are three sections: 65 * CMD_SECTION Definition of command keys. 66 * EDIT_SECTION Definition of editing keys. 67 * END_SECTION A special section header, with no 68 * length word or section data. 69 * 70 * Section data consists of zero or more byte sequences of the form: 71 * string <0> <action> 72 * or 73 * string <0> <action|A_EXTRA> chars <0> 74 * 75 * "string" is the command string. 76 * "<0>" is one null byte. 77 * "<action>" is one byte containing the action code (the A_xxx value). 78 * If action is ORed with A_EXTRA, the action byte is followed 79 * by the null-terminated "chars" string. 80 * 81 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 82 */ 83 84 #include "less.h" 85 #include "lesskey.h" 86 #include "cmd.h" 87 88 struct cmdname 89 { 90 char *cn_name; 91 int cn_action; 92 }; 93 94 struct cmdname cmdnames[] = 95 { 96 { "back-bracket", A_B_BRACKET }, 97 { "back-line", A_B_LINE }, 98 { "back-line-force", A_BF_LINE }, 99 { "back-screen", A_B_SCREEN }, 100 { "back-scroll", A_B_SCROLL }, 101 { "back-search", A_B_SEARCH }, 102 { "back-window", A_B_WINDOW }, 103 { "debug", A_DEBUG }, 104 { "digit", A_DIGIT }, 105 { "display-flag", A_DISP_OPTION }, 106 { "display-option", A_DISP_OPTION }, 107 { "end", A_GOEND }, 108 { "examine", A_EXAMINE }, 109 { "first-cmd", A_FIRSTCMD }, 110 { "firstcmd", A_FIRSTCMD }, 111 { "flush-repaint", A_FREPAINT }, 112 { "forw-bracket", A_F_BRACKET }, 113 { "forw-forever", A_F_FOREVER }, 114 { "forw-line", A_F_LINE }, 115 { "forw-line-force", A_FF_LINE }, 116 { "forw-screen", A_F_SCREEN }, 117 { "forw-screen-force", A_FF_SCREEN }, 118 { "forw-scroll", A_F_SCROLL }, 119 { "forw-search", A_F_SEARCH }, 120 { "forw-window", A_F_WINDOW }, 121 { "goto-end", A_GOEND }, 122 { "goto-line", A_GOLINE }, 123 { "goto-mark", A_GOMARK }, 124 { "help", A_HELP }, 125 { "index-file", A_INDEX_FILE }, 126 { "invalid", A_UINVALID }, 127 { "left-scroll", A_LSHIFT }, 128 { "next-file", A_NEXT_FILE }, 129 { "next-tag", A_NEXT_TAG }, 130 { "noaction", A_NOACTION }, 131 { "percent", A_PERCENT }, 132 { "pipe", A_PIPE }, 133 { "prev-file", A_PREV_FILE }, 134 { "prev-tag", A_PREV_TAG }, 135 { "quit", A_QUIT }, 136 { "remove-file", A_REMOVE_FILE }, 137 { "repaint", A_REPAINT }, 138 { "repaint-flush", A_FREPAINT }, 139 { "repeat-search", A_AGAIN_SEARCH }, 140 { "repeat-search-all", A_T_AGAIN_SEARCH }, 141 { "reverse-search", A_REVERSE_SEARCH }, 142 { "reverse-search-all", A_T_REVERSE_SEARCH }, 143 { "right-scroll", A_RSHIFT }, 144 { "set-mark", A_SETMARK }, 145 { "shell", A_SHELL }, 146 { "status", A_STAT }, 147 { "toggle-flag", A_OPT_TOGGLE }, 148 { "toggle-option", A_OPT_TOGGLE }, 149 { "undo-hilite", A_UNDO_SEARCH }, 150 { "version", A_VERSION }, 151 { "visual", A_VISUAL }, 152 { NULL, 0 } 153 }; 154 155 struct cmdname editnames[] = 156 { 157 { "back-complete", EC_B_COMPLETE }, 158 { "backspace", EC_BACKSPACE }, 159 { "delete", EC_DELETE }, 160 { "down", EC_DOWN }, 161 { "end", EC_END }, 162 { "expand", EC_EXPAND }, 163 { "forw-complete", EC_F_COMPLETE }, 164 { "home", EC_HOME }, 165 { "insert", EC_INSERT }, 166 { "invalid", EC_UINVALID }, 167 { "kill-line", EC_LINEKILL }, 168 { "left", EC_LEFT }, 169 { "literal", EC_LITERAL }, 170 { "right", EC_RIGHT }, 171 { "up", EC_UP }, 172 { "word-backspace", EC_W_BACKSPACE }, 173 { "word-delete", EC_W_DELETE }, 174 { "word-left", EC_W_LEFT }, 175 { "word-right", EC_W_RIGHT }, 176 { NULL, 0 } 177 }; 178 179 struct table 180 { 181 struct cmdname *names; 182 char *pbuffer; 183 char buffer[MAX_USERCMD]; 184 }; 185 186 struct table cmdtable; 187 struct table edittable; 188 struct table vartable; 189 struct table *currtable = &cmdtable; 190 191 char fileheader[] = { 192 C0_LESSKEY_MAGIC, 193 C1_LESSKEY_MAGIC, 194 C2_LESSKEY_MAGIC, 195 C3_LESSKEY_MAGIC 196 }; 197 char filetrailer[] = { 198 C0_END_LESSKEY_MAGIC, 199 C1_END_LESSKEY_MAGIC, 200 C2_END_LESSKEY_MAGIC 201 }; 202 char cmdsection[1] = { CMD_SECTION }; 203 char editsection[1] = { EDIT_SECTION }; 204 char varsection[1] = { VAR_SECTION }; 205 char endsection[1] = { END_SECTION }; 206 207 char *infile = NULL; 208 char *outfile = NULL ; 209 210 int linenum; 211 int errors; 212 213 extern char version[]; 214 215 void 216 usage() 217 { 218 fprintf(stderr, "usage: lesskey [-o output] [input]\n"); 219 exit(1); 220 } 221 222 char * 223 mkpathname(dirname, filename) 224 char *dirname; 225 char *filename; 226 { 227 char *pathname; 228 229 pathname = calloc(strlen(dirname) + strlen(filename) + 2, sizeof(char)); 230 strcpy(pathname, dirname); 231 strcat(pathname, PATHNAME_SEP); 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 char *arg; 269 270 outfile = NULL; 271 while (--argc > 0) 272 { 273 arg = *++argv; 274 if (arg[0] != '-') 275 /* Arg does not start with "-"; it's not an option. */ 276 break; 277 if (arg[1] == '\0') 278 /* "-" means standard input. */ 279 break; 280 if (arg[1] == '-' && arg[2] == '\0') 281 { 282 /* "--" means end of options. */ 283 argc--; 284 argv++; 285 break; 286 } 287 switch (arg[1]) 288 { 289 case '-': 290 if (strncmp(arg, "--output", 8) == 0) 291 { 292 if (arg[8] == '\0') 293 outfile = &arg[8]; 294 else if (arg[8] == '=') 295 outfile = &arg[9]; 296 else 297 usage(); 298 goto opt_o; 299 } 300 if (strcmp(arg, "--version") == 0) 301 { 302 goto opt_V; 303 } 304 usage(); 305 break; 306 case 'o': 307 outfile = &argv[0][2]; 308 opt_o: 309 if (*outfile == '\0') 310 { 311 if (--argc <= 0) 312 usage(); 313 outfile = *(++argv); 314 } 315 break; 316 case 'V': 317 opt_V: 318 printf("lesskey version %s\n", version); 319 exit(0); 320 default: 321 usage(); 322 } 323 } 324 if (argc > 1) 325 usage(); 326 /* 327 * Open the input file, or use DEF_LESSKEYINFILE if none specified. 328 */ 329 if (argc > 0) 330 infile = *argv; 331 else 332 infile = homefile(DEF_LESSKEYINFILE); 333 } 334 335 /* 336 * Initialize data structures. 337 */ 338 void 339 init_tables() 340 { 341 cmdtable.names = cmdnames; 342 cmdtable.pbuffer = cmdtable.buffer; 343 344 edittable.names = editnames; 345 edittable.pbuffer = edittable.buffer; 346 347 vartable.names = NULL; 348 vartable.pbuffer = vartable.buffer; 349 } 350 351 /* 352 * Parse one character of a string. 353 */ 354 char * 355 tstr(pp, xlate) 356 char **pp; 357 int xlate; 358 { 359 register char *p; 360 register char ch; 361 register int i; 362 static char buf[10]; 363 static char tstr_control_k[] = 364 { SK_SPECIAL_KEY, SK_CONTROL_K, 6, 1, 1, 1, '\0' }; 365 366 p = *pp; 367 switch (*p) 368 { 369 case '\\': 370 ++p; 371 switch (*p) 372 { 373 case '0': case '1': case '2': case '3': 374 case '4': case '5': case '6': case '7': 375 /* 376 * Parse an octal number. 377 */ 378 ch = 0; 379 i = 0; 380 do 381 ch = 8*ch + (*p - '0'); 382 while (*++p >= '0' && *p <= '7' && ++i < 3); 383 *pp = p; 384 if (xlate && ch == CONTROL('K')) 385 return tstr_control_k; 386 buf[0] = ch; 387 buf[1] = '\0'; 388 return (buf); 389 case 'b': 390 *pp = p+1; 391 return ("\b"); 392 case 'e': 393 *pp = p+1; 394 buf[0] = ESC; 395 buf[1] = '\0'; 396 return (buf); 397 case 'n': 398 *pp = p+1; 399 return ("\n"); 400 case 'r': 401 *pp = p+1; 402 return ("\r"); 403 case 't': 404 *pp = p+1; 405 return ("\t"); 406 case 'k': 407 if (xlate) 408 { 409 switch (*++p) 410 { 411 case 'u': ch = SK_UP_ARROW; break; 412 case 'd': ch = SK_DOWN_ARROW; break; 413 case 'r': ch = SK_RIGHT_ARROW; break; 414 case 'l': ch = SK_LEFT_ARROW; break; 415 case 'U': ch = SK_PAGE_UP; break; 416 case 'D': ch = SK_PAGE_DOWN; break; 417 case 'h': ch = SK_HOME; break; 418 case 'e': ch = SK_END; break; 419 case 'x': ch = SK_DELETE; break; 420 default: 421 error("illegal char after \\k"); 422 *pp = p+1; 423 return (""); 424 } 425 *pp = p+1; 426 buf[0] = SK_SPECIAL_KEY; 427 buf[1] = ch; 428 buf[2] = 6; 429 buf[3] = 1; 430 buf[4] = 1; 431 buf[5] = 1; 432 buf[6] = '\0'; 433 return (buf); 434 } 435 /* FALLTHRU */ 436 default: 437 /* 438 * Backslash followed by any other char 439 * just means that char. 440 */ 441 *pp = p+1; 442 buf[0] = *p; 443 buf[1] = '\0'; 444 if (xlate && buf[0] == CONTROL('K')) 445 return tstr_control_k; 446 return (buf); 447 } 448 case '^': 449 /* 450 * Carat means CONTROL. 451 */ 452 *pp = p+2; 453 buf[0] = CONTROL(p[1]); 454 buf[1] = '\0'; 455 if (buf[0] == CONTROL('K')) 456 return tstr_control_k; 457 return (buf); 458 } 459 *pp = p+1; 460 buf[0] = *p; 461 buf[1] = '\0'; 462 if (xlate && buf[0] == CONTROL('K')) 463 return tstr_control_k; 464 return (buf); 465 } 466 467 /* 468 * Skip leading spaces in a string. 469 */ 470 public char * 471 skipsp(s) 472 register char *s; 473 { 474 while (*s == ' ' || *s == '\t') 475 s++; 476 return (s); 477 } 478 479 /* 480 * Skip non-space characters in a string. 481 */ 482 public char * 483 skipnsp(s) 484 register char *s; 485 { 486 while (*s != '\0' && *s != ' ' && *s != '\t') 487 s++; 488 return (s); 489 } 490 491 /* 492 * Clean up an input line: 493 * strip off the trailing newline & any trailing # comment. 494 */ 495 char * 496 clean_line(s) 497 char *s; 498 { 499 register int i; 500 501 s = skipsp(s); 502 for (i = 0; s[i] != '\n' && s[i] != '\r' && s[i] != '\0'; i++) 503 if (s[i] == '#' && (i == 0 || s[i-1] != '\\')) 504 break; 505 s[i] = '\0'; 506 return (s); 507 } 508 509 /* 510 * Add a byte to the output command table. 511 */ 512 void 513 add_cmd_char(c) 514 int c; 515 { 516 if (currtable->pbuffer >= currtable->buffer + MAX_USERCMD) 517 { 518 error("too many commands"); 519 exit(1); 520 } 521 *(currtable->pbuffer)++ = c; 522 } 523 524 /* 525 * Add a string to the output command table. 526 */ 527 void 528 add_cmd_str(s) 529 char *s; 530 { 531 for ( ; *s != '\0'; s++) 532 add_cmd_char(*s); 533 } 534 535 /* 536 * See if we have a special "control" line. 537 */ 538 int 539 control_line(s) 540 char *s; 541 { 542 #define PREFIX(str,pat) (strncmp(str,pat,strlen(pat)-1) == 0) 543 544 if (PREFIX(s, "#line-edit")) 545 { 546 currtable = &edittable; 547 return (1); 548 } 549 if (PREFIX(s, "#command")) 550 { 551 currtable = &cmdtable; 552 return (1); 553 } 554 if (PREFIX(s, "#env")) 555 { 556 currtable = &vartable; 557 return (1); 558 } 559 if (PREFIX(s, "#stop")) 560 { 561 add_cmd_char('\0'); 562 add_cmd_char(A_END_LIST); 563 return (1); 564 } 565 return (0); 566 } 567 568 /* 569 * Output some bytes. 570 */ 571 void 572 fputbytes(fd, buf, len) 573 FILE *fd; 574 char *buf; 575 int len; 576 { 577 while (len-- > 0) 578 { 579 fwrite(buf, sizeof(char), 1, fd); 580 buf++; 581 } 582 } 583 584 /* 585 * Output an integer, in special KRADIX form. 586 */ 587 void 588 fputint(fd, val) 589 FILE *fd; 590 unsigned int val; 591 { 592 char c; 593 594 if (val >= KRADIX*KRADIX) 595 { 596 fprintf(stderr, "error: integer too big (%d > %d)\n", 597 val, KRADIX*KRADIX); 598 exit(1); 599 } 600 c = val % KRADIX; 601 fwrite(&c, sizeof(char), 1, fd); 602 c = val / KRADIX; 603 fwrite(&c, sizeof(char), 1, fd); 604 } 605 606 /* 607 * Find an action, given the name of the action. 608 */ 609 int 610 findaction(actname) 611 char *actname; 612 { 613 int i; 614 615 for (i = 0; currtable->names[i].cn_name != NULL; i++) 616 if (strcmp(currtable->names[i].cn_name, actname) == 0) 617 return (currtable->names[i].cn_action); 618 error("unknown action"); 619 return (A_INVALID); 620 } 621 622 void 623 error(s) 624 char *s; 625 { 626 fprintf(stderr, "line %d: %s\n", linenum, s); 627 errors++; 628 } 629 630 631 void 632 parse_cmdline(p) 633 char *p; 634 { 635 int cmdlen; 636 char *actname; 637 int action; 638 char *s; 639 char c; 640 641 /* 642 * Parse the command string and store it in the current table. 643 */ 644 cmdlen = 0; 645 do 646 { 647 s = tstr(&p, 1); 648 cmdlen += strlen(s); 649 if (cmdlen > MAX_CMDLEN) 650 error("command too long"); 651 else 652 add_cmd_str(s); 653 } while (*p != ' ' && *p != '\t' && *p != '\0'); 654 /* 655 * Terminate the command string with a null byte. 656 */ 657 add_cmd_char('\0'); 658 659 /* 660 * Skip white space between the command string 661 * and the action name. 662 * Terminate the action name with a null byte. 663 */ 664 p = skipsp(p); 665 if (*p == '\0') 666 { 667 error("missing action"); 668 return; 669 } 670 actname = p; 671 p = skipnsp(p); 672 c = *p; 673 *p = '\0'; 674 675 /* 676 * Parse the action name and store it in the current table. 677 */ 678 action = findaction(actname); 679 680 /* 681 * See if an extra string follows the action name. 682 */ 683 *p = c; 684 p = skipsp(p); 685 if (*p == '\0') 686 { 687 add_cmd_char(action); 688 } else 689 { 690 /* 691 * OR the special value A_EXTRA into the action byte. 692 * Put the extra string after the action byte. 693 */ 694 add_cmd_char(action | A_EXTRA); 695 while (*p != '\0') 696 add_cmd_str(tstr(&p, 0)); 697 add_cmd_char('\0'); 698 } 699 } 700 701 void 702 parse_varline(p) 703 char *p; 704 { 705 char *s; 706 707 do 708 { 709 s = tstr(&p, 0); 710 add_cmd_str(s); 711 } while (*p != ' ' && *p != '\t' && *p != '=' && *p != '\0'); 712 /* 713 * Terminate the variable name with a null byte. 714 */ 715 add_cmd_char('\0'); 716 717 p = skipsp(p); 718 if (*p++ != '=') 719 { 720 error("missing ="); 721 return; 722 } 723 724 add_cmd_char(EV_OK|A_EXTRA); 725 726 p = skipsp(p); 727 while (*p != '\0') 728 { 729 s = tstr(&p, 0); 730 add_cmd_str(s); 731 } 732 add_cmd_char('\0'); 733 } 734 735 /* 736 * Parse a line from the lesskey file. 737 */ 738 void 739 parse_line(line) 740 char *line; 741 { 742 char *p; 743 744 /* 745 * See if it is a control line. 746 */ 747 if (control_line(line)) 748 return; 749 /* 750 * Skip leading white space. 751 * Replace the final newline with a null byte. 752 * Ignore blank lines and comments. 753 */ 754 p = clean_line(line); 755 if (*p == '\0') 756 return; 757 758 if (currtable == &vartable) 759 parse_varline(p); 760 else 761 parse_cmdline(p); 762 } 763 764 int 765 main(argc, argv) 766 int argc; 767 char *argv[]; 768 { 769 FILE *desc; 770 FILE *out; 771 char line[1024]; 772 773 #ifdef WIN32 774 if (getenv("HOME") == NULL) 775 { 776 /* 777 * If there is no HOME environment variable, 778 * try the concatenation of HOMEDRIVE + HOMEPATH. 779 */ 780 char *drive = getenv("HOMEDRIVE"); 781 char *path = getenv("HOMEPATH"); 782 if (drive != NULL && path != NULL) 783 { 784 char *env = (char *) calloc(strlen(drive) + 785 strlen(path) + 6, sizeof(char)); 786 strcpy(env, "HOME="); 787 strcat(env, drive); 788 strcat(env, path); 789 putenv(env); 790 } 791 } 792 #endif /* WIN32 */ 793 794 /* 795 * Process command line arguments. 796 */ 797 parse_args(argc, argv); 798 init_tables(); 799 800 /* 801 * Open the input file. 802 */ 803 if (strcmp(infile, "-") == 0) 804 desc = stdin; 805 else if ((desc = fopen(infile, "r")) == NULL) 806 { 807 #if HAVE_PERROR 808 perror(infile); 809 #else 810 fprintf(stderr, "Cannot open %s\n", infile); 811 #endif 812 usage(); 813 } 814 815 /* 816 * Read and parse the input file, one line at a time. 817 */ 818 errors = 0; 819 linenum = 0; 820 while (fgets(line, sizeof(line), desc) != NULL) 821 { 822 ++linenum; 823 parse_line(line); 824 } 825 826 /* 827 * Write the output file. 828 * If no output file was specified, use "$HOME/.less" 829 */ 830 if (errors > 0) 831 { 832 fprintf(stderr, "%d errors; no output produced\n", errors); 833 exit(1); 834 } 835 836 if (outfile == NULL) 837 outfile = getenv("LESSKEY"); 838 if (outfile == NULL) 839 outfile = homefile(LESSKEYFILE); 840 if ((out = fopen(outfile, "wb")) == NULL) 841 { 842 #if HAVE_PERROR 843 perror(outfile); 844 #else 845 fprintf(stderr, "Cannot open %s\n", outfile); 846 #endif 847 exit(1); 848 } 849 850 /* File header */ 851 fputbytes(out, fileheader, sizeof(fileheader)); 852 853 /* Command key section */ 854 fputbytes(out, cmdsection, sizeof(cmdsection)); 855 fputint(out, cmdtable.pbuffer - cmdtable.buffer); 856 fputbytes(out, (char *)cmdtable.buffer, cmdtable.pbuffer-cmdtable.buffer); 857 /* Edit key section */ 858 fputbytes(out, editsection, sizeof(editsection)); 859 fputint(out, edittable.pbuffer - edittable.buffer); 860 fputbytes(out, (char *)edittable.buffer, edittable.pbuffer-edittable.buffer); 861 862 /* Environment variable section */ 863 fputbytes(out, varsection, sizeof(varsection)); 864 fputint(out, vartable.pbuffer - vartable.buffer); 865 fputbytes(out, (char *)vartable.buffer, vartable.pbuffer-vartable.buffer); 866 867 /* File trailer */ 868 fputbytes(out, endsection, sizeof(endsection)); 869 fputbytes(out, filetrailer, sizeof(filetrailer)); 870 return (0); 871 } 872