xref: /dragonfly/contrib/less/edit.c (revision e433da38)
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 #include "less.h"
12 #include "position.h"
13 #if HAVE_STAT
14 #include <sys/stat.h>
15 #endif
16 #if HAVE_SYS_WAIT_H
17 #include <sys/wait.h>
18 #endif
19 #if OS2 || __MVS__ || (defined WIFSIGNALED && defined WTERMSIG)
20 #include <signal.h>
21 #endif
22 
23 public int fd0 = 0;
24 
25 extern lbool new_file;
26 extern char *every_first_cmd;
27 extern int force_open;
28 extern int is_tty;
29 extern int sigs;
30 extern int hshift;
31 extern int want_filesize;
32 extern int consecutive_nulls;
33 extern int modelines;
34 extern int show_preproc_error;
35 extern IFILE curr_ifile;
36 extern IFILE old_ifile;
37 extern struct scrpos initial_scrpos;
38 extern void *ml_examine;
39 #if SPACES_IN_FILENAMES
40 extern char openquote;
41 extern char closequote;
42 #endif
43 
44 #if LOGFILE
45 extern int logfile;
46 extern int force_logfile;
47 extern char *namelogfile;
48 #endif
49 
50 #if HAVE_STAT_INO
51 public dev_t curr_dev;
52 public ino_t curr_ino;
53 #endif
54 
55 /*
56  * Textlist functions deal with a list of words separated by spaces.
57  * init_textlist sets up a textlist structure.
58  * forw_textlist uses that structure to iterate thru the list of
59  * words, returning each one as a standard null-terminated string.
60  * back_textlist does the same, but runs thru the list backwards.
61  */
init_textlist(struct textlist * tlist,mutable char * str)62 public void init_textlist(struct textlist *tlist, mutable char *str)
63 {
64 	char *s;
65 #if SPACES_IN_FILENAMES
66 	lbool meta_quoted = FALSE;
67 	lbool delim_quoted = FALSE;
68 	constant char *esc = get_meta_escape();
69 	size_t esclen = strlen(esc);
70 #endif
71 
72 	tlist->string = skipsp(str);
73 	tlist->endstring = tlist->string + strlen(tlist->string);
74 	for (s = str;  s < tlist->endstring;  s++)
75 	{
76 #if SPACES_IN_FILENAMES
77 		if (meta_quoted)
78 		{
79 			meta_quoted = 0;
80 		} else if (esclen > 0 && s + esclen < tlist->endstring &&
81 		           strncmp(s, esc, esclen) == 0)
82 		{
83 			meta_quoted = 1;
84 			s += esclen - 1;
85 		} else if (delim_quoted)
86 		{
87 			if (*s == closequote)
88 				delim_quoted = 0;
89 		} else /* (!delim_quoted) */
90 		{
91 			if (*s == openquote)
92 				delim_quoted = 1;
93 			else if (*s == ' ')
94 				*s = '\0';
95 		}
96 #else
97 		if (*s == ' ')
98 			*s = '\0';
99 #endif
100 	}
101 }
102 
forw_textlist(struct textlist * tlist,constant char * prev)103 public constant char * forw_textlist(struct textlist *tlist, constant char *prev)
104 {
105 	constant char *s;
106 
107 	/*
108 	 * prev == NULL means return the first word in the list.
109 	 * Otherwise, return the word after "prev".
110 	 */
111 	if (prev == NULL)
112 		s = tlist->string;
113 	else
114 		s = prev + strlen(prev);
115 	if (s >= tlist->endstring)
116 		return (NULL);
117 	while (*s == '\0')
118 		s++;
119 	if (s >= tlist->endstring)
120 		return (NULL);
121 	return (s);
122 }
123 
back_textlist(struct textlist * tlist,constant char * prev)124 public constant char * back_textlist(struct textlist *tlist, constant char *prev)
125 {
126 	constant char *s;
127 
128 	/*
129 	 * prev == NULL means return the last word in the list.
130 	 * Otherwise, return the word before "prev".
131 	 */
132 	if (prev == NULL)
133 		s = tlist->endstring;
134 	else if (prev <= tlist->string)
135 		return (NULL);
136 	else
137 		s = prev - 1;
138 	while (*s == '\0')
139 		s--;
140 	if (s <= tlist->string)
141 		return (NULL);
142 	while (s[-1] != '\0' && s > tlist->string)
143 		s--;
144 	return (s);
145 }
146 
147 /*
148  * Parse a single option setting in a modeline.
149  */
modeline_option(constant char * str,size_t opt_len)150 static void modeline_option(constant char *str, size_t opt_len)
151 {
152 	struct mloption { constant char *opt_name; void (*opt_func)(constant char*,size_t); };
153 	struct mloption options[] = {
154 		{ "ts=",         set_tabs },
155 		{ "tabstop=",    set_tabs },
156 		{ NULL, NULL }
157 	};
158 	struct mloption *opt;
159 	for (opt = options;  opt->opt_name != NULL;  opt++)
160 	{
161 		size_t name_len = strlen(opt->opt_name);
162 		if (opt_len > name_len && strncmp(str, opt->opt_name, name_len) == 0)
163 		{
164 			(*opt->opt_func)(str + name_len, opt_len - name_len);
165 			break;
166 		}
167 	}
168 }
169 
170 /*
171  * String length, terminated by option separator (space or colon).
172  * Space/colon can be escaped with backspace.
173  */
modeline_option_len(constant char * str)174 static size_t modeline_option_len(constant char *str)
175 {
176 	lbool esc = FALSE;
177 	constant char *s;
178 	for (s = str;  *s != '\0';  s++)
179 	{
180 		if (esc)
181 			esc = FALSE;
182 		else if (*s == '\\')
183 			esc = TRUE;
184 		else if (*s == ' ' || *s == ':') /* separator */
185 			break;
186 	}
187 	return ptr_diff(s, str);
188 }
189 
190 /*
191  * Parse colon- or space-separated option settings in a modeline.
192  */
modeline_options(constant char * str,char end_char)193 static void modeline_options(constant char *str, char end_char)
194 {
195 	for (;;)
196 	{
197 		size_t opt_len;
198 		str = skipspc(str);
199 		if (*str == '\0' || *str == end_char)
200 			break;
201 		opt_len = modeline_option_len(str);
202 		modeline_option(str, opt_len);
203 		str += opt_len;
204 		if (*str != '\0')
205 			str += 1; /* skip past the separator */
206 	}
207 }
208 
209 /*
210  * See if there is a modeline string in a line.
211  */
check_modeline(constant char * line)212 static void check_modeline(constant char *line)
213 {
214 #if HAVE_STRSTR
215 	static constant char *pgms[] = { "less:", "vim:", "vi:", "ex:", NULL };
216 	constant char **pgm;
217 	for (pgm = pgms;  *pgm != NULL;  ++pgm)
218 	{
219 		constant char *pline = line;
220 		for (;;)
221 		{
222 			constant char *str;
223 			pline = strstr(pline, *pgm);
224 			if (pline == NULL) /* pgm is not in this line */
225 				break;
226 			str = skipspc(pline + strlen(*pgm));
227 			if (pline == line || pline[-1] == ' ')
228 			{
229 				if (strncmp(str, "set ", 4) == 0)
230 					modeline_options(str+4, ':');
231 				else if (pgm != &pgms[0]) /* "less:" requires "set" */
232 					modeline_options(str, '\0');
233 				break;
234 			}
235 			/* Continue searching the rest of the line. */
236 			pline = str;
237 		}
238 	}
239 #endif /* HAVE_STRSTR */
240 }
241 
242 /*
243  * Read lines from start of file and check if any are modelines.
244  */
check_modelines(void)245 static void check_modelines(void)
246 {
247 	POSITION pos = ch_zero();
248 	int i;
249 	for (i = 0;  i < modelines;  i++)
250 	{
251 		constant char *line;
252 		size_t line_len;
253 		if (ABORT_SIGS())
254 			return;
255 		pos = forw_raw_line(pos, &line, &line_len);
256 		if (pos == NULL_POSITION)
257 			break;
258 		check_modeline(line);
259 	}
260 }
261 
262 /*
263  * Close a pipe opened via popen.
264  */
close_pipe(FILE * pipefd)265 static void close_pipe(FILE *pipefd)
266 {
267 	int status;
268 	char *p;
269 	PARG parg;
270 
271 	if (pipefd == NULL)
272 		return;
273 #if OS2
274 	/*
275 	 * The pclose function of OS/2 emx sometimes fails.
276 	 * Send SIGINT to the piped process before closing it.
277 	 */
278 	kill(pipefd->_pid, SIGINT);
279 #endif
280 	status = pclose(pipefd);
281 	if (status == -1)
282 	{
283 		/* An internal error in 'less', not a preprocessor error.  */
284 		p = errno_message("pclose");
285 		parg.p_string = p;
286 		error("%s", &parg);
287 		free(p);
288 		return;
289 	}
290 	if (!show_preproc_error)
291 		return;
292 #if defined WIFEXITED && defined WEXITSTATUS
293 	if (WIFEXITED(status))
294 	{
295 		int s = WEXITSTATUS(status);
296 		if (s != 0)
297 		{
298 			parg.p_int = s;
299 			error("Input preprocessor failed (status %d)", &parg);
300 		}
301 		return;
302 	}
303 #endif
304 #if defined WIFSIGNALED && defined WTERMSIG
305 	if (WIFSIGNALED(status))
306 	{
307 		int sig = WTERMSIG(status);
308 		if (sig != SIGPIPE || ch_length() != NULL_POSITION)
309 		{
310 			parg.p_string = signal_message(sig);
311 			error("Input preprocessor terminated: %s", &parg);
312 		}
313 		return;
314 	}
315 #endif
316 	if (status != 0)
317 	{
318 		parg.p_int = status;
319 		error("Input preprocessor exited with status %x", &parg);
320 	}
321 }
322 
323 /*
324  * Drain and close an input pipe if needed.
325  */
close_altpipe(IFILE ifile)326 public void close_altpipe(IFILE ifile)
327 {
328 	FILE *altpipe = get_altpipe(ifile);
329 	if (altpipe != NULL && !(ch_getflags() & CH_KEEPOPEN))
330 	{
331 		close_pipe(altpipe);
332 		set_altpipe(ifile, NULL);
333 	}
334 }
335 
336 /*
337  * Check for error status from the current altpipe.
338  * May or may not close the pipe.
339  */
check_altpipe_error(void)340 public void check_altpipe_error(void)
341 {
342 	if (!show_preproc_error)
343 		return;
344 	if (curr_ifile != NULL_IFILE && get_altfilename(curr_ifile) != NULL)
345 		close_altpipe(curr_ifile);
346 }
347 
348 /*
349  * Close the current input file.
350  */
close_file(void)351 static void close_file(void)
352 {
353 	struct scrpos scrpos;
354 	constant char *altfilename;
355 
356 	if (curr_ifile == NULL_IFILE)
357 		return;
358 
359 	/*
360 	 * Save the current position so that we can return to
361 	 * the same position if we edit this file again.
362 	 */
363 	get_scrpos(&scrpos, TOP);
364 	if (scrpos.pos != NULL_POSITION)
365 	{
366 		store_pos(curr_ifile, &scrpos);
367 		lastmark();
368 	}
369 	/*
370 	 * Close the file descriptor, unless it is a pipe.
371 	 */
372 	ch_close();
373 	/*
374 	 * If we opened a file using an alternate name,
375 	 * do special stuff to close it.
376 	 */
377 	altfilename = get_altfilename(curr_ifile);
378 	if (altfilename != NULL)
379 	{
380 		close_altpipe(curr_ifile);
381 		close_altfile(altfilename, get_filename(curr_ifile));
382 		set_altfilename(curr_ifile, NULL);
383 	}
384 	curr_ifile = NULL_IFILE;
385 #if HAVE_STAT_INO
386 	curr_ino = curr_dev = 0;
387 #endif
388 }
389 
390 /*
391  * Edit a new file (given its name).
392  * Filename == "-" means standard input.
393  * Filename == NULL means just close the current file.
394  */
edit(constant char * filename)395 public int edit(constant char *filename)
396 {
397 	if (filename == NULL)
398 		return (edit_ifile(NULL_IFILE));
399 	return (edit_ifile(get_ifile(filename, curr_ifile)));
400 }
401 
402 /*
403  * Clean up what edit_ifile did before error return.
404  */
edit_error(constant char * filename,constant char * alt_filename,void * altpipe,IFILE ifile)405 static int edit_error(constant char *filename, constant char *alt_filename, void *altpipe, IFILE ifile)
406 {
407 	if (alt_filename != NULL)
408 	{
409 		close_pipe(altpipe);
410 		close_altfile(alt_filename, filename);
411 		free((char*)alt_filename); /* FIXME: WTF? */
412 	}
413 	del_ifile(ifile);
414 	/*
415 	 * Re-open the current file.
416 	 */
417 	if (curr_ifile == ifile)
418 	{
419 		/*
420 		 * Whoops.  The "current" ifile is the one we just deleted.
421 		 * Just give up.
422 		 */
423 		quit(QUIT_ERROR);
424 	}
425 	return (1);
426 }
427 
428 /*
429  * Edit a new file (given its IFILE).
430  * ifile == NULL means just close the current file.
431  */
edit_ifile(IFILE ifile)432 public int edit_ifile(IFILE ifile)
433 {
434 	int f;
435 	int answer;
436 	int chflags;
437 	constant char *filename;
438 	constant char *open_filename;
439 	char *alt_filename;
440 	void *altpipe;
441 	IFILE was_curr_ifile;
442 	char *p;
443 	PARG parg;
444 	ssize_t nread;
445 
446 	if (ifile == curr_ifile)
447 	{
448 		/*
449 		 * Already have the correct file open.
450 		 */
451 		return (0);
452 	}
453 	new_file = TRUE;
454 
455 	if (ifile != NULL_IFILE)
456 	{
457 		/*
458 		 * See if LESSOPEN specifies an "alternate" file to open.
459 		 */
460 		filename = get_filename(ifile);
461 		altpipe = get_altpipe(ifile);
462 		if (altpipe != NULL)
463 		{
464 			/*
465 			 * File is already open.
466 			 * chflags and f are not used by ch_init if ifile has
467 			 * filestate which should be the case if we're here.
468 			 * Set them here to avoid uninitialized variable warnings.
469 			 */
470 			chflags = 0;
471 			f = -1;
472 			alt_filename = get_altfilename(ifile);
473 			open_filename = (alt_filename != NULL) ? alt_filename : filename;
474 		} else
475 		{
476 			if (strcmp(filename, FAKE_HELPFILE) == 0 ||
477 				strcmp(filename, FAKE_EMPTYFILE) == 0)
478 				alt_filename = NULL;
479 			else
480 				alt_filename = open_altfile(filename, &f, &altpipe);
481 
482 			open_filename = (alt_filename != NULL) ? alt_filename : filename;
483 
484 			chflags = 0;
485 			if (altpipe != NULL)
486 			{
487 				/*
488 				 * The alternate "file" is actually a pipe.
489 				 * f has already been set to the file descriptor of the pipe
490 				 * in the call to open_altfile above.
491 				 * Keep the file descriptor open because it was opened
492 				 * via popen(), and pclose() wants to close it.
493 				 */
494 				chflags |= CH_POPENED;
495 				if (strcmp(filename, "-") == 0)
496 					chflags |= CH_KEEPOPEN;
497 			} else if (strcmp(filename, "-") == 0)
498 			{
499 				/*
500 				 * Use standard input.
501 				 * Keep the file descriptor open because we can't reopen it.
502 				 */
503 				f = fd0;
504 				chflags |= CH_KEEPOPEN;
505 				/*
506 				 * Must switch stdin to BINARY mode.
507 				 */
508 				SET_BINARY(f);
509 #if MSDOS_COMPILER==DJGPPC
510 				/*
511 				 * Setting stdin to binary by default causes
512 				 * Ctrl-C to not raise SIGINT.  We must undo
513 				 * that side-effect.
514 				 */
515 				__djgpp_set_ctrl_c(1);
516 #endif
517 			} else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0)
518 			{
519 				f = -1;
520 				chflags |= CH_NODATA;
521 			} else if (strcmp(open_filename, FAKE_HELPFILE) == 0)
522 			{
523 				f = -1;
524 				chflags |= CH_HELPFILE;
525 			} else if ((p = bad_file(open_filename)) != NULL)
526 			{
527 				/*
528 				 * It looks like a bad file.  Don't try to open it.
529 				 */
530 				parg.p_string = p;
531 				error("%s", &parg);
532 				free(p);
533 				return edit_error(filename, alt_filename, altpipe, ifile);
534 			} else if ((f = open(open_filename, OPEN_READ)) < 0)
535 			{
536 				/*
537 				 * Got an error trying to open it.
538 				 */
539 				char *p = errno_message(filename);
540 				parg.p_string = p;
541 				error("%s", &parg);
542 				free(p);
543 				return edit_error(filename, alt_filename, altpipe, ifile);
544 			} else
545 			{
546 				chflags |= CH_CANSEEK;
547 				if (bin_file(f, &nread) && !force_open && !opened(ifile))
548 				{
549 					/*
550 					 * Looks like a binary file.
551 					 * Ask user if we should proceed.
552 					 */
553 					parg.p_string = filename;
554 					answer = query("\"%s\" may be a binary file.  See it anyway? ", &parg);
555 					if (answer != 'y' && answer != 'Y')
556 					{
557 						close(f);
558 						return edit_error(filename, alt_filename, altpipe, ifile);
559 					}
560 				}
561 			}
562 		}
563 		if (!force_open && f >= 0 && isatty(f))
564 		{
565 			PARG parg;
566 			parg.p_string = filename;
567 			error("%s is a terminal (use -f to open it)", &parg);
568 			return edit_error(filename, alt_filename, altpipe, ifile);
569 		}
570 	}
571 
572 #if LOGFILE
573 	end_logfile();
574 #endif
575 	was_curr_ifile = save_curr_ifile();
576 	if (curr_ifile != NULL_IFILE)
577 	{
578 		int was_helpfile = (ch_getflags() & CH_HELPFILE);
579 		close_file();
580 		if (was_helpfile && held_ifile(was_curr_ifile) <= 1)
581 		{
582 			/*
583 			 * Don't keep the help file in the ifile list.
584 			 */
585 			del_ifile(was_curr_ifile);
586 			was_curr_ifile = NULL_IFILE;
587 		}
588 	}
589 	unsave_ifile(was_curr_ifile);
590 
591 	if (ifile == NULL_IFILE)
592 	{
593 		/*
594 		 * No new file to open.
595 		 * (Don't set old_ifile, because if you call edit_ifile(NULL),
596 		 *  you're supposed to have saved curr_ifile yourself,
597 		 *  and you'll restore it if necessary.)
598 		 */
599 		return (0);
600 	}
601 
602 	/*
603 	 * Set up the new ifile.
604 	 * Get the saved position for the file.
605 	 */
606 	curr_ifile = ifile;
607 	set_altfilename(curr_ifile, alt_filename);
608 	set_altpipe(curr_ifile, altpipe);
609 	set_open(curr_ifile); /* File has been opened */
610 	get_pos(curr_ifile, &initial_scrpos);
611 	ch_init(f, chflags, nread);
612 	consecutive_nulls = 0;
613 	check_modelines();
614 
615 	if (!(chflags & CH_HELPFILE))
616 	{
617 		if (was_curr_ifile != NULL_IFILE)
618 			old_ifile = was_curr_ifile;
619 #if LOGFILE
620 		if (namelogfile != NULL && is_tty)
621 			use_logfile(namelogfile);
622 #endif
623 #if HAVE_STAT_INO
624 		/* Remember the i-number and device of the opened file. */
625 		if (strcmp(open_filename, "-") != 0)
626 		{
627 			struct stat statbuf;
628 			int r = stat(open_filename, &statbuf);
629 			if (r == 0)
630 			{
631 				curr_ino = statbuf.st_ino;
632 				curr_dev = statbuf.st_dev;
633 			}
634 		}
635 #endif
636 		if (every_first_cmd != NULL)
637 		{
638 			ungetsc(every_first_cmd);
639 			ungetcc_end_command();
640 		}
641 	}
642 
643 	flush();
644 
645 	if (is_tty)
646 	{
647 		/*
648 		 * Output is to a real tty.
649 		 */
650 
651 		/*
652 		 * Indicate there is nothing displayed yet.
653 		 */
654 		pos_clear();
655 		clr_linenum();
656 #if HILITE_SEARCH
657 		clr_hilite();
658 #endif
659 		hshift = 0;
660 		if (strcmp(filename, FAKE_HELPFILE) && strcmp(filename, FAKE_EMPTYFILE))
661 		{
662 			char *qfilename = shell_quote(filename);
663 			cmd_addhist(ml_examine, qfilename, 1);
664 			free(qfilename);
665 		}
666 		if (want_filesize)
667 			scan_eof();
668 		set_header(ch_zero());
669 	}
670 	return (0);
671 }
672 
673 /*
674  * Edit a space-separated list of files.
675  * For each filename in the list, enter it into the ifile list.
676  * Then edit the first one.
677  */
edit_list(char * filelist)678 public int edit_list(char *filelist)
679 {
680 	IFILE save_ifile;
681 	constant char *good_filename;
682 	constant char *filename;
683 	char *gfilelist;
684 	constant char *gfilename;
685 	char *qfilename;
686 	struct textlist tl_files;
687 	struct textlist tl_gfiles;
688 
689 	save_ifile = save_curr_ifile();
690 	good_filename = NULL;
691 
692 	/*
693 	 * Run thru each filename in the list.
694 	 * Try to glob the filename.
695 	 * If it doesn't expand, just try to open the filename.
696 	 * If it does expand, try to open each name in that list.
697 	 */
698 	init_textlist(&tl_files, filelist);
699 	filename = NULL;
700 	while ((filename = forw_textlist(&tl_files, filename)) != NULL)
701 	{
702 		gfilelist = lglob(filename);
703 		init_textlist(&tl_gfiles, gfilelist);
704 		gfilename = NULL;
705 		while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
706 		{
707 			qfilename = shell_unquote(gfilename);
708 			if (edit(qfilename) == 0 && good_filename == NULL)
709 				good_filename = get_filename(curr_ifile);
710 			free(qfilename);
711 		}
712 		free(gfilelist);
713 	}
714 	/*
715 	 * Edit the first valid filename in the list.
716 	 */
717 	if (good_filename == NULL)
718 	{
719 		unsave_ifile(save_ifile);
720 		return (1);
721 	}
722 	if (get_ifile(good_filename, curr_ifile) == curr_ifile)
723 	{
724 		/*
725 		 * Trying to edit the current file; don't reopen it.
726 		 */
727 		unsave_ifile(save_ifile);
728 		return (0);
729 	}
730 	reedit_ifile(save_ifile);
731 	return (edit(good_filename));
732 }
733 
734 /*
735  * Edit the first file in the command line (ifile) list.
736  */
edit_first(void)737 public int edit_first(void)
738 {
739 	if (nifile() == 0)
740 		return (edit_stdin());
741 	curr_ifile = NULL_IFILE;
742 	return (edit_next(1));
743 }
744 
745 /*
746  * Edit the last file in the command line (ifile) list.
747  */
edit_last(void)748 public int edit_last(void)
749 {
750 	curr_ifile = NULL_IFILE;
751 	return (edit_prev(1));
752 }
753 
754 
755 /*
756  * Edit the n-th next or previous file in the command line (ifile) list.
757  */
edit_istep(IFILE h,int n,int dir)758 static int edit_istep(IFILE h, int n, int dir)
759 {
760 	IFILE next;
761 
762 	/*
763 	 * Skip n filenames, then try to edit each filename.
764 	 */
765 	for (;;)
766 	{
767 		next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
768 		if (--n < 0)
769 		{
770 			if (edit_ifile(h) == 0)
771 				break;
772 		}
773 		if (next == NULL_IFILE)
774 		{
775 			/*
776 			 * Reached end of the ifile list.
777 			 */
778 			return (1);
779 		}
780 		if (ABORT_SIGS())
781 		{
782 			/*
783 			 * Interrupt breaks out, if we're in a long
784 			 * list of files that can't be opened.
785 			 */
786 			return (1);
787 		}
788 		h = next;
789 	}
790 	/*
791 	 * Found a file that we can edit.
792 	 */
793 	return (0);
794 }
795 
edit_inext(IFILE h,int n)796 static int edit_inext(IFILE h, int n)
797 {
798 	return (edit_istep(h, n, +1));
799 }
800 
edit_next(int n)801 public int edit_next(int n)
802 {
803 	return edit_istep(curr_ifile, n, +1);
804 }
805 
edit_iprev(IFILE h,int n)806 static int edit_iprev(IFILE h, int n)
807 {
808 	return (edit_istep(h, n, -1));
809 }
810 
edit_prev(int n)811 public int edit_prev(int n)
812 {
813 	return edit_istep(curr_ifile, n, -1);
814 }
815 
816 /*
817  * Edit a specific file in the command line (ifile) list.
818  */
edit_index(int n)819 public int edit_index(int n)
820 {
821 	IFILE h;
822 
823 	h = NULL_IFILE;
824 	do
825 	{
826 		if ((h = next_ifile(h)) == NULL_IFILE)
827 		{
828 			/*
829 			 * Reached end of the list without finding it.
830 			 */
831 			return (1);
832 		}
833 	} while (get_index(h) != n);
834 
835 	return (edit_ifile(h));
836 }
837 
save_curr_ifile(void)838 public IFILE save_curr_ifile(void)
839 {
840 	if (curr_ifile != NULL_IFILE)
841 		hold_ifile(curr_ifile, 1);
842 	return (curr_ifile);
843 }
844 
unsave_ifile(IFILE save_ifile)845 public void unsave_ifile(IFILE save_ifile)
846 {
847 	if (save_ifile != NULL_IFILE)
848 		hold_ifile(save_ifile, -1);
849 }
850 
851 /*
852  * Reedit the ifile which was previously open.
853  */
reedit_ifile(IFILE save_ifile)854 public void reedit_ifile(IFILE save_ifile)
855 {
856 	IFILE next;
857 	IFILE prev;
858 
859 	/*
860 	 * Try to reopen the ifile.
861 	 * Note that opening it may fail (maybe the file was removed),
862 	 * in which case the ifile will be deleted from the list.
863 	 * So save the next and prev ifiles first.
864 	 */
865 	unsave_ifile(save_ifile);
866 	next = next_ifile(save_ifile);
867 	prev = prev_ifile(save_ifile);
868 	if (edit_ifile(save_ifile) == 0)
869 		return;
870 	/*
871 	 * If can't reopen it, open the next input file in the list.
872 	 */
873 	if (next != NULL_IFILE && edit_inext(next, 0) == 0)
874 		return;
875 	/*
876 	 * If can't open THAT one, open the previous input file in the list.
877 	 */
878 	if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
879 		return;
880 	/*
881 	 * If can't even open that, we're stuck.  Just quit.
882 	 */
883 	quit(QUIT_ERROR);
884 }
885 
reopen_curr_ifile(void)886 public void reopen_curr_ifile(void)
887 {
888 	IFILE save_ifile = save_curr_ifile();
889 	close_file();
890 	reedit_ifile(save_ifile);
891 }
892 
893 /*
894  * Edit standard input.
895  */
edit_stdin(void)896 public int edit_stdin(void)
897 {
898 	if (isatty(fd0))
899 	{
900 		error("Missing filename (\"less --help\" for help)", NULL_PARG);
901 		quit(QUIT_OK);
902 	}
903 	return (edit("-"));
904 }
905 
906 /*
907  * Copy a file directly to standard output.
908  * Used if standard output is not a tty.
909  */
cat_file(void)910 public void cat_file(void)
911 {
912 	int c;
913 
914 	while ((c = ch_forw_get()) != EOI)
915 		putchr(c);
916 	flush();
917 }
918 
919 #if LOGFILE
920 
921 #define OVERWRITE_OPTIONS "Overwrite, Append, Don't log, or Quit?"
922 
923 /*
924  * If the user asked for a log file and our input file
925  * is standard input, create the log file.
926  * We take care not to blindly overwrite an existing file.
927  */
use_logfile(constant char * filename)928 public void use_logfile(constant char *filename)
929 {
930 	int exists;
931 	int answer;
932 	PARG parg;
933 
934 	if (ch_getflags() & CH_CANSEEK)
935 		/*
936 		 * Can't currently use a log file on a file that can seek.
937 		 */
938 		return;
939 
940 	/*
941 	 * {{ We could use access() here. }}
942 	 */
943 	exists = open(filename, OPEN_READ);
944 	if (exists >= 0)
945 		close(exists);
946 	exists = (exists >= 0);
947 
948 	/*
949 	 * Decide whether to overwrite the log file or append to it.
950 	 * If it doesn't exist we "overwrite" it.
951 	 */
952 	if (!exists || force_logfile)
953 	{
954 		/*
955 		 * Overwrite (or create) the log file.
956 		 */
957 		answer = 'O';
958 	} else
959 	{
960 		/*
961 		 * Ask user what to do.
962 		 */
963 		parg.p_string = filename;
964 		answer = query("Warning: \"%s\" exists; "OVERWRITE_OPTIONS" ", &parg);
965 	}
966 
967 loop:
968 	switch (answer)
969 	{
970 	case 'O': case 'o':
971 		/*
972 		 * Overwrite: create the file.
973 		 */
974 		logfile = creat(filename, CREAT_RW);
975 		break;
976 	case 'A': case 'a':
977 		/*
978 		 * Append: open the file and seek to the end.
979 		 */
980 		logfile = open(filename, OPEN_APPEND);
981 		if (less_lseek(logfile, (less_off_t)0, SEEK_END) == BAD_LSEEK)
982 		{
983 			close(logfile);
984 			logfile = -1;
985 		}
986 		break;
987 	case 'D': case 'd':
988 		/*
989 		 * Don't do anything.
990 		 */
991 		return;
992 	default:
993 		/*
994 		 * Eh?
995 		 */
996 
997 		answer = query(OVERWRITE_OPTIONS" (Type \"O\", \"A\", \"D\" or \"Q\") ", NULL_PARG);
998 		goto loop;
999 	}
1000 
1001 	if (logfile < 0)
1002 	{
1003 		/*
1004 		 * Error in opening logfile.
1005 		 */
1006 		parg.p_string = filename;
1007 		error("Cannot write to \"%s\"", &parg);
1008 		return;
1009 	}
1010 	SET_BINARY(logfile);
1011 }
1012 
1013 #endif
1014