xref: /freebsd/contrib/less/edit.c (revision bfe691b2f75de2224c7ceb304ebcdef2b42d4179)
1 /*
2  * Copyright (C) 1984-2005  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 about less, or for information on how to
8  * contact the author, see the README file.
9  */
10 
11 
12 #include "less.h"
13 
14 public int fd0 = 0;
15 
16 extern int new_file;
17 extern int errmsgs;
18 extern int cbufs;
19 extern char *every_first_cmd;
20 extern int any_display;
21 extern int force_open;
22 extern int is_tty;
23 extern int sigs;
24 extern IFILE curr_ifile;
25 extern IFILE old_ifile;
26 extern struct scrpos initial_scrpos;
27 extern void constant *ml_examine;
28 #if SPACES_IN_FILENAMES
29 extern char openquote;
30 extern char closequote;
31 #endif
32 
33 #if LOGFILE
34 extern int logfile;
35 extern int force_logfile;
36 extern char *namelogfile;
37 #endif
38 
39 char *curr_altfilename = NULL;
40 static void *curr_altpipe;
41 
42 
43 /*
44  * Textlist functions deal with a list of words separated by spaces.
45  * init_textlist sets up a textlist structure.
46  * forw_textlist uses that structure to iterate thru the list of
47  * words, returning each one as a standard null-terminated string.
48  * back_textlist does the same, but runs thru the list backwards.
49  */
50 	public void
51 init_textlist(tlist, str)
52 	struct textlist *tlist;
53 	char *str;
54 {
55 	char *s;
56 #if SPACES_IN_FILENAMES
57 	int meta_quoted = 0;
58 	int delim_quoted = 0;
59 	char *esc = get_meta_escape();
60 	int esclen = strlen(esc);
61 #endif
62 
63 	tlist->string = skipsp(str);
64 	tlist->endstring = tlist->string + strlen(tlist->string);
65 	for (s = str;  s < tlist->endstring;  s++)
66 	{
67 #if SPACES_IN_FILENAMES
68 		if (meta_quoted)
69 		{
70 			meta_quoted = 0;
71 		} else if (esclen > 0 && s + esclen < tlist->endstring &&
72 		           strncmp(s, esc, esclen) == 0)
73 		{
74 			meta_quoted = 1;
75 			s += esclen - 1;
76 		} else if (delim_quoted)
77 		{
78 			if (*s == closequote)
79 				delim_quoted = 0;
80 		} else /* (!delim_quoted) */
81 		{
82 			if (*s == openquote)
83 				delim_quoted = 1;
84 			else if (*s == ' ')
85 				*s = '\0';
86 		}
87 #else
88 		if (*s == ' ')
89 			*s = '\0';
90 #endif
91 	}
92 }
93 
94 	public char *
95 forw_textlist(tlist, prev)
96 	struct textlist *tlist;
97 	char *prev;
98 {
99 	char *s;
100 
101 	/*
102 	 * prev == NULL means return the first word in the list.
103 	 * Otherwise, return the word after "prev".
104 	 */
105 	if (prev == NULL)
106 		s = tlist->string;
107 	else
108 		s = prev + strlen(prev);
109 	if (s >= tlist->endstring)
110 		return (NULL);
111 	while (*s == '\0')
112 		s++;
113 	if (s >= tlist->endstring)
114 		return (NULL);
115 	return (s);
116 }
117 
118 	public char *
119 back_textlist(tlist, prev)
120 	struct textlist *tlist;
121 	char *prev;
122 {
123 	char *s;
124 
125 	/*
126 	 * prev == NULL means return the last word in the list.
127 	 * Otherwise, return the word before "prev".
128 	 */
129 	if (prev == NULL)
130 		s = tlist->endstring;
131 	else if (prev <= tlist->string)
132 		return (NULL);
133 	else
134 		s = prev - 1;
135 	while (*s == '\0')
136 		s--;
137 	if (s <= tlist->string)
138 		return (NULL);
139 	while (s[-1] != '\0' && s > tlist->string)
140 		s--;
141 	return (s);
142 }
143 
144 /*
145  * Close the current input file.
146  */
147 	static void
148 close_file()
149 {
150 	struct scrpos scrpos;
151 
152 	if (curr_ifile == NULL_IFILE)
153 		return;
154 
155 	/*
156 	 * Save the current position so that we can return to
157 	 * the same position if we edit this file again.
158 	 */
159 	get_scrpos(&scrpos);
160 	if (scrpos.pos != NULL_POSITION)
161 	{
162 		store_pos(curr_ifile, &scrpos);
163 		lastmark();
164 	}
165 	/*
166 	 * Close the file descriptor, unless it is a pipe.
167 	 */
168 	ch_close();
169 	/*
170 	 * If we opened a file using an alternate name,
171 	 * do special stuff to close it.
172 	 */
173 	if (curr_altfilename != NULL)
174 	{
175 		close_altfile(curr_altfilename, get_filename(curr_ifile),
176 				curr_altpipe);
177 		free(curr_altfilename);
178 		curr_altfilename = NULL;
179 	}
180 	curr_ifile = NULL_IFILE;
181 }
182 
183 /*
184  * Edit a new file (given its name).
185  * Filename == "-" means standard input.
186  * Filename == NULL means just close the current file.
187  */
188 	public int
189 edit(filename)
190 	char *filename;
191 {
192 	if (filename == NULL)
193 		return (edit_ifile(NULL_IFILE));
194 	return (edit_ifile(get_ifile(filename, curr_ifile)));
195 }
196 
197 /*
198  * Edit a new file (given its IFILE).
199  * ifile == NULL means just close the current file.
200  */
201 	public int
202 edit_ifile(ifile)
203 	IFILE ifile;
204 {
205 	int f;
206 	int answer;
207 	int no_display;
208 	int chflags;
209 	char *filename;
210 	char *open_filename;
211 	char *qopen_filename;
212 	char *alt_filename;
213 	void *alt_pipe;
214 	IFILE was_curr_ifile;
215 	PARG parg;
216 
217 	if (ifile == curr_ifile)
218 	{
219 		/*
220 		 * Already have the correct file open.
221 		 */
222 		return (0);
223 	}
224 
225 	/*
226 	 * We must close the currently open file now.
227 	 * This is necessary to make the open_altfile/close_altfile pairs
228 	 * nest properly (or rather to avoid nesting at all).
229 	 * {{ Some stupid implementations of popen() mess up if you do:
230 	 *    fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }}
231 	 */
232 #if LOGFILE
233 	end_logfile();
234 #endif
235 	was_curr_ifile = save_curr_ifile();
236 	if (curr_ifile != NULL_IFILE)
237 	{
238 		chflags = ch_getflags();
239 		close_file();
240 		if ((chflags & CH_HELPFILE) && held_ifile(was_curr_ifile) <= 1)
241 		{
242 			/*
243 			 * Don't keep the help file in the ifile list.
244 			 */
245 			del_ifile(was_curr_ifile);
246 			was_curr_ifile = old_ifile;
247 		}
248 	}
249 
250 	if (ifile == NULL_IFILE)
251 	{
252 		/*
253 		 * No new file to open.
254 		 * (Don't set old_ifile, because if you call edit_ifile(NULL),
255 		 *  you're supposed to have saved curr_ifile yourself,
256 		 *  and you'll restore it if necessary.)
257 		 */
258 		unsave_ifile(was_curr_ifile);
259 		return (0);
260 	}
261 
262 	filename = save(get_filename(ifile));
263 	/*
264 	 * See if LESSOPEN specifies an "alternate" file to open.
265 	 */
266 	alt_pipe = NULL;
267 	alt_filename = open_altfile(filename, &f, &alt_pipe);
268 	open_filename = (alt_filename != NULL) ? alt_filename : filename;
269 	qopen_filename = shell_unquote(open_filename);
270 
271 	chflags = 0;
272 	if (alt_pipe != NULL)
273 	{
274 		/*
275 		 * The alternate "file" is actually a pipe.
276 		 * f has already been set to the file descriptor of the pipe
277 		 * in the call to open_altfile above.
278 		 * Keep the file descriptor open because it was opened
279 		 * via popen(), and pclose() wants to close it.
280 		 */
281 		chflags |= CH_POPENED;
282 	} else if (strcmp(open_filename, "-") == 0)
283 	{
284 		/*
285 		 * Use standard input.
286 		 * Keep the file descriptor open because we can't reopen it.
287 		 */
288 		f = fd0;
289 		chflags |= CH_KEEPOPEN;
290 		/*
291 		 * Must switch stdin to BINARY mode.
292 		 */
293 		SET_BINARY(f);
294 #if MSDOS_COMPILER==DJGPPC
295 		/*
296 		 * Setting stdin to binary by default causes
297 		 * Ctrl-C to not raise SIGINT.  We must undo
298 		 * that side-effect.
299 		 */
300 		__djgpp_set_ctrl_c(1);
301 #endif
302 	} else if (strcmp(open_filename, FAKE_HELPFILE) == 0)
303 	{
304 		f = -1;
305 		chflags |= CH_HELPFILE;
306 	} else if ((parg.p_string = bad_file(open_filename)) != NULL)
307 	{
308 		/*
309 		 * It looks like a bad file.  Don't try to open it.
310 		 */
311 		error("%s", &parg);
312 		free(parg.p_string);
313 	    err1:
314 		if (alt_filename != NULL)
315 		{
316 			close_altfile(alt_filename, filename, alt_pipe);
317 			free(alt_filename);
318 		}
319 		del_ifile(ifile);
320 		free(qopen_filename);
321 		free(filename);
322 		/*
323 		 * Re-open the current file.
324 		 */
325 		if (was_curr_ifile == ifile)
326 		{
327 			/*
328 			 * Whoops.  The "current" ifile is the one we just deleted.
329 			 * Just give up.
330 			 */
331 			quit(QUIT_ERROR);
332 		}
333 		reedit_ifile(was_curr_ifile);
334 		return (1);
335 	} else if ((f = open(qopen_filename, OPEN_READ)) < 0)
336 	{
337 		/*
338 		 * Got an error trying to open it.
339 		 */
340 		parg.p_string = errno_message(filename);
341 		error("%s", &parg);
342 		free(parg.p_string);
343 	    	goto err1;
344 	} else
345 	{
346 		chflags |= CH_CANSEEK;
347 		if (!force_open && !opened(ifile) && bin_file(f))
348 		{
349 			/*
350 			 * Looks like a binary file.
351 			 * Ask user if we should proceed.
352 			 */
353 			parg.p_string = filename;
354 			answer = query("\"%s\" may be a binary file.  See it anyway? ",
355 				&parg);
356 			if (answer != 'y' && answer != 'Y')
357 			{
358 				close(f);
359 				goto err1;
360 			}
361 		}
362 	}
363 	free(qopen_filename);
364 
365 	/*
366 	 * Get the new ifile.
367 	 * Get the saved position for the file.
368 	 */
369 	if (was_curr_ifile != NULL_IFILE)
370 	{
371 		old_ifile = was_curr_ifile;
372 		unsave_ifile(was_curr_ifile);
373 	}
374 	curr_ifile = ifile;
375 	curr_altfilename = alt_filename;
376 	curr_altpipe = alt_pipe;
377 	set_open(curr_ifile); /* File has been opened */
378 	get_pos(curr_ifile, &initial_scrpos);
379 	new_file = TRUE;
380 	ch_init(f, chflags);
381 
382 	if (!(chflags & CH_HELPFILE))
383 	{
384 #if LOGFILE
385 		if (namelogfile != NULL && is_tty)
386 			use_logfile(namelogfile);
387 #endif
388 		if (every_first_cmd != NULL)
389 			ungetsc(every_first_cmd);
390 	}
391 
392 	no_display = !any_display;
393 	flush();
394 	any_display = TRUE;
395 
396 	if (is_tty)
397 	{
398 		/*
399 		 * Output is to a real tty.
400 		 */
401 
402 		/*
403 		 * Indicate there is nothing displayed yet.
404 		 */
405 		pos_clear();
406 		clr_linenum();
407 #if HILITE_SEARCH
408 		clr_hilite();
409 #endif
410 		cmd_addhist(ml_examine, filename);
411 		if (no_display && errmsgs > 0)
412 		{
413 			/*
414 			 * We displayed some messages on error output
415 			 * (file descriptor 2; see error() function).
416 			 * Before erasing the screen contents,
417 			 * display the file name and wait for a keystroke.
418 			 */
419 			parg.p_string = filename;
420 			error("%s", &parg);
421 		}
422 	}
423 	free(filename);
424 	return (0);
425 }
426 
427 /*
428  * Edit a space-separated list of files.
429  * For each filename in the list, enter it into the ifile list.
430  * Then edit the first one.
431  */
432 	public int
433 edit_list(filelist)
434 	char *filelist;
435 {
436 	IFILE save_ifile;
437 	char *good_filename;
438 	char *filename;
439 	char *gfilelist;
440 	char *gfilename;
441 	struct textlist tl_files;
442 	struct textlist tl_gfiles;
443 
444 	save_ifile = save_curr_ifile();
445 	good_filename = NULL;
446 
447 	/*
448 	 * Run thru each filename in the list.
449 	 * Try to glob the filename.
450 	 * If it doesn't expand, just try to open the filename.
451 	 * If it does expand, try to open each name in that list.
452 	 */
453 	init_textlist(&tl_files, filelist);
454 	filename = NULL;
455 	while ((filename = forw_textlist(&tl_files, filename)) != NULL)
456 	{
457 		gfilelist = lglob(filename);
458 		init_textlist(&tl_gfiles, gfilelist);
459 		gfilename = NULL;
460 		while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
461 		{
462 			if (edit(gfilename) == 0 && good_filename == NULL)
463 				good_filename = get_filename(curr_ifile);
464 		}
465 		free(gfilelist);
466 	}
467 	/*
468 	 * Edit the first valid filename in the list.
469 	 */
470 	if (good_filename == NULL)
471 	{
472 		unsave_ifile(save_ifile);
473 		return (1);
474 	}
475 	if (get_ifile(good_filename, curr_ifile) == curr_ifile)
476 	{
477 		/*
478 		 * Trying to edit the current file; don't reopen it.
479 		 */
480 		unsave_ifile(save_ifile);
481 		return (0);
482 	}
483 	reedit_ifile(save_ifile);
484 	return (edit(good_filename));
485 }
486 
487 /*
488  * Edit the first file in the command line (ifile) list.
489  */
490 	public int
491 edit_first()
492 {
493 	curr_ifile = NULL_IFILE;
494 	return (edit_next(1));
495 }
496 
497 /*
498  * Edit the last file in the command line (ifile) list.
499  */
500 	public int
501 edit_last()
502 {
503 	curr_ifile = NULL_IFILE;
504 	return (edit_prev(1));
505 }
506 
507 
508 /*
509  * Edit the n-th next or previous file in the command line (ifile) list.
510  */
511 	static int
512 edit_istep(h, n, dir)
513 	IFILE h;
514 	int n;
515 	int dir;
516 {
517 	IFILE next;
518 
519 	/*
520 	 * Skip n filenames, then try to edit each filename.
521 	 */
522 	for (;;)
523 	{
524 		next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
525 		if (--n < 0)
526 		{
527 			if (edit_ifile(h) == 0)
528 				break;
529 		}
530 		if (next == NULL_IFILE)
531 		{
532 			/*
533 			 * Reached end of the ifile list.
534 			 */
535 			return (1);
536 		}
537 		if (ABORT_SIGS())
538 		{
539 			/*
540 			 * Interrupt breaks out, if we're in a long
541 			 * list of files that can't be opened.
542 			 */
543 			return (1);
544 		}
545 		h = next;
546 	}
547 	/*
548 	 * Found a file that we can edit.
549 	 */
550 	return (0);
551 }
552 
553 	static int
554 edit_inext(h, n)
555 	IFILE h;
556 	int n;
557 {
558 	return (edit_istep(h, n, +1));
559 }
560 
561 	public int
562 edit_next(n)
563 	int n;
564 {
565 	return edit_istep(curr_ifile, n, +1);
566 }
567 
568 	static int
569 edit_iprev(h, n)
570 	IFILE h;
571 	int n;
572 {
573 	return (edit_istep(h, n, -1));
574 }
575 
576 	public int
577 edit_prev(n)
578 	int n;
579 {
580 	return edit_istep(curr_ifile, n, -1);
581 }
582 
583 /*
584  * Edit a specific file in the command line (ifile) list.
585  */
586 	public int
587 edit_index(n)
588 	int n;
589 {
590 	IFILE h;
591 
592 	h = NULL_IFILE;
593 	do
594 	{
595 		if ((h = next_ifile(h)) == NULL_IFILE)
596 		{
597 			/*
598 			 * Reached end of the list without finding it.
599 			 */
600 			return (1);
601 		}
602 	} while (get_index(h) != n);
603 
604 	return (edit_ifile(h));
605 }
606 
607 	public IFILE
608 save_curr_ifile()
609 {
610 	if (curr_ifile != NULL_IFILE)
611 		hold_ifile(curr_ifile, 1);
612 	return (curr_ifile);
613 }
614 
615 	public void
616 unsave_ifile(save_ifile)
617 	IFILE save_ifile;
618 {
619 	if (save_ifile != NULL_IFILE)
620 		hold_ifile(save_ifile, -1);
621 }
622 
623 /*
624  * Reedit the ifile which was previously open.
625  */
626 	public void
627 reedit_ifile(save_ifile)
628 	IFILE save_ifile;
629 {
630 	IFILE next;
631 	IFILE prev;
632 
633 	/*
634 	 * Try to reopen the ifile.
635 	 * Note that opening it may fail (maybe the file was removed),
636 	 * in which case the ifile will be deleted from the list.
637 	 * So save the next and prev ifiles first.
638 	 */
639 	unsave_ifile(save_ifile);
640 	next = next_ifile(save_ifile);
641 	prev = prev_ifile(save_ifile);
642 	if (edit_ifile(save_ifile) == 0)
643 		return;
644 	/*
645 	 * If can't reopen it, open the next input file in the list.
646 	 */
647 	if (next != NULL_IFILE && edit_inext(next, 0) == 0)
648 		return;
649 	/*
650 	 * If can't open THAT one, open the previous input file in the list.
651 	 */
652 	if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
653 		return;
654 	/*
655 	 * If can't even open that, we're stuck.  Just quit.
656 	 */
657 	quit(QUIT_ERROR);
658 }
659 
660 /*
661  * Edit standard input.
662  */
663 	public int
664 edit_stdin()
665 {
666 	if (isatty(fd0))
667 	{
668 		error("Missing filename (\"less --help\" for help)", NULL_PARG);
669 		quit(QUIT_OK);
670 	}
671 	return (edit("-"));
672 }
673 
674 /*
675  * Copy a file directly to standard output.
676  * Used if standard output is not a tty.
677  */
678 	public void
679 cat_file()
680 {
681 	register int c;
682 
683 	while ((c = ch_forw_get()) != EOI)
684 		putchr(c);
685 	flush();
686 }
687 
688 #if LOGFILE
689 
690 /*
691  * If the user asked for a log file and our input file
692  * is standard input, create the log file.
693  * We take care not to blindly overwrite an existing file.
694  */
695 	public void
696 use_logfile(filename)
697 	char *filename;
698 {
699 	register int exists;
700 	register int answer;
701 	PARG parg;
702 
703 	if (ch_getflags() & CH_CANSEEK)
704 		/*
705 		 * Can't currently use a log file on a file that can seek.
706 		 */
707 		return;
708 
709 	/*
710 	 * {{ We could use access() here. }}
711 	 */
712 	filename = shell_unquote(filename);
713 	exists = open(filename, OPEN_READ);
714 	close(exists);
715 	exists = (exists >= 0);
716 
717 	/*
718 	 * Decide whether to overwrite the log file or append to it.
719 	 * If it doesn't exist we "overwrite" it.
720 	 */
721 	if (!exists || force_logfile)
722 	{
723 		/*
724 		 * Overwrite (or create) the log file.
725 		 */
726 		answer = 'O';
727 	} else
728 	{
729 		/*
730 		 * Ask user what to do.
731 		 */
732 		parg.p_string = filename;
733 		answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg);
734 	}
735 
736 loop:
737 	switch (answer)
738 	{
739 	case 'O': case 'o':
740 		/*
741 		 * Overwrite: create the file.
742 		 */
743 		logfile = creat(filename, 0644);
744 		break;
745 	case 'A': case 'a':
746 		/*
747 		 * Append: open the file and seek to the end.
748 		 */
749 		logfile = open(filename, OPEN_APPEND);
750 		if (lseek(logfile, (off_t)0, 2) == BAD_LSEEK)
751 		{
752 			close(logfile);
753 			logfile = -1;
754 		}
755 		break;
756 	case 'D': case 'd':
757 		/*
758 		 * Don't do anything.
759 		 */
760 		free(filename);
761 		return;
762 	case 'q':
763 		quit(QUIT_OK);
764 		/*NOTREACHED*/
765 	default:
766 		/*
767 		 * Eh?
768 		 */
769 		answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG);
770 		goto loop;
771 	}
772 
773 	if (logfile < 0)
774 	{
775 		/*
776 		 * Error in opening logfile.
777 		 */
778 		parg.p_string = filename;
779 		error("Cannot write to \"%s\"", &parg);
780 		free(filename);
781 		return;
782 	}
783 	free(filename);
784 	SET_BINARY(logfile);
785 }
786 
787 #endif
788