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