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