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