Merge from vendor branch LESS:
[dragonfly.git] / contrib / less-406 / edit.c
1 /*
2  * Copyright (C) 1984-2007  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