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 * Routines to decode user commands. 30 * 31 * This is all table driven. 32 * A command table is a sequence of command descriptors. 33 * Each command descriptor is a sequence of bytes with the following format: 34 * <c1><c2>...<cN><0><action> 35 * The characters c1,c2,...,cN are the command string; that is, 36 * the characters which the user must type. 37 * It is terminated by a null <0> byte. 38 * The byte after the null byte is the action code associated 39 * with the command string. 40 * If an action byte is OR-ed with A_EXTRA, this indicates 41 * that the option byte is followed by an extra string. 42 * 43 * There may be many command tables. 44 * The first (default) table is built-in. 45 * Other tables are read in from "lesskey" files. 46 * All the tables are linked together and are searched in order. 47 */ 48 49 #include "less.h" 50 #include "cmd.h" 51 #include "lesskey.h" 52 53 extern int erase_char, kill_char; 54 55 /* 56 * Command table is ordered roughly according to expected 57 * frequency of use, so the common commands are near the beginning. 58 */ 59 static unsigned char cmdtable[] = 60 { 61 '\r',0, A_F_LINE, 62 '\n',0, A_F_LINE, 63 'e',0, A_F_LINE, 64 'j',0, A_F_LINE, 65 CONTROL('E'),0, A_F_LINE, 66 CONTROL('N'),0, A_F_LINE, 67 'k',0, A_B_LINE, 68 'y',0, A_B_LINE, 69 CONTROL('Y'),0, A_B_LINE, 70 CONTROL('K'),0, A_B_LINE, 71 CONTROL('P'),0, A_B_LINE, 72 'J',0, A_FF_LINE, 73 'K',0, A_BF_LINE, 74 'Y',0, A_BF_LINE, 75 'd',0, A_F_SCROLL, 76 CONTROL('D'),0, A_F_SCROLL, 77 'u',0, A_B_SCROLL, 78 CONTROL('U'),0, A_B_SCROLL, 79 ' ',0, A_F_SCREEN, 80 'f',0, A_F_SCREEN, 81 CONTROL('F'),0, A_F_SCREEN, 82 CONTROL('V'),0, A_F_SCREEN, 83 'b',0, A_B_SCREEN, 84 CONTROL('B'),0, A_B_SCREEN, 85 ESC,'v',0, A_B_SCREEN, 86 'z',0, A_F_WINDOW, 87 'w',0, A_B_WINDOW, 88 'F',0, A_F_FOREVER, 89 'R',0, A_FREPAINT, 90 'r',0, A_REPAINT, 91 CONTROL('R'),0, A_REPAINT, 92 CONTROL('L'),0, A_REPAINT, 93 ESC,'u',0, A_UNDO_SEARCH, 94 'g',0, A_GOLINE, 95 '<',0, A_GOLINE, 96 ESC,'<',0, A_GOLINE, 97 'p',0, A_PERCENT, 98 '%',0, A_PERCENT, 99 '{',0, A_F_BRACKET|A_EXTRA, '{','}',0, 100 '}',0, A_B_BRACKET|A_EXTRA, '{','}',0, 101 '(',0, A_F_BRACKET|A_EXTRA, '(',')',0, 102 ')',0, A_B_BRACKET|A_EXTRA, '(',')',0, 103 '[',0, A_F_BRACKET|A_EXTRA, '[',']',0, 104 ']',0, A_B_BRACKET|A_EXTRA, '[',']',0, 105 ESC,CONTROL('F'),0, A_F_BRACKET, 106 ESC,CONTROL('B'),0, A_B_BRACKET, 107 'G',0, A_GOEND, 108 ESC,'>',0, A_GOEND, 109 '>',0, A_GOEND, 110 'P',0, A_GOPOS, 111 112 '0',0, A_DIGIT, 113 '1',0, A_DIGIT, 114 '2',0, A_DIGIT, 115 '3',0, A_DIGIT, 116 '4',0, A_DIGIT, 117 '5',0, A_DIGIT, 118 '6',0, A_DIGIT, 119 '7',0, A_DIGIT, 120 '8',0, A_DIGIT, 121 '9',0, A_DIGIT, 122 123 '=',0, A_STAT, 124 CONTROL('G'),0, A_STAT, 125 ':','f',0, A_STAT, 126 '/',0, A_F_SEARCH, 127 '?',0, A_B_SEARCH, 128 ESC,'/',0, A_F_SEARCH|A_EXTRA, '*',0, 129 ESC,'?',0, A_B_SEARCH|A_EXTRA, '*',0, 130 'n',0, A_AGAIN_SEARCH, 131 ESC,'n',0, A_T_AGAIN_SEARCH, 132 'N',0, A_REVERSE_SEARCH, 133 ESC,'N',0, A_T_REVERSE_SEARCH, 134 'm',0, A_SETMARK, 135 '\'',0, A_GOMARK, 136 CONTROL('X'),CONTROL('X'),0, A_GOMARK, 137 'E',0, A_EXAMINE, 138 ':','e',0, A_EXAMINE, 139 CONTROL('X'),CONTROL('V'),0, A_EXAMINE, 140 ':','n',0, A_NEXT_FILE, 141 ':','p',0, A_PREV_FILE, 142 ':','x',0, A_INDEX_FILE, 143 '-',0, A_OPT_TOGGLE, 144 ':','t',0, A_OPT_TOGGLE|A_EXTRA, 't',0, 145 's',0, A_OPT_TOGGLE|A_EXTRA, 'o',0, 146 '_',0, A_DISP_OPTION, 147 '|',0, A_PIPE, 148 'v',0, A_VISUAL, 149 '!',0, A_SHELL, 150 '+',0, A_FIRSTCMD, 151 152 'H',0, A_HELP, 153 'h',0, A_HELP, 154 'V',0, A_VERSION, 155 'q',0, A_QUIT, 156 ':','q',0, A_QUIT, 157 ':','Q',0, A_QUIT, 158 'Z','Z',0, A_QUIT 159 }; 160 161 static unsigned char edittable[] = 162 { 163 '\t',0, EC_F_COMPLETE, /* TAB */ 164 '\17',0, EC_B_COMPLETE, /* BACKTAB */ 165 '\14',0, EC_EXPAND, /* CTRL-L */ 166 CONTROL('V'),0, EC_LITERAL, /* BACKSLASH */ 167 CONTROL('A'),0, EC_LITERAL, /* BACKSLASH */ 168 ESC,'l',0, EC_RIGHT, /* ESC l */ 169 ESC,'h',0, EC_LEFT, /* ESC h */ 170 ESC,'b',0, EC_W_LEFT, /* ESC b */ 171 ESC,'w',0, EC_W_RIGHT, /* ESC w */ 172 ESC,'i',0, EC_INSERT, /* ESC i */ 173 ESC,'x',0, EC_DELETE, /* ESC x */ 174 ESC,'X',0, EC_W_DELETE, /* ESC X */ 175 ESC,'\b',0, EC_W_BACKSPACE, /* ESC BACKSPACE */ 176 ESC,'0',0, EC_HOME, /* ESC 0 */ 177 ESC,'$',0, EC_END, /* ESC $ */ 178 ESC,'k',0, EC_UP, /* ESC k */ 179 ESC,'j',0, EC_DOWN, /* ESC j */ 180 ESC,'\t',0, EC_B_COMPLETE /* ESC TAB */ 181 }; 182 183 /* 184 * Structure to support a list of command tables. 185 */ 186 struct tablelist 187 { 188 struct tablelist *t_next; 189 char *t_start; 190 char *t_end; 191 }; 192 193 /* 194 * List of command tables and list of line-edit tables. 195 */ 196 static struct tablelist *list_fcmd_tables = NULL; 197 static struct tablelist *list_ecmd_tables = NULL; 198 199 200 /* 201 * Initialize the command lists. 202 */ 203 public void 204 init_cmds() 205 { 206 /* 207 * Add the default command tables. 208 */ 209 add_fcmd_table((char*)cmdtable, sizeof(cmdtable)); 210 add_ecmd_table((char*)edittable, sizeof(edittable)); 211 get_editkeys(); 212 #if USERFILE 213 /* 214 * Try to add the tables in the standard lesskey file "$HOME/.less". 215 */ 216 add_hometable(); 217 #endif 218 } 219 220 /* 221 * 222 */ 223 static int 224 add_cmd_table(tlist, buf, len) 225 struct tablelist **tlist; 226 char *buf; 227 int len; 228 { 229 register struct tablelist *t; 230 231 if (len == 0) 232 return (0); 233 /* 234 * Allocate a tablelist structure, initialize it, 235 * and link it into the list of tables. 236 */ 237 if ((t = (struct tablelist *) 238 calloc(1, sizeof(struct tablelist))) == NULL) 239 { 240 return (-1); 241 } 242 t->t_start = buf; 243 t->t_end = buf + len; 244 t->t_next = *tlist; 245 *tlist = t; 246 return (0); 247 } 248 249 /* 250 * Add a command table. 251 */ 252 public void 253 add_fcmd_table(buf, len) 254 char *buf; 255 int len; 256 { 257 if (add_cmd_table(&list_fcmd_tables, buf, len) < 0) 258 error("Warning: some commands disabled", NULL_PARG); 259 } 260 261 /* 262 * Add an editing command table. 263 */ 264 public void 265 add_ecmd_table(buf, len) 266 char *buf; 267 int len; 268 { 269 if (add_cmd_table(&list_ecmd_tables, buf, len) < 0) 270 error("Warning: some edit commands disabled", NULL_PARG); 271 } 272 273 /* 274 * Search a single command table for the command string in cmd. 275 */ 276 public int 277 cmd_search(cmd, table, endtable, sp) 278 char *cmd; 279 char *table; 280 char *endtable; 281 char **sp; 282 { 283 register char *p; 284 register char *q; 285 register int a; 286 287 for (p = table, q = cmd; p < endtable; p++, q++) 288 { 289 if (*p == *q) 290 { 291 /* 292 * Current characters match. 293 * If we're at the end of the string, we've found it. 294 * Return the action code, which is the character 295 * after the null at the end of the string 296 * in the command table. 297 */ 298 if (*p == '\0') 299 { 300 a = *++p & 0377; 301 if (a == A_END_LIST) 302 { 303 /* 304 * We get here only if the original 305 * cmd string passed in was empty (""). 306 * I don't think that can happen, 307 * but just in case ... 308 */ 309 return (A_UINVALID); 310 } 311 /* 312 * Check for an "extra" string. 313 */ 314 if (a & A_EXTRA) 315 { 316 *sp = ++p; 317 a &= ~A_EXTRA; 318 } else 319 *sp = NULL; 320 return (a); 321 } 322 } else if (*q == '\0') 323 { 324 /* 325 * Hit the end of the user's command, 326 * but not the end of the string in the command table. 327 * The user's command is incomplete. 328 */ 329 return (A_PREFIX); 330 } else 331 { 332 /* 333 * Not a match. 334 * Skip ahead to the next command in the 335 * command table, and reset the pointer 336 * to the beginning of the user's command. 337 */ 338 if (*p == '\0' && p[1] == A_END_LIST) 339 { 340 /* 341 * A_END_LIST is a special marker that tells 342 * us to abort the cmd search. 343 */ 344 return (A_UINVALID); 345 } 346 while (*p++ != '\0') ; 347 if (*p & A_EXTRA) 348 while (*++p != '\0') ; 349 q = cmd-1; 350 } 351 } 352 /* 353 * No match found in the entire command table. 354 */ 355 return (A_INVALID); 356 } 357 358 /* 359 * Decode a command character and return the associated action. 360 * The "extra" string, if any, is returned in sp. 361 */ 362 static int 363 cmd_decode(tlist, cmd, sp) 364 struct tablelist *tlist; 365 char *cmd; 366 char **sp; 367 { 368 register struct tablelist *t; 369 register int action = A_INVALID; 370 371 /* 372 * Search thru all the command tables. 373 * Stop when we find an action which is not A_INVALID. 374 */ 375 for (t = tlist; t != NULL; t = t->t_next) 376 { 377 action = cmd_search(cmd, t->t_start, t->t_end, sp); 378 if (action != A_INVALID) 379 break; 380 } 381 return (action); 382 } 383 384 /* 385 * Decode a command from the cmdtables list. 386 */ 387 public int 388 fcmd_decode(cmd, sp) 389 char *cmd; 390 char **sp; 391 { 392 return (cmd_decode(list_fcmd_tables, cmd, sp)); 393 } 394 395 /* 396 * Decode a command from the edittables list. 397 */ 398 public int 399 ecmd_decode(cmd, sp) 400 char *cmd; 401 char **sp; 402 { 403 return (cmd_decode(list_ecmd_tables, cmd, sp)); 404 } 405 406 #if USERFILE 407 static int 408 gint(sp) 409 char **sp; 410 { 411 int n; 412 413 n = *(*sp)++; 414 n += *(*sp)++ * KRADIX; 415 return (n); 416 } 417 418 static int 419 old_lesskey(buf, len) 420 char *buf; 421 int len; 422 { 423 /* 424 * Old-style lesskey file. 425 * The file must end with either 426 * ...,cmd,0,action 427 * or ...,cmd,0,action|A_EXTRA,string,0 428 * So the last byte or the second to last byte must be zero. 429 */ 430 if (buf[len-1] != '\0' && buf[len-2] != '\0') 431 return (-1); 432 add_fcmd_table(buf, len); 433 return (0); 434 } 435 436 static int 437 new_lesskey(buf, len) 438 char *buf; 439 int len; 440 { 441 char *p; 442 register int c; 443 register int done; 444 register int n; 445 446 /* 447 * New-style lesskey file. 448 * Extract the pieces. 449 */ 450 if (buf[len-3] != C0_END_LESSKEY_MAGIC || 451 buf[len-2] != C1_END_LESSKEY_MAGIC || 452 buf[len-1] != C2_END_LESSKEY_MAGIC) 453 return (-1); 454 p = buf + 4; 455 done = 0; 456 while (!done) 457 { 458 c = *p++; 459 switch (c) 460 { 461 case CMD_SECTION: 462 n = gint(&p); 463 add_fcmd_table(p, n); 464 p += n; 465 break; 466 case EDIT_SECTION: 467 n = gint(&p); 468 add_ecmd_table(p, n); 469 p += n; 470 break; 471 case END_SECTION: 472 done = 1; 473 break; 474 default: 475 free(buf); 476 return (-1); 477 } 478 } 479 return (0); 480 } 481 482 /* 483 * Set up a user command table, based on a "lesskey" file. 484 */ 485 public int 486 lesskey(filename) 487 char *filename; 488 { 489 register char *buf; 490 register POSITION len; 491 register long n; 492 register int f; 493 494 /* 495 * Try to open the lesskey file. 496 */ 497 f = open(filename, OPEN_READ); 498 if (f < 0) 499 return (1); 500 501 /* 502 * Read the file into a buffer. 503 * We first figure out the size of the file and allocate space for it. 504 * {{ Minimal error checking is done here. 505 * A garbage .less file will produce strange results. 506 * To avoid a large amount of error checking code here, we 507 * rely on the lesskey program to generate a good .less file. }} 508 */ 509 len = filesize(f); 510 if (len == NULL_POSITION || len < 3) 511 { 512 /* 513 * Bad file (valid file must have at least 3 chars). 514 */ 515 close(f); 516 return (-1); 517 } 518 if ((buf = (char *) calloc((int)len, sizeof(char))) == NULL) 519 { 520 close(f); 521 return (-1); 522 } 523 if (lseek(f, (off_t)0, 0) == BAD_LSEEK) 524 { 525 free(buf); 526 close(f); 527 return (-1); 528 } 529 n = read(f, buf, (unsigned int) len); 530 close(f); 531 if (n != len) 532 { 533 free(buf); 534 return (-1); 535 } 536 537 /* 538 * Figure out if this is an old-style (before version 241) 539 * or new-style lesskey file format. 540 */ 541 if (buf[0] != C0_LESSKEY_MAGIC || buf[1] != C1_LESSKEY_MAGIC || 542 buf[2] != C2_LESSKEY_MAGIC || buf[3] != C3_LESSKEY_MAGIC) 543 return (old_lesskey(buf, (int)len)); 544 return (new_lesskey(buf, (int)len)); 545 } 546 547 /* 548 * Add the standard lesskey file "$HOME/.less" 549 */ 550 public void 551 add_hometable() 552 { 553 char *filename; 554 PARG parg; 555 556 filename = homefile(LESSKEYFILE); 557 if (filename == NULL) 558 return; 559 if (lesskey(filename) < 0) 560 { 561 parg.p_string = filename; 562 error("Cannot use lesskey file \"%s\"", &parg); 563 } 564 free(filename); 565 } 566 #endif 567 568 /* 569 * See if a char is a special line-editing command. 570 */ 571 public int 572 editchar(c, flags) 573 int c; 574 int flags; 575 { 576 int action; 577 int nch; 578 char *s; 579 char usercmd[MAX_CMDLEN+1]; 580 581 /* 582 * An editing character could actually be a sequence of characters; 583 * for example, an escape sequence sent by pressing the uparrow key. 584 * To match the editing string, we use the command decoder 585 * but give it the edit-commands command table 586 * This table is constructed to match the user's keyboard. 587 */ 588 if (c == erase_char) 589 return (EC_BACKSPACE); 590 if (c == kill_char) 591 return (EC_LINEKILL); 592 593 /* 594 * Collect characters in a buffer. 595 * Start with the one we have, and get more if we need them. 596 */ 597 nch = 0; 598 do { 599 if (nch > 0) 600 c = getcc(); 601 usercmd[nch] = c; 602 usercmd[nch+1] = '\0'; 603 nch++; 604 action = ecmd_decode(usercmd, &s); 605 } while (action == A_PREFIX); 606 607 if (flags & EC_NOHISTORY) 608 { 609 /* 610 * The caller says there is no history list. 611 * Reject any history-manipulation action. 612 */ 613 switch (action) 614 { 615 case EC_UP: 616 case EC_DOWN: 617 action = A_INVALID; 618 break; 619 } 620 } 621 if (flags & EC_NOCOMPLETE) 622 { 623 /* 624 * The caller says we don't want any filename completion cmds. 625 * Reject them. 626 */ 627 switch (action) 628 { 629 case EC_F_COMPLETE: 630 case EC_B_COMPLETE: 631 case EC_EXPAND: 632 action = A_INVALID; 633 break; 634 } 635 } 636 if ((flags & EC_PEEK) || action == A_INVALID) 637 { 638 /* 639 * We're just peeking, or we didn't understand the command. 640 * Unget all the characters we read in the loop above. 641 * This does NOT include the original character that was 642 * passed in as a parameter. 643 */ 644 while (nch > 1) { 645 ungetcc(usercmd[--nch]); 646 } 647 } else 648 { 649 if (s != NULL) 650 ungetsc(s); 651 } 652 return action; 653 } 654 655