1 /* 2 * Copyright (C) 1984-2026 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 * Process command line options. 13 * 14 * Each option is a single letter which controls a program variable. 15 * The options have defaults which may be changed via 16 * the command line option, toggled via the "-" command, 17 * or queried via the "_" command. 18 */ 19 20 #include "less.h" 21 #include "option.h" 22 23 static struct loption *pendopt; 24 public lbool plusoption = FALSE; 25 26 static constant char *optstring(constant char *s, char **p_str, constant char *printopt, constant char *validchars); 27 static int flip_triple(int val, lbool lc); 28 29 extern int less_is_more; 30 extern int quit_at_eof; 31 extern char *every_first_cmd; 32 extern int opt_use_backslash; 33 extern int ctldisp; 34 35 /* 36 * Return a printable description of an option. 37 */ 38 static constant char * opt_desc(struct loption *o) 39 { 40 static char buf[OPTNAME_MAX + 10]; 41 if (o->oletter == OLETTER_NONE) 42 SNPRINTF1(buf, sizeof(buf), "--%s", o->onames->oname); 43 else 44 SNPRINTF2(buf, sizeof(buf), "-%c (--%s)", o->oletter, o->onames->oname); 45 return (buf); 46 } 47 48 /* 49 * Return a string suitable for printing as the "name" of an option. 50 * For example, if the option letter is 'x', just return "-x". 51 */ 52 public constant char * propt(char c) 53 { 54 static char buf[MAX_PRCHAR_LEN+2]; 55 56 sprintf(buf, "-%s", prchar((LWCHAR) c)); 57 return (buf); 58 } 59 60 /* 61 * Scan an argument (either from the command line or from the 62 * LESS environment variable) and process it. 63 */ 64 public void scan_option(constant char *s, lbool is_env) 65 { 66 struct loption *o; 67 char optc; 68 constant char *optname; 69 constant char *printopt; 70 char *str; 71 lbool set_default; 72 lbool lc; 73 PARG parg; 74 75 if (s == NULL) 76 return; 77 78 /* 79 * If we have a pending option which requires an argument, 80 * handle it now. 81 * This happens if the previous option was, for example, "-P" 82 * without a following string. In that case, the current 83 * option is simply the argument for the previous option. 84 */ 85 if (pendopt != NULL) 86 { 87 if (!(pendopt->otype & O_UNSUPPORTED)) 88 { 89 switch (pendopt->otype & OTYPE) 90 { 91 case O_STRING: 92 (*pendopt->ofunc)(INIT, s); 93 break; 94 case O_NUMBER: 95 printopt = opt_desc(pendopt); 96 getnumc(&s, printopt, (pendopt->otype & O_NEGOK) != 0, pendopt->ovar); 97 break; 98 } 99 } 100 pendopt = NULL; 101 return; 102 } 103 104 set_default = FALSE; 105 optname = NULL; 106 107 while (*s != '\0') 108 { 109 /* 110 * Check some special cases first. 111 */ 112 switch (optc = *s++) 113 { 114 case ' ': 115 case '\t': 116 case END_OPTION_STRING: 117 continue; 118 case '-': 119 /* 120 * "--" indicates an option name instead of a letter. 121 */ 122 if (*s == '-') 123 optname = ++s; 124 /* 125 * "-+" or "--+" means set these options back to their defaults. 126 * (They may have been set otherwise by previous options.) 127 */ 128 set_default = (*s == '+'); 129 if (set_default) 130 s++; 131 if (optname != NULL) 132 { 133 optname = s; 134 break; 135 } 136 continue; 137 case '+': 138 /* 139 * An option prefixed by a "+" is ungotten, so 140 * that it is interpreted as less commands 141 * processed at the start of the first input file. 142 * "++" means process the commands at the start of 143 * EVERY input file. 144 */ 145 plusoption = TRUE; 146 s = optstring(s, &str, propt('+'), NULL); 147 if (s == NULL) 148 return; 149 if (*str == '+') 150 { 151 if (every_first_cmd != NULL) 152 free(every_first_cmd); 153 every_first_cmd = save(str+1); 154 } else 155 { 156 ungetsc(str); 157 ungetcc_end_command(); 158 } 159 free(str); 160 continue; 161 case '0': case '1': case '2': case '3': case '4': 162 case '5': case '6': case '7': case '8': case '9': 163 /* 164 * Special "more" compatibility form "-<number>" 165 * instead of -z<number> to set the scrolling 166 * window size. 167 */ 168 s--; 169 optc = 'z'; 170 break; 171 case 'n': 172 if (less_is_more) 173 optc = 'z'; 174 break; 175 } 176 177 /* 178 * Not a special case. 179 * Look up the option letter in the option table. 180 */ 181 if (optname == NULL) 182 { 183 printopt = propt(optc); 184 lc = ASCII_IS_LOWER(optc); 185 o = findopt(optc); 186 if (o == NULL) 187 { 188 parg.p_string = printopt; 189 error("There is no %s option (\"less --help\" for help)", &parg); 190 return; 191 } 192 } else 193 { 194 lbool ambig = FALSE; 195 printopt = optname; 196 lc = ASCII_IS_LOWER(optname[0]); 197 o = findopt_name(&optname, NULL, &ambig); 198 s = optname; 199 optname = NULL; 200 if (*s == '\0' || *s == ' ') 201 { 202 /* 203 * The option name matches exactly. 204 */ 205 ; 206 } else if (*s == '=') 207 { 208 /* 209 * The option name is followed by "=value". 210 */ 211 if (o != NULL && 212 (o->otype & OTYPE) != O_STRING && 213 (o->otype & OTYPE) != O_NUMBER) 214 { 215 parg.p_string = printopt; 216 error("The --%s option should not be followed by =", 217 &parg); 218 return; 219 } 220 s++; 221 } else 222 { 223 /* 224 * The specified name is longer than the 225 * real option name. 226 */ 227 o = NULL; 228 } 229 if (o == NULL) 230 { 231 parg.p_string = printopt; 232 if (ambig) 233 error("--%s is an ambiguous abbreviation (\"less --help\" for help)", &parg); 234 else 235 error("There is no --%s option (\"less --help\" for help)", &parg); 236 return; 237 } 238 } 239 240 str = NULL; 241 switch (o->otype & OTYPE) 242 { 243 case O_BOOL: 244 if (o->otype & O_UNSUPPORTED) 245 break; 246 if (o->ovar != NULL) 247 { 248 if (set_default) 249 *(o->ovar) = o->odefault; 250 else 251 *(o->ovar) = ! o->odefault; 252 } 253 break; 254 case O_TRIPLE: 255 if (o->otype & O_UNSUPPORTED) 256 break; 257 if (o->ovar != NULL) 258 { 259 if (set_default) 260 *(o->ovar) = o->odefault; 261 else if (is_env && o->ovar == &ctldisp) 262 /* If -r appears in an env var, treat it as -R. */ 263 *(o->ovar) = OPT_ONPLUS; 264 else 265 *(o->ovar) = flip_triple(o->odefault, lc); 266 } 267 break; 268 case O_STRING: 269 if (*s == '\0') 270 { 271 /* 272 * Set pendopt and return. 273 * We will get the string next time 274 * scan_option is called. 275 */ 276 pendopt = o; 277 return; 278 } 279 /* 280 * Don't do anything here. 281 * All processing of STRING options is done by 282 * the handling function. 283 */ 284 while (*s == ' ') 285 s++; 286 s = optstring(s, &str, printopt, o->odesc[1]); 287 if (s == NULL) 288 return; 289 break; 290 case O_NUMBER: 291 if (*s == '\0') 292 { 293 pendopt = o; 294 return; 295 } 296 if (o->otype & O_UNSUPPORTED) 297 break; 298 getnumc(&s, printopt, (o->otype & O_NEGOK) != 0, o->ovar); 299 break; 300 } 301 /* 302 * If the option has a handling function, call it. 303 */ 304 if (o->ofunc != NULL && !(o->otype & O_UNSUPPORTED)) 305 (*o->ofunc)(INIT, str); 306 if (str != NULL) 307 free(str); 308 } 309 } 310 311 /* 312 * Toggle command line flags from within the program. 313 * Used by the "-" and "_" commands. 314 * how_toggle may be: 315 * OPT_NO_TOGGLE just report the current setting, without changing it. 316 * OPT_TOGGLE invert the current setting 317 * OPT_UNSET set to the default value 318 * OPT_SET set to the inverse of the default value 319 */ 320 public lbool toggle_option(struct loption *o, lbool lower, constant char *s, int how_toggle) 321 { 322 int no_prompt; 323 PARG parg; 324 325 no_prompt = (how_toggle & OPT_NO_PROMPT); 326 how_toggle &= ~OPT_NO_PROMPT; 327 328 if (o == NULL) 329 { 330 error("No such option", NULL_PARG); 331 return FALSE; 332 } 333 334 if (how_toggle == OPT_TOGGLE && (o->otype & O_NO_TOGGLE)) 335 { 336 parg.p_string = opt_desc(o); 337 error("Cannot change the %s option", &parg); 338 return FALSE; 339 } 340 341 if (how_toggle == OPT_NO_TOGGLE && (o->otype & O_NO_QUERY)) 342 { 343 parg.p_string = opt_desc(o); 344 error("Cannot query the %s option", &parg); 345 return FALSE; 346 } 347 348 /* 349 * Check for something which appears to be a do_toggle 350 * (because the "-" command was used), but really is not. 351 * This could be a string option with no string, or 352 * a number option with no number. 353 */ 354 switch (o->otype & OTYPE) 355 { 356 case O_STRING: 357 case O_NUMBER: 358 if (how_toggle == OPT_TOGGLE && *s == '\0') 359 how_toggle = OPT_NO_TOGGLE; 360 break; 361 } 362 363 #if HILITE_SEARCH 364 if (how_toggle != OPT_NO_TOGGLE && (o->otype & O_HL_REPAINT)) 365 repaint_hilite(FALSE); 366 #endif 367 368 /* 369 * Now actually toggle (change) the variable. 370 */ 371 if (how_toggle != OPT_NO_TOGGLE) 372 { 373 switch (o->otype & OTYPE) 374 { 375 case O_BOOL: 376 /* 377 * Boolean. 378 */ 379 if (o->ovar != NULL) 380 { 381 switch (how_toggle) 382 { 383 case OPT_TOGGLE: 384 *(o->ovar) = ! *(o->ovar); 385 break; 386 case OPT_UNSET: 387 *(o->ovar) = o->odefault; 388 break; 389 case OPT_SET: 390 *(o->ovar) = ! o->odefault; 391 break; 392 } 393 } 394 break; 395 case O_TRIPLE: 396 /* 397 * Triple: 398 * If user gave the lower case letter, then switch 399 * to 1 unless already 1, in which case make it 0. 400 * If user gave the upper case letter, then switch 401 * to 2 unless already 2, in which case make it 0. 402 */ 403 if (o->ovar != NULL) 404 { 405 switch (how_toggle) 406 { 407 case OPT_TOGGLE: 408 *(o->ovar) = flip_triple(*(o->ovar), lower); 409 break; 410 case OPT_UNSET: 411 *(o->ovar) = o->odefault; 412 break; 413 case OPT_SET: 414 *(o->ovar) = flip_triple(o->odefault, lower); 415 break; 416 } 417 } 418 break; 419 case O_STRING: 420 /* 421 * String: don't do anything here. 422 * The handling function will do everything. 423 */ 424 switch (how_toggle) 425 { 426 case OPT_SET: 427 case OPT_UNSET: 428 error("Cannot use \"-+\" or \"-!\" for a string option", 429 NULL_PARG); 430 return FALSE; 431 } 432 break; 433 case O_NUMBER: 434 /* 435 * Number: set the variable to the given number. 436 */ 437 switch (how_toggle) 438 { 439 case OPT_TOGGLE: 440 if (!getnumc(&s, opt_desc(o), (o->otype & O_NEGOK) != 0, o->ovar)) 441 return FALSE; 442 break; 443 case OPT_UNSET: 444 *(o->ovar) = o->odefault; 445 break; 446 case OPT_SET: 447 error("Can't use \"-!\" for a numeric option", 448 NULL_PARG); 449 return FALSE; 450 } 451 break; 452 } 453 } 454 455 /* 456 * Call the handling function for any special action 457 * specific to this option. 458 */ 459 if (o->ofunc != NULL) 460 (*o->ofunc)((how_toggle==OPT_NO_TOGGLE) ? QUERY : TOGGLE, s); 461 462 #if HILITE_SEARCH 463 if (how_toggle != OPT_NO_TOGGLE && (o->otype & O_HL_REPAINT)) 464 chg_hilite(); 465 #endif 466 467 if (!no_prompt) 468 { 469 /* 470 * Print a message describing the new setting. 471 */ 472 switch (o->otype & OTYPE) 473 { 474 case O_BOOL: 475 case O_TRIPLE: 476 /* 477 * Print the odesc message. 478 */ 479 if (o->ovar != NULL) 480 error(o->odesc[*(o->ovar)], NULL_PARG); 481 break; 482 case O_NUMBER: 483 /* 484 * The message is in odesc[1] and has a %d for 485 * the value of the variable. 486 */ 487 parg.p_int = *(o->ovar); 488 error(o->odesc[1], &parg); 489 break; 490 case O_STRING: 491 if (how_toggle != OPT_NO_TOGGLE && o->ofunc != NULL) 492 (*o->ofunc)(QUERY, NULL); 493 break; 494 } 495 } 496 497 if (how_toggle != OPT_NO_TOGGLE && (o->otype & O_REPAINT)) 498 screen_trashed(); 499 return TRUE; 500 } 501 502 /* 503 * "Toggle" a triple-valued option. 504 */ 505 static int flip_triple(int val, lbool lc) 506 { 507 if (lc) 508 return ((val == OPT_ON) ? OPT_OFF : OPT_ON); 509 else 510 return ((val == OPT_ONPLUS) ? OPT_OFF : OPT_ONPLUS); 511 } 512 513 /* 514 * Determine if an option takes a parameter. 515 */ 516 public lbool opt_has_param(constant struct loption *o) 517 { 518 if (o == NULL) 519 return FALSE; 520 if (o->otype & (O_BOOL|O_TRIPLE|O_NOVAR|O_NO_TOGGLE)) 521 return FALSE; 522 return TRUE; 523 } 524 525 /* 526 * Return the prompt to be used for a given option letter. 527 * Only string and number valued options have prompts. 528 */ 529 public constant char * opt_prompt(constant struct loption *o) 530 { 531 if (o == NULL || (o->otype & (O_STRING|O_NUMBER)) == 0) 532 return ("?"); 533 return (o->odesc[0]); 534 } 535 536 /* 537 * If the specified option can be toggled, return NULL. 538 * Otherwise return an appropriate error message. 539 */ 540 public constant char * opt_toggle_disallowed(int c) 541 { 542 switch (c) 543 { 544 case 'o': 545 if (ch_getflags() & CH_CANSEEK) 546 return "Input is not a pipe"; 547 break; 548 } 549 return NULL; 550 } 551 552 /* 553 * Return whether or not there is a string option pending; 554 * that is, if the previous option was a string-valued option letter 555 * (like -P) without a following string. 556 * In that case, the current option is taken to be the string for 557 * the previous option. 558 */ 559 public lbool isoptpending(void) 560 { 561 return (pendopt != NULL); 562 } 563 564 /* 565 * Print error message about missing string. 566 */ 567 static void nostring(constant char *printopt) 568 { 569 PARG parg; 570 parg.p_string = printopt; 571 error("Value is required after %s", &parg); 572 } 573 574 /* 575 * Print error message if a STRING type option is not followed by a string. 576 */ 577 public void nopendopt(void) 578 { 579 nostring(opt_desc(pendopt)); 580 } 581 582 /* 583 * Scan to end of string or to an END_OPTION_STRING character. 584 * In the latter case, replace the char with a null char. 585 * Return a pointer to the remainder of the string, if any. 586 * validchars is of the form "[-][.]d[,]". 587 * "-" means an optional leading "-" is allowed 588 * "." means an optional leading "." is allowed (after any "-") 589 * "d" indicates a string of one or more digits (0-9) 590 * "," indicates a comma-separated list of digit strings is allowed 591 * "s" means a space char terminates the argument 592 */ 593 static constant char * optstring(constant char *s, char **p_str, constant char *printopt, constant char *validchars) 594 { 595 constant char *p; 596 char *out; 597 598 if (*s == '\0') 599 { 600 nostring(printopt); 601 return (NULL); 602 } 603 /* Alloc could be more than needed, but not worth trimming. */ 604 *p_str = (char *) ecalloc(strlen(s)+1, sizeof(char)); 605 out = *p_str; 606 607 for (p = s; *p != '\0'; p++) 608 { 609 if (opt_use_backslash && *p == '\\' && p[1] != '\0') 610 { 611 /* Take next char literally. */ 612 ++p; 613 } else 614 { 615 if (validchars != NULL) 616 { 617 if (validchars[0] == 's') 618 { 619 if (*p == ' ') 620 break; 621 } else if (*p == '-') 622 { 623 if (validchars[0] != '-') 624 break; 625 ++validchars; 626 } else if (*p == '.') 627 { 628 if (validchars[0] == '-') 629 ++validchars; 630 if (validchars[0] != '.') 631 break; 632 ++validchars; 633 } else if (*p == ',') 634 { 635 if (validchars[0] == '\0' || validchars[1] != ',') 636 break; 637 } else if (*p >= '0' && *p <= '9') 638 { 639 while (validchars[0] == '-' || validchars[0] == '.') 640 ++validchars; 641 if (validchars[0] != 'd') 642 break; 643 } else 644 break; 645 } 646 if (*p == END_OPTION_STRING) 647 /* End of option string. */ 648 break; 649 } 650 *out++ = *p; 651 } 652 *out = '\0'; 653 return (p); 654 } 655 656 typedef enum { 657 NUM_ERR_NONE, NUM_ERR_OVERFLOW, NUM_ERR_NEG, NUM_ERR_MISSING 658 } num_error_type; 659 660 /* 661 * Display error message for invalid number. 662 */ 663 static lbool num_error(constant char *printopt, num_error_type error_type) 664 { 665 if (printopt != NULL) 666 { 667 constant char *msg; 668 PARG parg; 669 switch (error_type) 670 { 671 case NUM_ERR_OVERFLOW: msg = "Number too large in %s"; break; 672 case NUM_ERR_NEG: msg = "Negative number not allowed in %s"; break; 673 default: msg = "Number is required after %s"; break; 674 } 675 parg.p_string = printopt; 676 error(msg, &parg); 677 } 678 return FALSE; 679 } 680 681 /* 682 * Translate a string into a number. 683 * Like atoi(), but takes a pointer to a char *, and updates 684 * the char * to point after the translated number. 685 */ 686 public lbool getnumc(constant char **sp, constant char *printopt, lbool neg_ok, mutable int *p_num) 687 { 688 constant char *s = *sp; 689 int n; 690 lbool neg = FALSE; 691 692 s = skipspc(s); 693 if (*s == '-') 694 { 695 if (!neg_ok) 696 return num_error(printopt, NUM_ERR_NEG); 697 neg = TRUE; 698 s++; 699 } 700 if (*s < '0' || *s > '9') 701 return num_error(printopt, NUM_ERR_MISSING); 702 n = lstrtoic(s, sp, 10); 703 if (n < 0) 704 return num_error(printopt, NUM_ERR_OVERFLOW); 705 if (neg) 706 n = -n; 707 *p_num = n; 708 return TRUE; 709 } 710 711 public lbool getnum(char **sp, constant char *printopt, lbool neg_ok, mutable int *p_num) 712 { 713 constant char *cs = *sp; 714 if (!getnumc(&cs, printopt, neg_ok, p_num)) 715 return FALSE; 716 *sp = (char *) cs; 717 return TRUE; 718 } 719 720 /* 721 * Translate a string into a fraction, represented by the part of a 722 * number which would follow a decimal point. 723 * The value of the fraction is returned as parts per NUM_FRAC_DENOM. 724 * That is, if "n" is returned, the fraction intended is n/NUM_FRAC_DENOM. 725 */ 726 public lbool getfraction(constant char **sp, mutable long *p_frac) 727 { 728 constant char *s; 729 long frac = 0; 730 int fraclen = 0; 731 732 s = skipspc(*sp); 733 if (*s < '0' || *s > '9') 734 return FALSE; 735 736 for ( ; *s >= '0' && *s <= '9'; s++) 737 { 738 if (NUM_LOG_FRAC_DENOM <= fraclen) 739 continue; 740 frac = (frac * 10) + (*s - '0'); 741 fraclen++; 742 } 743 while (fraclen++ < NUM_LOG_FRAC_DENOM) 744 frac *= 10; 745 *sp = s; 746 *p_frac = frac; 747 return TRUE; 748 } 749 750 /* 751 * Set the UNSUPPORTED bit in every option listed 752 * in the LESS_UNSUPPORT environment variable. 753 */ 754 public void init_unsupport(void) 755 { 756 constant char *s = lgetenv("LESS_UNSUPPORT"); 757 if (isnullenv(s)) 758 return; 759 for (;;) 760 { 761 struct loption *opt; 762 s = skipspc(s); 763 if (*s == '\0') break; 764 if (*s == '-' && *++s == '\0') break; 765 if (*s == '-') /* long option name */ 766 { 767 ++s; 768 opt = findopt_name(&s, NULL, NULL); 769 } else /* short (single-char) option */ 770 { 771 opt = findopt(*s); 772 if (opt != NULL) ++s; 773 } 774 if (opt != NULL) 775 opt->otype |= O_UNSUPPORTED; 776 } 777 } 778 779 /* 780 * Get the value of the -e flag. 781 */ 782 public int get_quit_at_eof(void) 783 { 784 if (!less_is_more) 785 return quit_at_eof; 786 /* When less_is_more is set, the -e flag semantics are different. */ 787 return quit_at_eof ? OPT_ONPLUS : OPT_ON; 788 } 789