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