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
30extern int nbufs;
31extern int bufspace;
32extern int pr_type;
33extern int plusoption;
34extern int swindow;
35extern int sc_width;
36extern int sc_height;
37extern int secure;
38extern int dohelp;
39extern int is_tty;
40extern char openquote;
41extern char closequote;
42extern char *prproto[];
43extern char *eqproto;
44extern char *hproto;
45extern char *wproto;
46extern char *every_first_cmd;
47extern IFILE curr_ifile;
48extern char version[];
49extern int jump_sline;
50extern long jump_sline_fraction;
51extern int shift_count;
52extern long shift_count_fraction;
53extern char rscroll_char;
54extern int rscroll_attr;
55extern int mousecap;
56extern int wheel_lines;
57extern int less_is_more;
58extern int linenum_width;
59extern int status_col_width;
60extern int use_color;
61#if LOGFILE
62extern char *namelogfile;
63extern int force_logfile;
64extern int logfile;
65#endif
66#if TAGS
67public char *tagoption = NULL;
68extern char *tags;
69extern char ztags[];
70#endif
71#if LESSTEST
72extern char *ttyin_name;
73extern int rstat_file;
74#endif /*LESSTEST*/
75#if MSDOS_COMPILER
76extern int nm_fg_color, nm_bg_color;
77extern int bo_fg_color, bo_bg_color;
78extern int ul_fg_color, ul_bg_color;
79extern int so_fg_color, so_bg_color;
80extern int bl_fg_color, bl_bg_color;
81extern 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
95opt_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
148opt__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
161opt_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
215calc_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
226opt_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
279calc_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
288opt_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
312opt_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
353opt__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
385opt_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
421opt__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
460opt_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
483opt_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
503opt__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
546colordesc(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
588color_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
616opt_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
695opt_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
753opt_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
795opt_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
830opt_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
850opt_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
873opt_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
894opt_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
921opt_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
949opt_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
967opt_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
990get_swindow(VOID_PARAM)
991{
992	if (swindow > 0)
993		return (swindow);
994	return (sc_height + swindow);
995}
996
997