1 /*
2 * Copyright (C) 1984-2021 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 * Handling functions for command line options.
13 *
14 * Most options are handled by the generic code in option.c.
15 * But all string options, and a few non-string options, require
16 * special handling specific to the particular option.
17 * This special processing is done by the "handling functions" in this file.
18 *
19 * Each handling function is passed a "type" and, if it is a string
20 * option, the string which should be "assigned" to the option.
21 * The type may be one of:
22 * INIT The option is being initialized from the command line.
23 * TOGGLE The option is being changed from within the program.
24 * QUERY The setting of the option is merely being queried.
25 */
26
27 #include "less.h"
28 #include "option.h"
29
30 extern int nbufs;
31 extern int bufspace;
32 extern int pr_type;
33 extern int plusoption;
34 extern int swindow;
35 extern int sc_width;
36 extern int sc_height;
37 extern int secure;
38 extern int dohelp;
39 extern int is_tty;
40 extern char openquote;
41 extern char closequote;
42 extern char *prproto[];
43 extern char *eqproto;
44 extern char *hproto;
45 extern char *wproto;
46 extern char *every_first_cmd;
47 extern IFILE curr_ifile;
48 extern char version[];
49 extern int jump_sline;
50 extern long jump_sline_fraction;
51 extern int shift_count;
52 extern long shift_count_fraction;
53 extern char rscroll_char;
54 extern int rscroll_attr;
55 extern int mousecap;
56 extern int wheel_lines;
57 extern int less_is_more;
58 extern int linenum_width;
59 extern int status_col_width;
60 extern int use_color;
61 #if LOGFILE
62 extern char *namelogfile;
63 extern int force_logfile;
64 extern int logfile;
65 #endif
66 #if TAGS
67 public char *tagoption = NULL;
68 extern char *tags;
69 extern char ztags[];
70 #endif
71 #if LESSTEST
72 extern char *ttyin_name;
73 extern int rstat_file;
74 #endif /*LESSTEST*/
75 #if MSDOS_COMPILER
76 extern int nm_fg_color, nm_bg_color;
77 extern int bo_fg_color, bo_bg_color;
78 extern int ul_fg_color, ul_bg_color;
79 extern int so_fg_color, so_bg_color;
80 extern int bl_fg_color, bl_bg_color;
81 extern int sgr_mode;
82 #if MSDOS_COMPILER==WIN32C
83 #ifndef COMMON_LVB_UNDERSCORE
84 #define COMMON_LVB_UNDERSCORE 0x8000
85 #endif
86 #endif
87 #endif
88
89
90 #if LOGFILE
91 /*
92 * Handler for -o option.
93 */
94 public void
opt_o(type,s)95 opt_o(type, s)
96 int type;
97 char *s;
98 {
99 PARG parg;
100 char *filename;
101
102 if (secure)
103 {
104 error("log file support is not available", NULL_PARG);
105 return;
106 }
107 switch (type)
108 {
109 case INIT:
110 namelogfile = save(s);
111 break;
112 case TOGGLE:
113 if (ch_getflags() & CH_CANSEEK)
114 {
115 error("Input is not a pipe", NULL_PARG);
116 return;
117 }
118 if (logfile >= 0)
119 {
120 error("Log file is already in use", NULL_PARG);
121 return;
122 }
123 s = skipsp(s);
124 if (namelogfile != NULL)
125 free(namelogfile);
126 filename = lglob(s);
127 namelogfile = shell_unquote(filename);
128 free(filename);
129 use_logfile(namelogfile);
130 sync_logfile();
131 break;
132 case QUERY:
133 if (logfile < 0)
134 error("No log file", NULL_PARG);
135 else
136 {
137 parg.p_string = namelogfile;
138 error("Log file \"%s\"", &parg);
139 }
140 break;
141 }
142 }
143
144 /*
145 * Handler for -O option.
146 */
147 public void
opt__O(type,s)148 opt__O(type, s)
149 int type;
150 char *s;
151 {
152 force_logfile = TRUE;
153 opt_o(type, s);
154 }
155 #endif
156
157 /*
158 * Handlers for -j option.
159 */
160 public void
opt_j(type,s)161 opt_j(type, s)
162 int type;
163 char *s;
164 {
165 PARG parg;
166 char buf[16];
167 int len;
168 int err;
169
170 switch (type)
171 {
172 case INIT:
173 case TOGGLE:
174 if (*s == '.')
175 {
176 s++;
177 jump_sline_fraction = getfraction(&s, "j", &err);
178 if (err)
179 error("Invalid line fraction", NULL_PARG);
180 else
181 calc_jump_sline();
182 } else
183 {
184 int sline = getnum(&s, "j", &err);
185 if (err)
186 error("Invalid line number", NULL_PARG);
187 else
188 {
189 jump_sline = sline;
190 jump_sline_fraction = -1;
191 }
192 }
193 break;
194 case QUERY:
195 if (jump_sline_fraction < 0)
196 {
197 parg.p_int = jump_sline;
198 error("Position target at screen line %d", &parg);
199 } else
200 {
201
202 sprintf(buf, ".%06ld", jump_sline_fraction);
203 len = (int) strlen(buf);
204 while (len > 2 && buf[len-1] == '0')
205 len--;
206 buf[len] = '\0';
207 parg.p_string = buf;
208 error("Position target at screen position %s", &parg);
209 }
210 break;
211 }
212 }
213
214 public void
calc_jump_sline(VOID_PARAM)215 calc_jump_sline(VOID_PARAM)
216 {
217 if (jump_sline_fraction < 0)
218 return;
219 jump_sline = sc_height * jump_sline_fraction / NUM_FRAC_DENOM;
220 }
221
222 /*
223 * Handlers for -# option.
224 */
225 public void
opt_shift(type,s)226 opt_shift(type, s)
227 int type;
228 char *s;
229 {
230 PARG parg;
231 char buf[16];
232 int len;
233 int err;
234
235 switch (type)
236 {
237 case INIT:
238 case TOGGLE:
239 if (*s == '.')
240 {
241 s++;
242 shift_count_fraction = getfraction(&s, "#", &err);
243 if (err)
244 error("Invalid column fraction", NULL_PARG);
245 else
246 calc_shift_count();
247 } else
248 {
249 int hs = getnum(&s, "#", &err);
250 if (err)
251 error("Invalid column number", NULL_PARG);
252 else
253 {
254 shift_count = hs;
255 shift_count_fraction = -1;
256 }
257 }
258 break;
259 case QUERY:
260 if (shift_count_fraction < 0)
261 {
262 parg.p_int = shift_count;
263 error("Horizontal shift %d columns", &parg);
264 } else
265 {
266
267 sprintf(buf, ".%06ld", shift_count_fraction);
268 len = (int) strlen(buf);
269 while (len > 2 && buf[len-1] == '0')
270 len--;
271 buf[len] = '\0';
272 parg.p_string = buf;
273 error("Horizontal shift %s of screen width", &parg);
274 }
275 break;
276 }
277 }
278 public void
calc_shift_count(VOID_PARAM)279 calc_shift_count(VOID_PARAM)
280 {
281 if (shift_count_fraction < 0)
282 return;
283 shift_count = sc_width * shift_count_fraction / NUM_FRAC_DENOM;
284 }
285
286 #if USERFILE
287 public void
opt_k(type,s)288 opt_k(type, s)
289 int type;
290 char *s;
291 {
292 PARG parg;
293
294 switch (type)
295 {
296 case INIT:
297 if (lesskey(s, 0))
298 {
299 parg.p_string = s;
300 error("Cannot use lesskey file \"%s\"", &parg);
301 }
302 break;
303 }
304 }
305 #endif
306
307 #if TAGS
308 /*
309 * Handler for -t option.
310 */
311 public void
opt_t(type,s)312 opt_t(type, s)
313 int type;
314 char *s;
315 {
316 IFILE save_ifile;
317 POSITION pos;
318
319 switch (type)
320 {
321 case INIT:
322 tagoption = save(s);
323 /* Do the rest in main() */
324 break;
325 case TOGGLE:
326 if (secure)
327 {
328 error("tags support is not available", NULL_PARG);
329 break;
330 }
331 findtag(skipsp(s));
332 save_ifile = save_curr_ifile();
333 /*
334 * Try to open the file containing the tag
335 * and search for the tag in that file.
336 */
337 if (edit_tagfile() || (pos = tagsearch()) == NULL_POSITION)
338 {
339 /* Failed: reopen the old file. */
340 reedit_ifile(save_ifile);
341 break;
342 }
343 unsave_ifile(save_ifile);
344 jump_loc(pos, jump_sline);
345 break;
346 }
347 }
348
349 /*
350 * Handler for -T option.
351 */
352 public void
opt__T(type,s)353 opt__T(type, s)
354 int type;
355 char *s;
356 {
357 PARG parg;
358 char *filename;
359
360 switch (type)
361 {
362 case INIT:
363 tags = save(s);
364 break;
365 case TOGGLE:
366 s = skipsp(s);
367 if (tags != NULL && tags != ztags)
368 free(tags);
369 filename = lglob(s);
370 tags = shell_unquote(filename);
371 free(filename);
372 break;
373 case QUERY:
374 parg.p_string = tags;
375 error("Tags file \"%s\"", &parg);
376 break;
377 }
378 }
379 #endif
380
381 /*
382 * Handler for -p option.
383 */
384 public void
opt_p(type,s)385 opt_p(type, s)
386 int type;
387 char *s;
388 {
389 switch (type)
390 {
391 case INIT:
392 /*
393 * Unget a command for the specified string.
394 */
395 if (less_is_more)
396 {
397 /*
398 * In "more" mode, the -p argument is a command,
399 * not a search string, so we don't need a slash.
400 */
401 every_first_cmd = save(s);
402 } else
403 {
404 plusoption = TRUE;
405 /*
406 * {{ This won't work if the "/" command is
407 * changed or invalidated by a .lesskey file. }}
408 */
409 ungetsc("/");
410 ungetsc(s);
411 ungetcc_back(CHAR_END_COMMAND);
412 }
413 break;
414 }
415 }
416
417 /*
418 * Handler for -P option.
419 */
420 public void
opt__P(type,s)421 opt__P(type, s)
422 int type;
423 char *s;
424 {
425 char **proto;
426 PARG parg;
427
428 switch (type)
429 {
430 case INIT:
431 case TOGGLE:
432 /*
433 * Figure out which prototype string should be changed.
434 */
435 switch (*s)
436 {
437 case 's': proto = &prproto[PR_SHORT]; s++; break;
438 case 'm': proto = &prproto[PR_MEDIUM]; s++; break;
439 case 'M': proto = &prproto[PR_LONG]; s++; break;
440 case '=': proto = &eqproto; s++; break;
441 case 'h': proto = &hproto; s++; break;
442 case 'w': proto = &wproto; s++; break;
443 default: proto = &prproto[PR_SHORT]; break;
444 }
445 free(*proto);
446 *proto = save(s);
447 break;
448 case QUERY:
449 parg.p_string = prproto[pr_type];
450 error("%s", &parg);
451 break;
452 }
453 }
454
455 /*
456 * Handler for the -b option.
457 */
458 /*ARGSUSED*/
459 public void
opt_b(type,s)460 opt_b(type, s)
461 int type;
462 char *s;
463 {
464 switch (type)
465 {
466 case INIT:
467 case TOGGLE:
468 /*
469 * Set the new number of buffers.
470 */
471 ch_setbufspace(bufspace);
472 break;
473 case QUERY:
474 break;
475 }
476 }
477
478 /*
479 * Handler for the -i option.
480 */
481 /*ARGSUSED*/
482 public void
opt_i(type,s)483 opt_i(type, s)
484 int type;
485 char *s;
486 {
487 switch (type)
488 {
489 case TOGGLE:
490 chg_caseless();
491 break;
492 case QUERY:
493 case INIT:
494 break;
495 }
496 }
497
498 /*
499 * Handler for the -V option.
500 */
501 /*ARGSUSED*/
502 public void
opt__V(type,s)503 opt__V(type, s)
504 int type;
505 char *s;
506 {
507 switch (type)
508 {
509 case TOGGLE:
510 case QUERY:
511 dispversion();
512 break;
513 case INIT:
514 set_output(1); /* Force output to stdout per GNU standard for --version output. */
515 putstr("less ");
516 putstr(version);
517 putstr(" (");
518 putstr(pattern_lib_name());
519 putstr(" regular expressions)\n");
520 {
521 char constant *copyright = "Copyright (C) 1984-2021 Mark Nudelman\n\n";
522 if (copyright[0] == '@')
523 copyright = "Copyright (C) 1984 Mark Nudelman\n\n";
524 putstr(copyright);
525 }
526 if (version[strlen(version)-1] == 'x')
527 {
528 putstr("** This is an EXPERIMENTAL build of the 'less' software,\n");
529 putstr("** and may not function correctly.\n");
530 putstr("** Obtain release builds from the web page below.\n\n");
531 }
532 putstr("less comes with NO WARRANTY, to the extent permitted by law.\n");
533 putstr("For information about the terms of redistribution,\n");
534 putstr("see the file named README in the less distribution.\n");
535 putstr("Home page: https://greenwoodsoftware.com/less\n");
536 quit(QUIT_OK);
537 break;
538 }
539 }
540
541 #if MSDOS_COMPILER
542 /*
543 * Parse an MSDOS color descriptor.
544 */
545 static void
colordesc(s,fg_color,bg_color)546 colordesc(s, fg_color, bg_color)
547 char *s;
548 int *fg_color;
549 int *bg_color;
550 {
551 int fg, bg;
552 #if MSDOS_COMPILER==WIN32C
553 int ul = 0;
554
555 if (*s == 'u')
556 {
557 ul = COMMON_LVB_UNDERSCORE;
558 s++;
559 if (*s == '\0')
560 {
561 *fg_color = nm_fg_color | ul;
562 *bg_color = nm_bg_color;
563 return;
564 }
565 }
566 #endif
567 if (parse_color(s, &fg, &bg) == CT_NULL)
568 {
569 PARG p;
570 p.p_string = s;
571 error("Invalid color string \"%s\"", &p);
572 } else
573 {
574 if (fg == CV_NOCHANGE)
575 fg = nm_fg_color;
576 if (bg == CV_NOCHANGE)
577 bg = nm_bg_color;
578 #if MSDOS_COMPILER==WIN32C
579 fg |= ul;
580 #endif
581 *fg_color = fg;
582 *bg_color = bg;
583 }
584 }
585 #endif
586
587 static int
color_from_namechar(namechar)588 color_from_namechar(namechar)
589 char namechar;
590 {
591 switch (namechar)
592 {
593 case 'A': return AT_COLOR_ATTN;
594 case 'B': return AT_COLOR_BIN;
595 case 'C': return AT_COLOR_CTRL;
596 case 'E': return AT_COLOR_ERROR;
597 case 'M': return AT_COLOR_MARK;
598 case 'N': return AT_COLOR_LINENUM;
599 case 'P': return AT_COLOR_PROMPT;
600 case 'R': return AT_COLOR_RSCROLL;
601 case 'S': return AT_COLOR_SEARCH;
602 case 'n': return AT_NORMAL;
603 case 's': return AT_STANDOUT;
604 case 'd': return AT_BOLD;
605 case 'u': return AT_UNDERLINE;
606 case 'k': return AT_BLINK;
607 default: return -1;
608 }
609 }
610
611 /*
612 * Handler for the -D option.
613 */
614 /*ARGSUSED*/
615 public void
opt_D(type,s)616 opt_D(type, s)
617 int type;
618 char *s;
619 {
620 PARG p;
621 int attr;
622
623 switch (type)
624 {
625 case INIT:
626 case TOGGLE:
627 #if MSDOS_COMPILER
628 if (*s == 'a')
629 {
630 sgr_mode = !sgr_mode;
631 break;
632 }
633 #endif
634 attr = color_from_namechar(s[0]);
635 if (attr < 0)
636 {
637 p.p_char = s[0];
638 error("Invalid color specifier '%c'", &p);
639 return;
640 }
641 if (!use_color && (attr & AT_COLOR))
642 {
643 error("Set --use-color before changing colors", NULL_PARG);
644 return;
645 }
646 s++;
647 #if MSDOS_COMPILER
648 if (!(attr & AT_COLOR))
649 {
650 switch (attr)
651 {
652 case AT_NORMAL:
653 colordesc(s, &nm_fg_color, &nm_bg_color);
654 break;
655 case AT_BOLD:
656 colordesc(s, &bo_fg_color, &bo_bg_color);
657 break;
658 case AT_UNDERLINE:
659 colordesc(s, &ul_fg_color, &ul_bg_color);
660 break;
661 case AT_BLINK:
662 colordesc(s, &bl_fg_color, &bl_bg_color);
663 break;
664 case AT_STANDOUT:
665 colordesc(s, &so_fg_color, &so_bg_color);
666 break;
667 }
668 if (type == TOGGLE)
669 {
670 at_enter(AT_STANDOUT);
671 at_exit();
672 }
673 } else
674 #endif
675 if (set_color_map(attr, s) < 0)
676 {
677 p.p_string = s;
678 error("Invalid color string \"%s\"", &p);
679 return;
680 }
681 break;
682 #if MSDOS_COMPILER
683 case QUERY:
684 p.p_string = (sgr_mode) ? "on" : "off";
685 error("SGR mode is %s", &p);
686 break;
687 #endif
688 }
689 }
690
691 /*
692 * Handler for the -x option.
693 */
694 public void
opt_x(type,s)695 opt_x(type, s)
696 int type;
697 char *s;
698 {
699 extern int tabstops[];
700 extern int ntabstops;
701 extern int tabdefault;
702 char msg[60+(4*TABSTOP_MAX)];
703 int i;
704 PARG p;
705
706 switch (type)
707 {
708 case INIT:
709 case TOGGLE:
710 /* Start at 1 because tabstops[0] is always zero. */
711 for (i = 1; i < TABSTOP_MAX; )
712 {
713 int n = 0;
714 s = skipsp(s);
715 while (*s >= '0' && *s <= '9')
716 n = (10 * n) + (*s++ - '0');
717 if (n > tabstops[i-1])
718 tabstops[i++] = n;
719 s = skipsp(s);
720 if (*s++ != ',')
721 break;
722 }
723 if (i < 2)
724 return;
725 ntabstops = i;
726 tabdefault = tabstops[ntabstops-1] - tabstops[ntabstops-2];
727 break;
728 case QUERY:
729 strcpy(msg, "Tab stops ");
730 if (ntabstops > 2)
731 {
732 for (i = 1; i < ntabstops; i++)
733 {
734 if (i > 1)
735 strcat(msg, ",");
736 sprintf(msg+strlen(msg), "%d", tabstops[i]);
737 }
738 sprintf(msg+strlen(msg), " and then ");
739 }
740 sprintf(msg+strlen(msg), "every %d spaces",
741 tabdefault);
742 p.p_string = msg;
743 error("%s", &p);
744 break;
745 }
746 }
747
748
749 /*
750 * Handler for the -" option.
751 */
752 public void
opt_quote(type,s)753 opt_quote(type, s)
754 int type;
755 char *s;
756 {
757 char buf[3];
758 PARG parg;
759
760 switch (type)
761 {
762 case INIT:
763 case TOGGLE:
764 if (s[0] == '\0')
765 {
766 openquote = closequote = '\0';
767 break;
768 }
769 if (s[1] != '\0' && s[2] != '\0')
770 {
771 error("-\" must be followed by 1 or 2 chars", NULL_PARG);
772 return;
773 }
774 openquote = s[0];
775 if (s[1] == '\0')
776 closequote = openquote;
777 else
778 closequote = s[1];
779 break;
780 case QUERY:
781 buf[0] = openquote;
782 buf[1] = closequote;
783 buf[2] = '\0';
784 parg.p_string = buf;
785 error("quotes %s", &parg);
786 break;
787 }
788 }
789
790 /*
791 * Handler for the --rscroll option.
792 */
793 /*ARGSUSED*/
794 public void
opt_rscroll(type,s)795 opt_rscroll(type, s)
796 int type;
797 char *s;
798 {
799 PARG p;
800
801 switch (type)
802 {
803 case INIT:
804 case TOGGLE: {
805 char *fmt;
806 int attr = AT_STANDOUT;
807 setfmt(s, &fmt, &attr, "*s>");
808 if (strcmp(fmt, "-") == 0)
809 {
810 rscroll_char = 0;
811 } else
812 {
813 rscroll_char = *fmt ? *fmt : '>';
814 rscroll_attr = attr|AT_COLOR_RSCROLL;
815 }
816 break; }
817 case QUERY: {
818 p.p_string = rscroll_char ? prchar(rscroll_char) : "-";
819 error("rscroll char is %s", &p);
820 break; }
821 }
822 }
823
824 /*
825 * "-?" means display a help message.
826 * If from the command line, exit immediately.
827 */
828 /*ARGSUSED*/
829 public void
opt_query(type,s)830 opt_query(type, s)
831 int type;
832 char *s;
833 {
834 switch (type)
835 {
836 case QUERY:
837 case TOGGLE:
838 error("Use \"h\" for help", NULL_PARG);
839 break;
840 case INIT:
841 dohelp = 1;
842 }
843 }
844
845 /*
846 * Handler for the --mouse option.
847 */
848 /*ARGSUSED*/
849 public void
opt_mousecap(type,s)850 opt_mousecap(type, s)
851 int type;
852 char *s;
853 {
854 switch (type)
855 {
856 case TOGGLE:
857 if (mousecap == OPT_OFF)
858 deinit_mouse();
859 else
860 init_mouse();
861 break;
862 case INIT:
863 case QUERY:
864 break;
865 }
866 }
867
868 /*
869 * Handler for the --wheel-lines option.
870 */
871 /*ARGSUSED*/
872 public void
opt_wheel_lines(type,s)873 opt_wheel_lines(type, s)
874 int type;
875 char *s;
876 {
877 switch (type)
878 {
879 case INIT:
880 case TOGGLE:
881 if (wheel_lines <= 0)
882 wheel_lines = default_wheel_lines();
883 break;
884 case QUERY:
885 break;
886 }
887 }
888
889 /*
890 * Handler for the --line-number-width option.
891 */
892 /*ARGSUSED*/
893 public void
opt_linenum_width(type,s)894 opt_linenum_width(type, s)
895 int type;
896 char *s;
897 {
898 PARG parg;
899
900 switch (type)
901 {
902 case INIT:
903 case TOGGLE:
904 if (linenum_width > MAX_LINENUM_WIDTH)
905 {
906 parg.p_int = MAX_LINENUM_WIDTH;
907 error("Line number width must not be larger than %d", &parg);
908 linenum_width = MIN_LINENUM_WIDTH;
909 }
910 break;
911 case QUERY:
912 break;
913 }
914 }
915
916 /*
917 * Handler for the --status-column-width option.
918 */
919 /*ARGSUSED*/
920 public void
opt_status_col_width(type,s)921 opt_status_col_width(type, s)
922 int type;
923 char *s;
924 {
925 PARG parg;
926
927 switch (type)
928 {
929 case INIT:
930 case TOGGLE:
931 if (status_col_width > MAX_STATUSCOL_WIDTH)
932 {
933 parg.p_int = MAX_STATUSCOL_WIDTH;
934 error("Status column width must not be larger than %d", &parg);
935 status_col_width = 2;
936 }
937 break;
938 case QUERY:
939 break;
940 }
941 }
942
943 #if LESSTEST
944 /*
945 * Handler for the --tty option.
946 */
947 /*ARGSUSED*/
948 public void
opt_ttyin_name(type,s)949 opt_ttyin_name(type, s)
950 int type;
951 char *s;
952 {
953 switch (type)
954 {
955 case INIT:
956 ttyin_name = s;
957 is_tty = 1;
958 break;
959 }
960 }
961
962 /*
963 * Handler for the --rstat option.
964 */
965 /*ARGSUSED*/
966 public void
opt_rstat(type,s)967 opt_rstat(type, s)
968 int type;
969 char *s;
970 {
971 switch (type)
972 {
973 case INIT:
974 rstat_file = open(s, O_WRONLY|O_CREAT, 0664);
975 if (rstat_file < 0)
976 {
977 PARG parg;
978 parg.p_string = s;
979 error("Cannot create rstat file \"%s\"", &parg);
980 }
981 break;
982 }
983 }
984 #endif /*LESSTEST*/
985
986 /*
987 * Get the "screen window" size.
988 */
989 public int
get_swindow(VOID_PARAM)990 get_swindow(VOID_PARAM)
991 {
992 if (swindow > 0)
993 return (swindow);
994 return (sc_height + swindow);
995 }
996
997