1 /* 2 * Copyright (C) 1984-2025 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 lbool ambig; 74 PARG parg; 75 76 if (s == NULL) 77 return; 78 79 /* 80 * If we have a pending option which requires an argument, 81 * handle it now. 82 * This happens if the previous option was, for example, "-P" 83 * without a following string. In that case, the current 84 * option is simply the argument for the previous option. 85 */ 86 if (pendopt != NULL) 87 { 88 if (!(pendopt->otype & O_UNSUPPORTED)) 89 { 90 switch (pendopt->otype & OTYPE) 91 { 92 case O_STRING: 93 (*pendopt->ofunc)(INIT, s); 94 break; 95 case O_NUMBER: 96 printopt = opt_desc(pendopt); 97 *(pendopt->ovar) = getnumc(&s, printopt, NULL); 98 break; 99 } 100 } 101 pendopt = NULL; 102 return; 103 } 104 105 set_default = FALSE; 106 optname = NULL; 107 108 while (*s != '\0') 109 { 110 /* 111 * Check some special cases first. 112 */ 113 switch (optc = *s++) 114 { 115 case ' ': 116 case '\t': 117 case END_OPTION_STRING: 118 continue; 119 case '-': 120 /* 121 * "--" indicates an option name instead of a letter. 122 */ 123 if (*s == '-') 124 optname = ++s; 125 /* 126 * "-+" or "--+" means set these options back to their defaults. 127 * (They may have been set otherwise by previous options.) 128 */ 129 set_default = (*s == '+'); 130 if (set_default) 131 s++; 132 if (optname != NULL) 133 { 134 optname = s; 135 break; 136 } 137 continue; 138 case '+': 139 /* 140 * An option prefixed by a "+" is ungotten, so 141 * that it is interpreted as less commands 142 * processed at the start of the first input file. 143 * "++" means process the commands at the start of 144 * EVERY input file. 145 */ 146 plusoption = TRUE; 147 s = optstring(s, &str, propt('+'), NULL); 148 if (s == NULL) 149 return; 150 if (*str == '+') 151 { 152 if (every_first_cmd != NULL) 153 free(every_first_cmd); 154 every_first_cmd = save(str+1); 155 } else 156 { 157 ungetsc(str); 158 ungetcc_end_command(); 159 } 160 free(str); 161 continue; 162 case '0': case '1': case '2': case '3': case '4': 163 case '5': case '6': case '7': case '8': case '9': 164 /* 165 * Special "more" compatibility form "-<number>" 166 * instead of -z<number> to set the scrolling 167 * window size. 168 */ 169 s--; 170 optc = 'z'; 171 break; 172 case 'n': 173 if (less_is_more) 174 optc = 'z'; 175 break; 176 } 177 178 /* 179 * Not a special case. 180 * Look up the option letter in the option table. 181 */ 182 ambig = FALSE; 183 if (optname == NULL) 184 { 185 printopt = propt(optc); 186 lc = ASCII_IS_LOWER(optc); 187 o = findopt(optc); 188 } else 189 { 190 printopt = optname; 191 lc = ASCII_IS_LOWER(optname[0]); 192 o = findopt_name(&optname, NULL, &ambig); 193 s = optname; 194 optname = NULL; 195 if (*s == '\0' || *s == ' ') 196 { 197 /* 198 * The option name matches exactly. 199 */ 200 ; 201 } else if (*s == '=') 202 { 203 /* 204 * The option name is followed by "=value". 205 */ 206 if (o != NULL && 207 (o->otype & OTYPE) != O_STRING && 208 (o->otype & OTYPE) != O_NUMBER) 209 { 210 parg.p_string = printopt; 211 error("The %s option should not be followed by =", 212 &parg); 213 return; 214 } 215 s++; 216 } else 217 { 218 /* 219 * The specified name is longer than the 220 * real option name. 221 */ 222 o = NULL; 223 } 224 } 225 if (o == NULL) 226 { 227 parg.p_string = printopt; 228 if (ambig) 229 error("%s is an ambiguous abbreviation (\"less --help\" for help)", 230 &parg); 231 else 232 error("There is no %s option (\"less --help\" for help)", 233 &parg); 234 return; 235 } 236 237 str = NULL; 238 switch (o->otype & OTYPE) 239 { 240 case O_BOOL: 241 if (o->otype & O_UNSUPPORTED) 242 break; 243 if (o->ovar != NULL) 244 { 245 if (set_default) 246 *(o->ovar) = o->odefault; 247 else 248 *(o->ovar) = ! o->odefault; 249 } 250 break; 251 case O_TRIPLE: 252 if (o->otype & O_UNSUPPORTED) 253 break; 254 if (o->ovar != NULL) 255 { 256 if (set_default) 257 *(o->ovar) = o->odefault; 258 else if (is_env && o->ovar == &ctldisp) 259 /* If -r appears in an env var, treat it as -R. */ 260 *(o->ovar) = OPT_ONPLUS; 261 else 262 *(o->ovar) = flip_triple(o->odefault, lc); 263 } 264 break; 265 case O_STRING: 266 if (*s == '\0') 267 { 268 /* 269 * Set pendopt and return. 270 * We will get the string next time 271 * scan_option is called. 272 */ 273 pendopt = o; 274 return; 275 } 276 /* 277 * Don't do anything here. 278 * All processing of STRING options is done by 279 * the handling function. 280 */ 281 while (*s == ' ') 282 s++; 283 s = optstring(s, &str, printopt, o->odesc[1]); 284 if (s == NULL) 285 return; 286 break; 287 case O_NUMBER: 288 if (*s == '\0') 289 { 290 pendopt = o; 291 return; 292 } 293 if (o->otype & O_UNSUPPORTED) 294 break; 295 *(o->ovar) = getnumc(&s, printopt, NULL); 296 break; 297 } 298 /* 299 * If the option has a handling function, call it. 300 */ 301 if (o->ofunc != NULL && !(o->otype & O_UNSUPPORTED)) 302 (*o->ofunc)(INIT, str); 303 if (str != NULL) 304 free(str); 305 } 306 } 307 308 /* 309 * Toggle command line flags from within the program. 310 * Used by the "-" and "_" commands. 311 * how_toggle may be: 312 * OPT_NO_TOGGLE just report the current setting, without changing it. 313 * OPT_TOGGLE invert the current setting 314 * OPT_UNSET set to the default value 315 * OPT_SET set to the inverse of the default value 316 */ 317 public void toggle_option(struct loption *o, lbool lower, constant char *s, int how_toggle) 318 { 319 int num; 320 int no_prompt; 321 lbool err; 322 PARG parg; 323 324 no_prompt = (how_toggle & OPT_NO_PROMPT); 325 how_toggle &= ~OPT_NO_PROMPT; 326 327 if (o == NULL) 328 { 329 error("No such option", NULL_PARG); 330 return; 331 } 332 333 if (how_toggle == OPT_TOGGLE && (o->otype & O_NO_TOGGLE)) 334 { 335 parg.p_string = opt_desc(o); 336 error("Cannot change the %s option", &parg); 337 return; 338 } 339 340 if (how_toggle == OPT_NO_TOGGLE && (o->otype & O_NO_QUERY)) 341 { 342 parg.p_string = opt_desc(o); 343 error("Cannot query the %s option", &parg); 344 return; 345 } 346 347 /* 348 * Check for something which appears to be a do_toggle 349 * (because the "-" command was used), but really is not. 350 * This could be a string option with no string, or 351 * a number option with no number. 352 */ 353 switch (o->otype & OTYPE) 354 { 355 case O_STRING: 356 case O_NUMBER: 357 if (how_toggle == OPT_TOGGLE && *s == '\0') 358 how_toggle = OPT_NO_TOGGLE; 359 break; 360 } 361 362 #if HILITE_SEARCH 363 if (how_toggle != OPT_NO_TOGGLE && (o->otype & O_HL_REPAINT)) 364 repaint_hilite(FALSE); 365 #endif 366 367 /* 368 * Now actually toggle (change) the variable. 369 */ 370 if (how_toggle != OPT_NO_TOGGLE) 371 { 372 switch (o->otype & OTYPE) 373 { 374 case O_BOOL: 375 /* 376 * Boolean. 377 */ 378 if (o->ovar != NULL) 379 { 380 switch (how_toggle) 381 { 382 case OPT_TOGGLE: 383 *(o->ovar) = ! *(o->ovar); 384 break; 385 case OPT_UNSET: 386 *(o->ovar) = o->odefault; 387 break; 388 case OPT_SET: 389 *(o->ovar) = ! o->odefault; 390 break; 391 } 392 } 393 break; 394 case O_TRIPLE: 395 /* 396 * Triple: 397 * If user gave the lower case letter, then switch 398 * to 1 unless already 1, in which case make it 0. 399 * If user gave the upper case letter, then switch 400 * to 2 unless already 2, in which case make it 0. 401 */ 402 if (o->ovar != NULL) 403 { 404 switch (how_toggle) 405 { 406 case OPT_TOGGLE: 407 *(o->ovar) = flip_triple(*(o->ovar), lower); 408 break; 409 case OPT_UNSET: 410 *(o->ovar) = o->odefault; 411 break; 412 case OPT_SET: 413 *(o->ovar) = flip_triple(o->odefault, lower); 414 break; 415 } 416 } 417 break; 418 case O_STRING: 419 /* 420 * String: don't do anything here. 421 * The handling function will do everything. 422 */ 423 switch (how_toggle) 424 { 425 case OPT_SET: 426 case OPT_UNSET: 427 error("Cannot use \"-+\" or \"-!\" for a string option", 428 NULL_PARG); 429 return; 430 } 431 break; 432 case O_NUMBER: 433 /* 434 * Number: set the variable to the given number. 435 */ 436 switch (how_toggle) 437 { 438 case OPT_TOGGLE: 439 num = getnumc(&s, NULL, &err); 440 if (!err) 441 *(o->ovar) = num; 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; 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 /* 492 * Message was already printed by the handling function. 493 */ 494 break; 495 } 496 } 497 498 if (how_toggle != OPT_NO_TOGGLE && (o->otype & O_REPAINT)) 499 screen_trashed(); 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 int opt_has_param(struct loption *o) 517 { 518 if (o == NULL) 519 return (0); 520 if (o->otype & (O_BOOL|O_TRIPLE|O_NOVAR|O_NO_TOGGLE)) 521 return (0); 522 return (1); 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(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 /* 657 */ 658 static int num_error(constant char *printopt, lbool *errp, lbool overflow) 659 { 660 PARG parg; 661 662 if (errp != NULL) 663 { 664 *errp = TRUE; 665 return (-1); 666 } 667 if (printopt != NULL) 668 { 669 parg.p_string = printopt; 670 error((overflow 671 ? "Number too large in '%s'" 672 : "Number is required after %s"), 673 &parg); 674 } 675 return (-1); 676 } 677 678 /* 679 * Translate a string into a number. 680 * Like atoi(), but takes a pointer to a char *, and updates 681 * the char * to point after the translated number. 682 */ 683 public int getnumc(constant char **sp, constant char *printopt, lbool *errp) 684 { 685 constant char *s = *sp; 686 int n; 687 lbool neg; 688 689 s = skipspc(s); 690 neg = FALSE; 691 if (*s == '-') 692 { 693 neg = TRUE; 694 s++; 695 } 696 if (*s < '0' || *s > '9') 697 return (num_error(printopt, errp, FALSE)); 698 699 n = lstrtoic(s, sp, 10); 700 if (n < 0) 701 return (num_error(printopt, errp, TRUE)); 702 if (errp != NULL) 703 *errp = FALSE; 704 if (neg) 705 n = -n; 706 return (n); 707 } 708 709 public int getnum(char **sp, constant char *printopt, lbool *errp) 710 { 711 constant char *cs = *sp; 712 int r = getnumc(&cs, printopt, errp); 713 *sp = (char *) cs; 714 return r; 715 } 716 717 /* 718 * Translate a string into a fraction, represented by the part of a 719 * number which would follow a decimal point. 720 * The value of the fraction is returned as parts per NUM_FRAC_DENOM. 721 * That is, if "n" is returned, the fraction intended is n/NUM_FRAC_DENOM. 722 */ 723 public long getfraction(constant char **sp, constant char *printopt, lbool *errp) 724 { 725 constant char *s; 726 long frac = 0; 727 int fraclen = 0; 728 729 s = skipspc(*sp); 730 if (*s < '0' || *s > '9') 731 return (num_error(printopt, errp, FALSE)); 732 733 for ( ; *s >= '0' && *s <= '9'; s++) 734 { 735 if (NUM_LOG_FRAC_DENOM <= fraclen) 736 continue; 737 frac = (frac * 10) + (*s - '0'); 738 fraclen++; 739 } 740 while (fraclen++ < NUM_LOG_FRAC_DENOM) 741 frac *= 10; 742 *sp = s; 743 if (errp != NULL) 744 *errp = FALSE; 745 return (frac); 746 } 747 748 /* 749 * Set the UNSUPPORTED bit in every option listed 750 * in the LESS_UNSUPPORT environment variable. 751 */ 752 public void init_unsupport(void) 753 { 754 constant char *s = lgetenv("LESS_UNSUPPORT"); 755 if (isnullenv(s)) 756 return; 757 for (;;) 758 { 759 struct loption *opt; 760 s = skipspc(s); 761 if (*s == '\0') break; 762 if (*s == '-' && *++s == '\0') break; 763 if (*s == '-') /* long option name */ 764 { 765 ++s; 766 opt = findopt_name(&s, NULL, NULL); 767 } else /* short (single-char) option */ 768 { 769 opt = findopt(*s); 770 if (opt != NULL) ++s; 771 } 772 if (opt != NULL) 773 opt->otype |= O_UNSUPPORTED; 774 } 775 } 776 777 /* 778 * Get the value of the -e flag. 779 */ 780 public int get_quit_at_eof(void) 781 { 782 if (!less_is_more) 783 return quit_at_eof; 784 /* When less_is_more is set, the -e flag semantics are different. */ 785 return quit_at_eof ? OPT_ONPLUS : OPT_ON; 786 } 787