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