xref: /freebsd/contrib/less/optfunc.c (revision c77c488926555ca344ae3a417544cf7a720e1de1)
1 /*
2  * Copyright (C) 1984-2024  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 #include "position.h"
30 
31 extern int bufspace;
32 extern int pr_type;
33 extern lbool plusoption;
34 extern int swindow;
35 extern int sc_width;
36 extern int sc_height;
37 extern int dohelp;
38 extern char openquote;
39 extern char closequote;
40 extern char *prproto[];
41 extern char *eqproto;
42 extern char *hproto;
43 extern char *wproto;
44 extern char *every_first_cmd;
45 extern IFILE curr_ifile;
46 extern char version[];
47 extern int jump_sline;
48 extern long jump_sline_fraction;
49 extern int shift_count;
50 extern long shift_count_fraction;
51 extern int match_shift;
52 extern long match_shift_fraction;
53 extern LWCHAR 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 extern int want_filesize;
62 extern int header_lines;
63 extern int header_cols;
64 extern int def_search_type;
65 extern int chopline;
66 extern int tabstops[];
67 extern int ntabstops;
68 extern int tabdefault;
69 extern char intr_char;
70 extern int nosearch_header_lines;
71 extern int nosearch_header_cols;
72 extern POSITION header_start_pos;
73 extern char *init_header;
74 #if LOGFILE
75 extern char *namelogfile;
76 extern lbool force_logfile;
77 extern int logfile;
78 #endif
79 #if TAGS
80 public char *tagoption = NULL;
81 extern char *tags;
82 extern char ztags[];
83 #endif
84 #if LESSTEST
85 extern constant char *ttyin_name;
86 extern int is_tty;
87 #endif /*LESSTEST*/
88 #if MSDOS_COMPILER
89 extern int nm_fg_color, nm_bg_color, nm_attr;
90 extern int bo_fg_color, bo_bg_color, bo_attr;
91 extern int ul_fg_color, ul_bg_color, ul_attr;
92 extern int so_fg_color, so_bg_color, so_attr;
93 extern int bl_fg_color, bl_bg_color, bl_attr;
94 extern int sgr_mode;
95 #if MSDOS_COMPILER==WIN32C
96 #ifndef COMMON_LVB_UNDERSCORE
97 #define COMMON_LVB_UNDERSCORE 0x8000
98 #endif
99 #ifndef COMMON_LVB_REVERSE_VIDEO
100 #define COMMON_LVB_REVERSE_VIDEO 0x4000
101 #endif
102 #endif
103 #endif
104 
105 
106 #if LOGFILE
107 /*
108  * Handler for -o option.
109  */
110 public void opt_o(int type, constant char *s)
111 {
112 	PARG parg;
113 	char *filename;
114 
115 	if (!secure_allow(SF_LOGFILE))
116 	{
117 		error("log file support is not available", NULL_PARG);
118 		return;
119 	}
120 	switch (type)
121 	{
122 	case INIT:
123 		namelogfile = save(s);
124 		break;
125 	case TOGGLE:
126 		if (ch_getflags() & CH_CANSEEK)
127 		{
128 			error("Input is not a pipe", NULL_PARG);
129 			return;
130 		}
131 		if (logfile >= 0)
132 		{
133 			error("Log file is already in use", NULL_PARG);
134 			return;
135 		}
136 		s = skipspc(s);
137 		if (namelogfile != NULL)
138 			free(namelogfile);
139 		filename = lglob(s);
140 		namelogfile = shell_unquote(filename);
141 		free(filename);
142 		use_logfile(namelogfile);
143 		sync_logfile();
144 		break;
145 	case QUERY:
146 		if (logfile < 0)
147 			error("No log file", NULL_PARG);
148 		else
149 		{
150 			parg.p_string = namelogfile;
151 			error("Log file \"%s\"", &parg);
152 		}
153 		break;
154 	}
155 }
156 
157 /*
158  * Handler for -O option.
159  */
160 public void opt__O(int type, constant char *s)
161 {
162 	force_logfile = TRUE;
163 	opt_o(type, s);
164 }
165 #endif
166 
167 static int toggle_fraction(int *num, long *frac, constant char *s, constant char *printopt, void (*calc)(void))
168 {
169 	lbool err;
170 	if (s == NULL)
171 	{
172 		(*calc)();
173 	} else if (*s == '.')
174 	{
175         long tfrac;
176 		s++;
177 		tfrac = getfraction(&s, printopt, &err);
178 		if (err)
179 		{
180 			error("Invalid fraction", NULL_PARG);
181 			return -1;
182 		}
183 		*frac = tfrac;
184 		(*calc)();
185 	} else
186 	{
187 		int tnum = getnumc(&s, printopt, &err);
188 		if (err)
189 		{
190 			error("Invalid number", NULL_PARG);
191 			return -1;
192 		}
193 		*frac = -1;
194 		*num = tnum;
195 	}
196 	return 0;
197 }
198 
199 static void query_fraction(int value, long fraction, constant char *int_msg, constant char *frac_msg)
200 {
201 	PARG parg;
202 
203 	if (fraction < 0)
204 	{
205 		parg.p_int = value;
206 		error(int_msg, &parg);
207 	} else
208 	{
209 		char buf[INT_STRLEN_BOUND(long)+2];
210 		size_t len;
211 		SNPRINTF1(buf, sizeof(buf), ".%06ld", fraction);
212 		len = strlen(buf);
213 		while (len > 2 && buf[len-1] == '0')
214 			len--;
215 		buf[len] = '\0';
216 		parg.p_string = buf;
217 		error(frac_msg, &parg);
218 	}
219 }
220 
221 /*
222  * Handlers for -j option.
223  */
224 public void opt_j(int type, constant char *s)
225 {
226 	switch (type)
227 	{
228 	case INIT:
229 	case TOGGLE:
230 		toggle_fraction(&jump_sline, &jump_sline_fraction,
231 			s, "j", calc_jump_sline);
232 		break;
233 	case QUERY:
234 		query_fraction(jump_sline, jump_sline_fraction,
235 			"Position target at screen line %d", "Position target at screen position %s");
236 		break;
237 	}
238 }
239 
240 public void calc_jump_sline(void)
241 {
242 	if (jump_sline_fraction >= 0)
243 		jump_sline = (int) muldiv(sc_height, jump_sline_fraction, NUM_FRAC_DENOM);
244 	if (jump_sline <= header_lines)
245 		jump_sline = header_lines + 1;
246 }
247 
248 /*
249  * Handlers for -# option.
250  */
251 public void opt_shift(int type, constant char *s)
252 {
253 	switch (type)
254 	{
255 	case INIT:
256 	case TOGGLE:
257 		toggle_fraction(&shift_count, &shift_count_fraction,
258 			s, "#", calc_shift_count);
259 		break;
260 	case QUERY:
261 		query_fraction(shift_count, shift_count_fraction,
262 			"Horizontal shift %d columns", "Horizontal shift %s of screen width");
263 		break;
264 	}
265 }
266 
267 public void calc_shift_count(void)
268 {
269 	if (shift_count_fraction < 0)
270 		return;
271 	shift_count = (int) muldiv(sc_width, shift_count_fraction, NUM_FRAC_DENOM);
272 }
273 
274 #if USERFILE
275 public void opt_k(int type, constant char *s)
276 {
277 	PARG parg;
278 
279 	switch (type)
280 	{
281 	case INIT:
282 		if (lesskey(s, 0))
283 		{
284 			parg.p_string = s;
285 			error("Cannot use lesskey file \"%s\"", &parg);
286 		}
287 		break;
288 	}
289 }
290 
291 #if HAVE_LESSKEYSRC
292 public void opt_ks(int type, constant char *s)
293 {
294 	PARG parg;
295 
296 	switch (type)
297 	{
298 	case INIT:
299 		if (lesskey_src(s, 0))
300 		{
301 			parg.p_string = s;
302 			error("Cannot use lesskey source file \"%s\"", &parg);
303 		}
304 		break;
305 	}
306 }
307 
308 public void opt_kc(int type, constant char *s)
309 {
310 	switch (type)
311 	{
312 	case INIT:
313 		if (lesskey_content(s, 0))
314 		{
315 			error("Error in lesskey content", NULL_PARG);
316 		}
317 		break;
318 	}
319 }
320 
321 #endif /* HAVE_LESSKEYSRC */
322 #endif /* USERFILE */
323 
324 /*
325  * Handler for -S option.
326  */
327 public void opt__S(int type, constant char *s)
328 {
329 	switch (type)
330 	{
331 	case TOGGLE:
332 		pos_rehead();
333 		break;
334 	}
335 }
336 
337 #if TAGS
338 /*
339  * Handler for -t option.
340  */
341 public void opt_t(int type, constant char *s)
342 {
343 	IFILE save_ifile;
344 	POSITION pos;
345 
346 	switch (type)
347 	{
348 	case INIT:
349 		tagoption = save(s);
350 		/* Do the rest in main() */
351 		break;
352 	case TOGGLE:
353 		if (!secure_allow(SF_TAGS))
354 		{
355 			error("tags support is not available", NULL_PARG);
356 			break;
357 		}
358 		findtag(skipspc(s));
359 		save_ifile = save_curr_ifile();
360 		/*
361 		 * Try to open the file containing the tag
362 		 * and search for the tag in that file.
363 		 */
364 		if (edit_tagfile() || (pos = tagsearch()) == NULL_POSITION)
365 		{
366 			/* Failed: reopen the old file. */
367 			reedit_ifile(save_ifile);
368 			break;
369 		}
370 		unsave_ifile(save_ifile);
371 		jump_loc(pos, jump_sline);
372 		break;
373 	}
374 }
375 
376 /*
377  * Handler for -T option.
378  */
379 public void opt__T(int type, constant char *s)
380 {
381 	PARG parg;
382 	char *filename;
383 
384 	switch (type)
385 	{
386 	case INIT:
387 		tags = save(s);
388 		break;
389 	case TOGGLE:
390 		s = skipspc(s);
391 		if (tags != NULL && tags != ztags)
392 			free(tags);
393 		filename = lglob(s);
394 		tags = shell_unquote(filename);
395 		free(filename);
396 		break;
397 	case QUERY:
398 		parg.p_string = tags;
399 		error("Tags file \"%s\"", &parg);
400 		break;
401 	}
402 }
403 #endif
404 
405 /*
406  * Handler for -p option.
407  */
408 public void opt_p(int type, constant char *s)
409 {
410 	switch (type)
411 	{
412 	case INIT:
413 		/*
414 		 * Unget a command for the specified string.
415 		 */
416 		if (less_is_more)
417 		{
418 			/*
419 			 * In "more" mode, the -p argument is a command,
420 			 * not a search string, so we don't need a slash.
421 			 */
422 			every_first_cmd = save(s);
423 		} else
424 		{
425 			plusoption = TRUE;
426 			 /*
427 			  * {{ This won't work if the "/" command is
428 			  *    changed or invalidated by a .lesskey file. }}
429 			  */
430 			ungetsc("/");
431 			ungetsc(s);
432 			ungetcc_end_command();
433 		}
434 		break;
435 	}
436 }
437 
438 /*
439  * Handler for -P option.
440  */
441 public void opt__P(int type, constant char *s)
442 {
443 	char **proto;
444 	PARG parg;
445 
446 	switch (type)
447 	{
448 	case INIT:
449 	case TOGGLE:
450 		/*
451 		 * Figure out which prototype string should be changed.
452 		 */
453 		switch (*s)
454 		{
455 		case 's':  proto = &prproto[PR_SHORT];  s++;    break;
456 		case 'm':  proto = &prproto[PR_MEDIUM]; s++;    break;
457 		case 'M':  proto = &prproto[PR_LONG];   s++;    break;
458 		case '=':  proto = &eqproto;            s++;    break;
459 		case 'h':  proto = &hproto;             s++;    break;
460 		case 'w':  proto = &wproto;             s++;    break;
461 		default:   proto = &prproto[PR_SHORT];          break;
462 		}
463 		free(*proto);
464 		*proto = save(s);
465 		break;
466 	case QUERY:
467 		parg.p_string = prproto[pr_type];
468 		error("%s", &parg);
469 		break;
470 	}
471 }
472 
473 /*
474  * Handler for the -b option.
475  */
476 	/*ARGSUSED*/
477 public void opt_b(int type, constant char *s)
478 {
479 	switch (type)
480 	{
481 	case INIT:
482 	case TOGGLE:
483 		/*
484 		 * Set the new number of buffers.
485 		 */
486 		ch_setbufspace((ssize_t) bufspace);
487 		break;
488 	case QUERY:
489 		break;
490 	}
491 }
492 
493 /*
494  * Handler for the -i option.
495  */
496 	/*ARGSUSED*/
497 public void opt_i(int type, constant char *s)
498 {
499 	switch (type)
500 	{
501 	case TOGGLE:
502 		chg_caseless();
503 		break;
504 	case QUERY:
505 	case INIT:
506 		break;
507 	}
508 }
509 
510 /*
511  * Handler for the -V option.
512  */
513 	/*ARGSUSED*/
514 public void opt__V(int type, constant char *s)
515 {
516 	switch (type)
517 	{
518 	case TOGGLE:
519 	case QUERY:
520 		dispversion();
521 		break;
522 	case INIT:
523 		set_output(1); /* Force output to stdout per GNU standard for --version output. */
524 		putstr("less ");
525 		putstr(version);
526 		putstr(" (");
527 		putstr(pattern_lib_name());
528 		putstr(" regular expressions)\n");
529 		{
530 			char constant *copyright =
531 				"Copyright (C) 1984-2024  Mark Nudelman\n\n";
532 			putstr(copyright);
533 		}
534 		if (version[strlen(version)-1] == 'x')
535 		{
536 			putstr("** This is an EXPERIMENTAL build of the 'less' software,\n");
537 			putstr("** and may not function correctly.\n");
538 			putstr("** Obtain release builds from the web page below.\n\n");
539 		}
540 #if LESSTEST
541 		putstr("This build supports LESSTEST.\n");
542 #endif /*LESSTEST*/
543 		putstr("less comes with NO WARRANTY, to the extent permitted by law.\n");
544 		putstr("For information about the terms of redistribution,\n");
545 		putstr("see the file named README in the less distribution.\n");
546 		putstr("Home page: https://greenwoodsoftware.com/less\n");
547 		quit(QUIT_OK);
548 		break;
549 	}
550 }
551 
552 #if MSDOS_COMPILER
553 /*
554  * Parse an MSDOS color descriptor.
555  */
556 static void colordesc(constant char *s, int *fg_color, int *bg_color, int *dattr)
557 {
558 	int fg, bg;
559 	CHAR_ATTR attr;
560 	if (parse_color(s, &fg, &bg, &attr) == CT_NULL)
561 	{
562 		PARG p;
563 		p.p_string = s;
564 		error("Invalid color string \"%s\"", &p);
565 	} else
566 	{
567 		*fg_color = fg;
568 		*bg_color = bg;
569 		*dattr = 0;
570 #if MSDOS_COMPILER==WIN32C
571 		if (attr & CATTR_UNDERLINE)
572 			*dattr |= COMMON_LVB_UNDERSCORE;
573 		if (attr & CATTR_STANDOUT)
574 			*dattr |= COMMON_LVB_REVERSE_VIDEO;
575 #endif
576 	}
577 }
578 #endif
579 
580 static int color_from_namechar(char namechar)
581 {
582 	switch (namechar)
583 	{
584 	case 'B': return AT_COLOR_BIN;
585 	case 'C': return AT_COLOR_CTRL;
586 	case 'E': return AT_COLOR_ERROR;
587 	case 'H': return AT_COLOR_HEADER;
588 	case 'M': return AT_COLOR_MARK;
589 	case 'N': return AT_COLOR_LINENUM;
590 	case 'P': return AT_COLOR_PROMPT;
591 	case 'R': return AT_COLOR_RSCROLL;
592 	case 'S': return AT_COLOR_SEARCH;
593 	case 'W': case 'A': return AT_COLOR_ATTN;
594 	case 'n': return AT_NORMAL;
595 	case 's': return AT_STANDOUT;
596 	case 'd': return AT_BOLD;
597 	case 'u': return AT_UNDERLINE;
598 	case 'k': return AT_BLINK;
599 	default:
600 		if (namechar >= '1' && namechar <= '0'+NUM_SEARCH_COLORS)
601 			return AT_COLOR_SUBSEARCH(namechar-'0');
602 		return -1;
603 	}
604 }
605 
606 /*
607  * Handler for the -D option.
608  */
609 	/*ARGSUSED*/
610 public void opt_D(int type, constant char *s)
611 {
612 	PARG p;
613 	int attr;
614 
615 	switch (type)
616 	{
617 	case INIT:
618 	case TOGGLE:
619 #if MSDOS_COMPILER
620 		if (*s == 'a')
621 		{
622 			sgr_mode = !sgr_mode;
623 			break;
624 		}
625 #endif
626 		attr = color_from_namechar(s[0]);
627 		if (attr < 0)
628 		{
629 			p.p_char = s[0];
630 			error("Invalid color specifier '%c'", &p);
631 			return;
632 		}
633 		if (!use_color && (attr & AT_COLOR))
634 		{
635 			error("Set --use-color before changing colors", NULL_PARG);
636 			return;
637 		}
638 		s++;
639 #if MSDOS_COMPILER
640 		if (!(attr & AT_COLOR))
641 		{
642 			switch (attr)
643 			{
644 			case AT_NORMAL:
645 				colordesc(s, &nm_fg_color, &nm_bg_color, &nm_attr);
646 				break;
647 			case AT_BOLD:
648 				colordesc(s, &bo_fg_color, &bo_bg_color, &bo_attr);
649 				break;
650 			case AT_UNDERLINE:
651 				colordesc(s, &ul_fg_color, &ul_bg_color, &ul_attr);
652 				break;
653 			case AT_BLINK:
654 				colordesc(s, &bl_fg_color, &bl_bg_color, &bl_attr);
655 				break;
656 			case AT_STANDOUT:
657 				colordesc(s, &so_fg_color, &so_bg_color, &so_attr);
658 				break;
659 			}
660 			if (type == TOGGLE)
661 			{
662 				init_win_colors();
663 				at_enter(AT_STANDOUT);
664 				at_exit();
665 			}
666 		} else
667 #endif
668 		if (set_color_map(attr, s) < 0)
669 		{
670 			p.p_string = s;
671 			error("Invalid color string \"%s\"", &p);
672 			return;
673 		}
674 		break;
675 #if MSDOS_COMPILER
676 	case QUERY:
677 		p.p_string = (sgr_mode) ? "on" : "off";
678 		error("SGR mode is %s", &p);
679 		break;
680 #endif
681 	}
682 }
683 
684 /*
685  */
686 public void set_tabs(constant char *s, size_t len)
687 {
688 	int i;
689 	constant char *es = s + len;
690 	/* Start at 1 because tabstops[0] is always zero. */
691 	for (i = 1;  i < TABSTOP_MAX;  )
692 	{
693 		int n = 0;
694 		lbool v = FALSE;
695 		while (s < es && *s == ' ')
696 			s++;
697 		for (; s < es && *s >= '0' && *s <= '9'; s++)
698 		{
699 			v = v || ckd_mul(&n, n, 10);
700 			v = v || ckd_add(&n, n, *s - '0');
701 		}
702 		if (!v && n > tabstops[i-1])
703 			tabstops[i++] = n;
704 		while (s < es && *s == ' ')
705 			s++;
706 		if (s == es || *s++ != ',')
707 			break;
708 	}
709 	if (i < 2)
710 		return;
711 	ntabstops = i;
712 	tabdefault = tabstops[ntabstops-1] - tabstops[ntabstops-2];
713 }
714 
715 /*
716  * Handler for the -x option.
717  */
718 public void opt_x(int type, constant char *s)
719 {
720 	char msg[60+((INT_STRLEN_BOUND(int)+1)*TABSTOP_MAX)];
721 	int i;
722 	PARG p;
723 
724 	switch (type)
725 	{
726 	case INIT:
727 	case TOGGLE:
728 		set_tabs(s, strlen(s));
729 		break;
730 	case QUERY:
731 		strcpy(msg, "Tab stops ");
732 		if (ntabstops > 2)
733 		{
734 			for (i = 1;  i < ntabstops;  i++)
735 			{
736 				if (i > 1)
737 					strcat(msg, ",");
738 				sprintf(msg+strlen(msg), "%d", tabstops[i]);
739 			}
740 			sprintf(msg+strlen(msg), " and then ");
741 		}
742 		sprintf(msg+strlen(msg), "every %d spaces",
743 			tabdefault);
744 		p.p_string = msg;
745 		error("%s", &p);
746 		break;
747 	}
748 }
749 
750 
751 /*
752  * Handler for the -" option.
753  */
754 public void opt_quote(int type, constant char *s)
755 {
756 	char buf[3];
757 	PARG parg;
758 
759 	switch (type)
760 	{
761 	case INIT:
762 	case TOGGLE:
763 		if (s[0] == '\0')
764 		{
765 			openquote = closequote = '\0';
766 			break;
767 		}
768 		if (s[1] != '\0' && s[2] != '\0')
769 		{
770 			error("-\" must be followed by 1 or 2 chars", NULL_PARG);
771 			return;
772 		}
773 		openquote = s[0];
774 		if (s[1] == '\0')
775 			closequote = openquote;
776 		else
777 			closequote = s[1];
778 		break;
779 	case QUERY:
780 		buf[0] = openquote;
781 		buf[1] = closequote;
782 		buf[2] = '\0';
783 		parg.p_string = buf;
784 		error("quotes %s", &parg);
785 		break;
786 	}
787 }
788 
789 /*
790  * Handler for the --rscroll option.
791  */
792 	/*ARGSUSED*/
793 public void opt_rscroll(int type, constant char *s)
794 {
795 	PARG p;
796 
797 	switch (type)
798 	{
799 	case INIT:
800 	case TOGGLE: {
801 		constant char *fmt;
802 		int attr = AT_STANDOUT;
803 		setfmt(s, &fmt, &attr, "*s>", FALSE);
804 		if (strcmp(fmt, "-") == 0)
805 		{
806 			rscroll_char = 0;
807 		} else
808 		{
809 			rscroll_attr = attr|AT_COLOR_RSCROLL;
810 			if (*fmt == '\0')
811 				rscroll_char = '>';
812 			else
813 			{
814 				LWCHAR ch = step_charc(&fmt, +1, fmt+strlen(fmt));
815 				if (pwidth(ch, rscroll_attr, 0, 0) > 1)
816 					error("cannot set rscroll to a wide character", NULL_PARG);
817 				else
818 					rscroll_char = ch;
819 			}
820 		}
821 		break; }
822 	case QUERY: {
823 		p.p_string = rscroll_char ? prchar((LWCHAR) rscroll_char) : "-";
824 		error("rscroll character is %s", &p);
825 		break; }
826 	}
827 }
828 
829 /*
830  * "-?" means display a help message.
831  * If from the command line, exit immediately.
832  */
833 	/*ARGSUSED*/
834 public void opt_query(int type, constant char *s)
835 {
836 	switch (type)
837 	{
838 	case QUERY:
839 	case TOGGLE:
840 		error("Use \"h\" for help", NULL_PARG);
841 		break;
842 	case INIT:
843 		dohelp = 1;
844 	}
845 }
846 
847 	/*ARGSUSED*/
848 public void opt_match_shift(int type, constant char *s)
849 {
850 	switch (type)
851 	{
852 	case INIT:
853 	case TOGGLE:
854 		toggle_fraction(&match_shift, &match_shift_fraction,
855 			s, "--match-shift", calc_match_shift);
856 		break;
857 	case QUERY:
858 		query_fraction(match_shift, match_shift_fraction,
859 			"Search match shift is %d", "Search match shift is %s of screen width");
860 		break;
861 	}
862 }
863 
864 public void calc_match_shift(void)
865 {
866 	if (match_shift_fraction < 0)
867 		return;
868 	match_shift = (int) muldiv(sc_width, match_shift_fraction, NUM_FRAC_DENOM);
869 }
870 
871 /*
872  * Handler for the --mouse option.
873  */
874 	/*ARGSUSED*/
875 public void opt_mousecap(int type, constant char *s)
876 {
877 	switch (type)
878 	{
879 	case TOGGLE:
880 		if (mousecap == OPT_OFF)
881 			deinit_mouse();
882 		else
883 			init_mouse();
884 		break;
885 	case INIT:
886 	case QUERY:
887 		break;
888 	}
889 }
890 
891 /*
892  * Handler for the --wheel-lines option.
893  */
894 	/*ARGSUSED*/
895 public void opt_wheel_lines(int type, constant char *s)
896 {
897 	switch (type)
898 	{
899 	case INIT:
900 	case TOGGLE:
901 		if (wheel_lines <= 0)
902 			wheel_lines = default_wheel_lines();
903 		break;
904 	case QUERY:
905 		break;
906 	}
907 }
908 
909 /*
910  * Handler for the --line-number-width option.
911  */
912 	/*ARGSUSED*/
913 public void opt_linenum_width(int type, constant char *s)
914 {
915 	PARG parg;
916 
917 	switch (type)
918 	{
919 	case INIT:
920 	case TOGGLE:
921 		if (linenum_width > MAX_LINENUM_WIDTH)
922 		{
923 			parg.p_int = MAX_LINENUM_WIDTH;
924 			error("Line number width must not be larger than %d", &parg);
925 			linenum_width = MIN_LINENUM_WIDTH;
926 		}
927 		break;
928 	case QUERY:
929 		break;
930 	}
931 }
932 
933 /*
934  * Handler for the --status-column-width option.
935  */
936 	/*ARGSUSED*/
937 public void opt_status_col_width(int type, constant char *s)
938 {
939 	PARG parg;
940 
941 	switch (type)
942 	{
943 	case INIT:
944 	case TOGGLE:
945 		if (status_col_width > MAX_STATUSCOL_WIDTH)
946 		{
947 			parg.p_int = MAX_STATUSCOL_WIDTH;
948 			error("Status column width must not be larger than %d", &parg);
949 			status_col_width = 2;
950 		}
951 		break;
952 	case QUERY:
953 		break;
954 	}
955 }
956 
957 /*
958  * Handler for the --file-size option.
959  */
960 	/*ARGSUSED*/
961 public void opt_filesize(int type, constant char *s)
962 {
963 	switch (type)
964 	{
965 	case INIT:
966 	case TOGGLE:
967 		if (want_filesize && curr_ifile != NULL && ch_length() == NULL_POSITION)
968 			scan_eof();
969 		break;
970 	case QUERY:
971 		break;
972 	}
973 }
974 
975 /*
976  * Handler for the --intr option.
977  */
978 	/*ARGSUSED*/
979 public void opt_intr(int type, constant char *s)
980 {
981 	PARG p;
982 
983 	switch (type)
984 	{
985 	case INIT:
986 	case TOGGLE:
987 		intr_char = *s;
988 		if (intr_char == '^' && s[1] != '\0')
989 			intr_char = CONTROL(s[1]);
990 		break;
991 	case QUERY: {
992 		p.p_string = prchar((LWCHAR) intr_char);
993 		error("interrupt character is %s", &p);
994 		break; }
995 	}
996 }
997 
998 /*
999  * Return the next number from a comma-separated list.
1000  * Return -1 if the list entry is missing or empty.
1001  * Updates *sp to point to the first char of the next number in the list.
1002  */
1003 public int next_cnum(constant char **sp, constant char *printopt, constant char *errmsg, lbool *errp)
1004 {
1005 	int n;
1006 	*errp = FALSE;
1007 	if (**sp == '\0') /* at end of line */
1008 		return -1;
1009 	if (**sp == ',') /* that's the next comma; we have an empty string */
1010 	{
1011 		++(*sp);
1012 		return -1;
1013 	}
1014 	n = getnumc(sp, printopt, errp);
1015 	if (*errp)
1016 	{
1017 		PARG parg;
1018 		parg.p_string = errmsg;
1019 		error("invalid %s", &parg);
1020 		return -1;
1021 	}
1022 	if (**sp == ',')
1023 		++(*sp);
1024 	return n;
1025 }
1026 
1027 /*
1028  * Parse a parameter to the --header option.
1029  * Value is "L,C,N", where each field is a decimal number or empty.
1030  */
1031 static lbool parse_header(constant char *s, int *lines, int *cols, POSITION *start_pos)
1032 {
1033 	int n;
1034 	lbool err;
1035 
1036 	if (*s == '-')
1037 		s = "0,0";
1038 
1039 	n = next_cnum(&s, "header", "number of lines", &err);
1040 	if (err) return FALSE;
1041 	if (n >= 0) *lines = n;
1042 
1043 	n = next_cnum(&s, "header", "number of columns", &err);
1044 	if (err) return FALSE;
1045 	if (n >= 0) *cols = n;
1046 
1047 	n = next_cnum(&s, "header", "line number", &err);
1048 	if (err) return FALSE;
1049 	if (n > 0)
1050 	{
1051 		LINENUM lnum = (LINENUM) n;
1052 		if (lnum < 1) lnum = 1;
1053 		*start_pos = find_pos(lnum);
1054 	}
1055 	return TRUE;
1056 }
1057 
1058 /*
1059  * Handler for the --header option.
1060  */
1061 	/*ARGSUSED*/
1062 public void opt_header(int type, constant char *s)
1063 {
1064 	switch (type)
1065 	{
1066 	case INIT:
1067 		/* Can't call parse_header now because input file is not yet opened,
1068 		 * so find_pos won't work. */
1069 		init_header = save(s);
1070 		break;
1071 	case TOGGLE: {
1072 		int lines = header_lines;
1073 		int cols = header_cols;
1074 		POSITION start_pos = (type == INIT) ? ch_zero() : position(TOP);
1075 		if (start_pos == NULL_POSITION) start_pos = ch_zero();
1076 		if (!parse_header(s, &lines, &cols, &start_pos))
1077 			break;
1078 		header_lines = lines;
1079 		header_cols = cols;
1080 		set_header(start_pos);
1081 		calc_jump_sline();
1082 		break; }
1083     case QUERY: {
1084         char buf[3*INT_STRLEN_BOUND(long)+3];
1085         PARG parg;
1086         SNPRINTF3(buf, sizeof(buf), "%ld,%ld,%ld", (long) header_lines, (long) header_cols, (long) find_linenum(header_start_pos));
1087         parg.p_string = buf;
1088         error("Header (lines,columns,line-number) is %s", &parg);
1089         break; }
1090 	}
1091 }
1092 
1093 /*
1094  * Handler for the --search-options option.
1095  */
1096 	/*ARGSUSED*/
1097 public void opt_search_type(int type, constant char *s)
1098 {
1099 	int st;
1100 	PARG parg;
1101 	char buf[16];
1102 	char *bp;
1103 	int i;
1104 
1105 	switch (type)
1106 	{
1107 	case INIT:
1108 	case TOGGLE:
1109 		st = 0;
1110 		for (;  *s != '\0';  s++)
1111 		{
1112 			switch (*s)
1113 			{
1114 			case 'E': case 'e': case CONTROL('E'): st |= SRCH_PAST_EOF;   break;
1115 			case 'F': case 'f': case CONTROL('F'): st |= SRCH_FIRST_FILE; break;
1116 			case 'K': case 'k': case CONTROL('K'): st |= SRCH_NO_MOVE;    break;
1117 			case 'N': case 'n': case CONTROL('N'): st |= SRCH_NO_MATCH;   break;
1118 			case 'R': case 'r': case CONTROL('R'): st |= SRCH_NO_REGEX;   break;
1119 			case 'W': case 'w': case CONTROL('W'): st |= SRCH_WRAP;       break;
1120 			case '-': st = 0; break;
1121 			case '^': break;
1122 			default:
1123 				if (*s >= '1' && *s <= '0'+NUM_SEARCH_COLORS)
1124 				{
1125 					st |= SRCH_SUBSEARCH(*s-'0');
1126 					break;
1127 				}
1128 				parg.p_char = *s;
1129 				error("invalid search option '%c'", &parg);
1130 				return;
1131 			}
1132 		}
1133 		def_search_type = norm_search_type(st);
1134 		break;
1135 	case QUERY:
1136 		bp = buf;
1137 		if (def_search_type & SRCH_PAST_EOF)   *bp++ = 'E';
1138 		if (def_search_type & SRCH_FIRST_FILE) *bp++ = 'F';
1139 		if (def_search_type & SRCH_NO_MOVE)    *bp++ = 'K';
1140 		if (def_search_type & SRCH_NO_MATCH)   *bp++ = 'N';
1141 		if (def_search_type & SRCH_NO_REGEX)   *bp++ = 'R';
1142 		if (def_search_type & SRCH_WRAP)       *bp++ = 'W';
1143 		for (i = 1;  i <= NUM_SEARCH_COLORS;  i++)
1144 			if (def_search_type & SRCH_SUBSEARCH(i))
1145 				*bp++ = (char) ('0'+i);
1146 		if (bp == buf)
1147 			*bp++ = '-';
1148 		*bp = '\0';
1149 		parg.p_string = buf;
1150 		error("search options: %s", &parg);
1151 		break;
1152 	}
1153 }
1154 
1155 /*
1156  * Handler for the --no-search-headers, --no-search-header-lines
1157  * and --no-search-header-cols options.
1158  */
1159 static void do_nosearch_headers(int type, int no_header_lines, int no_header_cols)
1160 {
1161 	switch (type)
1162 	{
1163 	case INIT:
1164 	case TOGGLE:
1165 		nosearch_header_lines = no_header_lines;
1166 		nosearch_header_cols = no_header_cols;
1167 		break;
1168 	case QUERY:
1169 		if (nosearch_header_lines && nosearch_header_cols)
1170 			error("Search does not include header lines or columns", NULL_PARG);
1171 		else if (nosearch_header_lines)
1172 			error("Search includes header columns but not header lines", NULL_PARG);
1173 		else if (nosearch_header_cols)
1174 			error("Search includes header lines but not header columns", NULL_PARG);
1175 		else
1176 			error("Search includes header lines and columns", NULL_PARG);
1177 	}
1178 }
1179 
1180 	/*ARGSUSED*/
1181 public void opt_nosearch_headers(int type, constant char *s)
1182 {
1183 	do_nosearch_headers(type, 1, 1);
1184 }
1185 
1186 	/*ARGSUSED*/
1187 public void opt_nosearch_header_lines(int type, constant char *s)
1188 {
1189 	do_nosearch_headers(type, 1, 0);
1190 }
1191 
1192 	/*ARGSUSED*/
1193 public void opt_nosearch_header_cols(int type, constant char *s)
1194 {
1195 	do_nosearch_headers(type, 0, 1);
1196 }
1197 
1198 #if LESSTEST
1199 /*
1200  * Handler for the --tty option.
1201  */
1202 	/*ARGSUSED*/
1203 public void opt_ttyin_name(int type, constant char *s)
1204 {
1205 	switch (type)
1206 	{
1207 	case INIT:
1208 		ttyin_name = s;
1209 		is_tty = 1;
1210 		break;
1211 	}
1212 }
1213 #endif /*LESSTEST*/
1214 
1215 public int chop_line(void)
1216 {
1217 	return (chopline || header_cols > 0 || header_lines > 0);
1218 }
1219 
1220 /*
1221  * Get the "screen window" size.
1222  */
1223 public int get_swindow(void)
1224 {
1225 	if (swindow > 0)
1226 		return (swindow);
1227 	return (sc_height - header_lines + swindow);
1228 }
1229 
1230