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