edit.c revision 161475
1115140Strhodes/*
2115140Strhodes * Copyright (C) 1984-2005  Mark Nudelman
3115140Strhodes *
4115140Strhodes * You may distribute under the terms of either the GNU General Public
5115140Strhodes * License or the Less License, as specified in the README file.
6115140Strhodes *
7115140Strhodes * For more information about less, or for information on how to
8115140Strhodes * contact the author, see the README file.
9115140Strhodes */
10115140Strhodes
11115140Strhodes
12115140Strhodes#include "less.h"
13115140Strhodes
14115140Strhodespublic int fd0 = 0;
15115140Strhodes
16115140Strhodesextern int new_file;
17115140Strhodesextern int errmsgs;
18115140Strhodesextern int cbufs;
19115140Strhodesextern char *every_first_cmd;
20115140Strhodesextern int any_display;
21115140Strhodesextern int force_open;
22115140Strhodesextern int is_tty;
23115140Strhodesextern int sigs;
24115140Strhodesextern IFILE curr_ifile;
25115140Strhodesextern IFILE old_ifile;
26115140Strhodesextern struct scrpos initial_scrpos;
27115140Strhodesextern void constant *ml_examine;
28115140Strhodes#if SPACES_IN_FILENAMES
29115140Strhodesextern char openquote;
30115140Strhodesextern char closequote;
31115140Strhodes#endif
32232507Seadler
33115140Strhodes#if LOGFILE
34115140Strhodesextern int logfile;
35115140Strhodesextern int force_logfile;
36115140Strhodesextern char *namelogfile;
37115140Strhodes#endif
38115140Strhodes
39115140Strhodeschar *curr_altfilename = NULL;
40115140Strhodesstatic void *curr_altpipe;
41115140Strhodes
42115140Strhodes
43115140Strhodes/*
44115140Strhodes * Textlist functions deal with a list of words separated by spaces.
45115140Strhodes * init_textlist sets up a textlist structure.
46115140Strhodes * forw_textlist uses that structure to iterate thru the list of
47115140Strhodes * words, returning each one as a standard null-terminated string.
48115140Strhodes * back_textlist does the same, but runs thru the list backwards.
49115140Strhodes */
50115140Strhodes	public void
51115140Strhodesinit_textlist(tlist, str)
52115140Strhodes	struct textlist *tlist;
53115140Strhodes	char *str;
54115140Strhodes{
55115140Strhodes	char *s;
56115140Strhodes#if SPACES_IN_FILENAMES
57115140Strhodes	int meta_quoted = 0;
58115140Strhodes	int delim_quoted = 0;
59115140Strhodes	char *esc = get_meta_escape();
60115140Strhodes	int esclen = strlen(esc);
61115140Strhodes#endif
62115140Strhodes
63115140Strhodes	tlist->string = skipsp(str);
64115140Strhodes	tlist->endstring = tlist->string + strlen(tlist->string);
65115140Strhodes	for (s = str;  s < tlist->endstring;  s++)
66115140Strhodes	{
67115140Strhodes#if SPACES_IN_FILENAMES
68115140Strhodes		if (meta_quoted)
69115140Strhodes		{
70115140Strhodes			meta_quoted = 0;
71115140Strhodes		} else if (esclen > 0 && s + esclen < tlist->endstring &&
72115140Strhodes		           strncmp(s, esc, esclen) == 0)
73219004Shselasky		{
74115140Strhodes			meta_quoted = 1;
75115140Strhodes			s += esclen - 1;
76115140Strhodes		} else if (delim_quoted)
77115140Strhodes		{
78115140Strhodes			if (*s == closequote)
79115140Strhodes				delim_quoted = 0;
80115140Strhodes		} else /* (!delim_quoted) */
81115140Strhodes		{
82219004Shselasky			if (*s == openquote)
83232501Seadler				delim_quoted = 1;
84232501Seadler			else if (*s == ' ')
85232501Seadler				*s = '\0';
86232501Seadler		}
87232501Seadler#else
88219004Shselasky		if (*s == ' ')
89219004Shselasky			*s = '\0';
90219004Shselasky#endif
91219004Shselasky	}
92219004Shselasky}
93219004Shselasky
94219004Shselasky	public char *
95219004Shselaskyforw_textlist(tlist, prev)
96219004Shselasky	struct textlist *tlist;
97219004Shselasky	char *prev;
98219004Shselasky{
99219004Shselasky	char *s;
100219004Shselasky
101219004Shselasky	/*
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 *
119back_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
148close_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
189edit(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
202edit_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
433edit_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
491edit_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
501edit_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
512edit_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
554edit_inext(h, n)
555	IFILE h;
556	int n;
557{
558	return (edit_istep(h, n, +1));
559}
560
561	public int
562edit_next(n)
563	int n;
564{
565	return edit_istep(curr_ifile, n, +1);
566}
567
568	static int
569edit_iprev(h, n)
570	IFILE h;
571	int n;
572{
573	return (edit_istep(h, n, -1));
574}
575
576	public int
577edit_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
587edit_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
608save_curr_ifile()
609{
610	if (curr_ifile != NULL_IFILE)
611		hold_ifile(curr_ifile, 1);
612	return (curr_ifile);
613}
614
615	public void
616unsave_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
627reedit_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
664edit_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
679cat_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
696use_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
736loop:
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