xref: /dragonfly/contrib/less/main.c (revision 7485684f)
1 /*
2  * Copyright (C) 1984-2023  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  * Entry point, initialization, miscellaneous routines.
13  */
14 
15 #include "less.h"
16 #if MSDOS_COMPILER==WIN32C
17 #define WIN32_LEAN_AND_MEAN
18 #include <windows.h>
19 #endif
20 
21 public char *   every_first_cmd = NULL;
22 public int      new_file;
23 public int      is_tty;
24 public IFILE    curr_ifile = NULL_IFILE;
25 public IFILE    old_ifile = NULL_IFILE;
26 public struct scrpos initial_scrpos;
27 public POSITION start_attnpos = NULL_POSITION;
28 public POSITION end_attnpos = NULL_POSITION;
29 public int      wscroll;
30 public char *   progname;
31 public int      quitting;
32 public int      secure;
33 public int      dohelp;
34 
35 #if LOGFILE
36 public int      logfile = -1;
37 public int      force_logfile = FALSE;
38 public char *   namelogfile = NULL;
39 #endif
40 
41 #if EDITOR
42 public char *   editor;
43 public char *   editproto;
44 #endif
45 
46 #if TAGS
47 extern char *   tags;
48 extern char *   tagoption;
49 extern int      jump_sline;
50 #endif
51 
52 #ifdef WIN32
53 static char consoleTitle[256];
54 #endif
55 
56 public int      one_screen;
57 extern int      less_is_more;
58 extern int      missing_cap;
59 extern int      know_dumb;
60 extern int      pr_type;
61 extern int      quit_if_one_screen;
62 extern int      no_init;
63 extern int      errmsgs;
64 extern int      redraw_on_quit;
65 extern int      term_init_done;
66 extern int      first_time;
67 
68 /*
69  * Entry point.
70  */
71 int main(int argc, char *argv[])
72 {
73 	IFILE ifile;
74 	char *s;
75 
76 #ifdef __EMX__
77 	_response(&argc, &argv);
78 	_wildcard(&argc, &argv);
79 #endif
80 
81 	progname = *argv++;
82 	argc--;
83 
84 #if SECURE
85 	secure = 1;
86 #else
87 	secure = 0;
88 	s = lgetenv("LESSSECURE");
89 	if (!isnullenv(s))
90 		secure = 1;
91 #endif
92 
93 #ifdef WIN32
94 	if (getenv("HOME") == NULL)
95 	{
96 		/*
97 		 * If there is no HOME environment variable,
98 		 * try the concatenation of HOMEDRIVE + HOMEPATH.
99 		 */
100 		char *drive = getenv("HOMEDRIVE");
101 		char *path  = getenv("HOMEPATH");
102 		if (drive != NULL && path != NULL)
103 		{
104 			char *env = (char *) ecalloc(strlen(drive) +
105 					strlen(path) + 6, sizeof(char));
106 			strcpy(env, "HOME=");
107 			strcat(env, drive);
108 			strcat(env, path);
109 			putenv(env);
110 		}
111 	}
112 	GetConsoleTitle(consoleTitle, sizeof(consoleTitle)/sizeof(char));
113 #endif /* WIN32 */
114 
115 	/*
116 	 * Process command line arguments and LESS environment arguments.
117 	 * Command line arguments override environment arguments.
118 	 */
119 	is_tty = isatty(1);
120 	init_mark();
121 	init_cmds();
122 	init_poll();
123 	get_term();
124 	init_charset();
125 	init_line();
126 	init_cmdhist();
127 	init_option();
128 	init_search();
129 
130 	/*
131 	 * If the name of the executable program is "more",
132 	 * act like LESS_IS_MORE is set.
133 	 */
134 	s = last_component(progname);
135 	if (strcmp(s, "more") == 0)
136 		less_is_more = 1;
137 
138 	init_prompt();
139 
140 	s = lgetenv(less_is_more ? "MORE" : "LESS");
141 	if (s != NULL)
142 		scan_option(s);
143 
144 #define isoptstring(s)  (((s)[0] == '-' || (s)[0] == '+') && (s)[1] != '\0')
145 	while (argc > 0 && (isoptstring(*argv) || isoptpending()))
146 	{
147 		s = *argv++;
148 		argc--;
149 		if (strcmp(s, "--") == 0)
150 			break;
151 		scan_option(s);
152 	}
153 #undef isoptstring
154 
155 	if (isoptpending())
156 	{
157 		/*
158 		 * Last command line option was a flag requiring a
159 		 * following string, but there was no following string.
160 		 */
161 		nopendopt();
162 		quit(QUIT_OK);
163 	}
164 
165 	expand_cmd_tables();
166 
167 #if EDITOR
168 	editor = lgetenv("VISUAL");
169 	if (editor == NULL || *editor == '\0')
170 	{
171 		editor = lgetenv("EDITOR");
172 		if (isnullenv(editor))
173 			editor = EDIT_PGM;
174 	}
175 	editproto = lgetenv("LESSEDIT");
176 	if (isnullenv(editproto))
177 		editproto = "%E ?lm+%lm. %g";
178 #endif
179 
180 	/*
181 	 * Call get_ifile with all the command line filenames
182 	 * to "register" them with the ifile system.
183 	 */
184 	ifile = NULL_IFILE;
185 	if (dohelp)
186 		ifile = get_ifile(FAKE_HELPFILE, ifile);
187 	while (argc-- > 0)
188 	{
189 #if (MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC)
190 		/*
191 		 * Because the "shell" doesn't expand filename patterns,
192 		 * treat each argument as a filename pattern rather than
193 		 * a single filename.
194 		 * Expand the pattern and iterate over the expanded list.
195 		 */
196 		struct textlist tlist;
197 		char *filename;
198 		char *gfilename;
199 		char *qfilename;
200 
201 		gfilename = lglob(*argv++);
202 		init_textlist(&tlist, gfilename);
203 		filename = NULL;
204 		while ((filename = forw_textlist(&tlist, filename)) != NULL)
205 		{
206 			qfilename = shell_unquote(filename);
207 			(void) get_ifile(qfilename, ifile);
208 			free(qfilename);
209 			ifile = prev_ifile(NULL_IFILE);
210 		}
211 		free(gfilename);
212 #else
213 		(void) get_ifile(*argv++, ifile);
214 		ifile = prev_ifile(NULL_IFILE);
215 #endif
216 	}
217 	/*
218 	 * Set up terminal, etc.
219 	 */
220 	if (!is_tty)
221 	{
222 		/*
223 		 * Output is not a tty.
224 		 * Just copy the input file(s) to output.
225 		 */
226 		set_output(1); /* write to stdout */
227 		SET_BINARY(1);
228 		if (edit_first() == 0)
229 		{
230 			do {
231 				cat_file();
232 			} while (edit_next(1) == 0);
233 		}
234 		quit(QUIT_OK);
235 	}
236 
237 	if (missing_cap && !know_dumb)
238 		error("WARNING: terminal is not fully functional", NULL_PARG);
239 	open_getchr();
240 	raw_mode(1);
241 	init_signals(1);
242 
243 	/*
244 	 * Select the first file to examine.
245 	 */
246 #if TAGS
247 	if (tagoption != NULL || strcmp(tags, "-") == 0)
248 	{
249 		/*
250 		 * A -t option was given.
251 		 * Verify that no filenames were also given.
252 		 * Edit the file selected by the "tags" search,
253 		 * and search for the proper line in the file.
254 		 */
255 		if (nifile() > 0)
256 		{
257 			error("No filenames allowed with -t option", NULL_PARG);
258 			quit(QUIT_ERROR);
259 		}
260 		findtag(tagoption);
261 		if (edit_tagfile())  /* Edit file which contains the tag */
262 			quit(QUIT_ERROR);
263 		/*
264 		 * Search for the line which contains the tag.
265 		 * Set up initial_scrpos so we display that line.
266 		 */
267 		initial_scrpos.pos = tagsearch();
268 		if (initial_scrpos.pos == NULL_POSITION)
269 			quit(QUIT_ERROR);
270 		initial_scrpos.ln = jump_sline;
271 	} else
272 #endif
273 	{
274 		if (edit_first())
275 			quit(QUIT_ERROR);
276 		/*
277 		 * See if file fits on one screen to decide whether
278 		 * to send terminal init. But don't need this
279 		 * if -X (no_init) overrides this (see init()).
280 		 */
281 		if (quit_if_one_screen)
282 		{
283 			if (nifile() > 1) /* If more than one file, -F cannot be used */
284 				quit_if_one_screen = FALSE;
285 			else if (!no_init)
286 				one_screen = get_one_screen();
287 		}
288 	}
289 
290 	if (errmsgs > 0)
291 	{
292 		/*
293 		 * We displayed some messages on error output
294 		 * (file descriptor 2; see flush()).
295 		 * Before erasing the screen contents, wait for a keystroke.
296 		 */
297 		less_printf("Press RETURN to continue ", NULL_PARG);
298 		get_return();
299 		putchr('\n');
300 	}
301 	set_output(1);
302 	init();
303 	commands();
304 	quit(QUIT_OK);
305 	/*NOTREACHED*/
306 	return (0);
307 }
308 
309 /*
310  * Copy a string to a "safe" place
311  * (that is, to a buffer allocated by calloc).
312  */
313 public char * save(constant char *s)
314 {
315 	char *p;
316 
317 	p = (char *) ecalloc(strlen(s)+1, sizeof(char));
318 	strcpy(p, s);
319 	return (p);
320 }
321 
322 public void out_of_memory(void)
323 {
324 	error("Cannot allocate memory", NULL_PARG);
325 	quit(QUIT_ERROR);
326 }
327 
328 /*
329  * Allocate memory.
330  * Like calloc(), but never returns an error (NULL).
331  */
332 public void * ecalloc(int count, unsigned int size)
333 {
334 	void * p;
335 
336 	p = (void *) calloc(count, size);
337 	if (p == NULL)
338 		out_of_memory();
339 	return p;
340 }
341 
342 /*
343  * Skip leading spaces in a string.
344  */
345 public char * skipsp(char *s)
346 {
347 	while (*s == ' ' || *s == '\t')
348 		s++;
349 	return (s);
350 }
351 
352 /*
353  * See how many characters of two strings are identical.
354  * If uppercase is true, the first string must begin with an uppercase
355  * character; the remainder of the first string may be either case.
356  */
357 public int sprefix(char *ps, char *s, int uppercase)
358 {
359 	int c;
360 	int sc;
361 	int len = 0;
362 
363 	for ( ;  *s != '\0';  s++, ps++)
364 	{
365 		c = *ps;
366 		if (uppercase)
367 		{
368 			if (len == 0 && ASCII_IS_LOWER(c))
369 				return (-1);
370 			if (ASCII_IS_UPPER(c))
371 				c = ASCII_TO_LOWER(c);
372 		}
373 		sc = *s;
374 		if (len > 0 && ASCII_IS_UPPER(sc))
375 			sc = ASCII_TO_LOWER(sc);
376 		if (c != sc)
377 			break;
378 		len++;
379 	}
380 	return (len);
381 }
382 
383 /*
384  * Exit the program.
385  */
386 public void quit(int status)
387 {
388 	static int save_status;
389 
390 	/*
391 	 * Put cursor at bottom left corner, clear the line,
392 	 * reset the terminal modes, and exit.
393 	 */
394 	if (status < 0)
395 		status = save_status;
396 	else
397 		save_status = status;
398 	quitting = 1;
399 	check_altpipe_error();
400 	if (interactive())
401 		clear_bot();
402 	deinit();
403 	flush();
404 	if (redraw_on_quit && term_init_done)
405 	{
406 		/*
407 		 * The last file text displayed might have been on an
408 		 * alternate screen, which now (since deinit) cannot be seen.
409 		 * redraw_on_quit tells us to redraw it on the main screen.
410 		 */
411 		first_time = 1; /* Don't print "skipping" or tildes */
412 		repaint();
413 		flush();
414 	}
415 	edit((char*)NULL);
416 	save_cmdhist();
417 	raw_mode(0);
418 #if MSDOS_COMPILER && MSDOS_COMPILER != DJGPPC
419 	/*
420 	 * If we don't close 2, we get some garbage from
421 	 * 2's buffer when it flushes automatically.
422 	 * I cannot track this one down  RB
423 	 * The same bug shows up if we use ^C^C to abort.
424 	 */
425 	close(2);
426 #endif
427 #ifdef WIN32
428 	SetConsoleTitle(consoleTitle);
429 #endif
430 	close_getchr();
431 	exit(status);
432 }
433