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