b3e5669b85bf0fb82afce18385bd2de45bbb68c2
[dragonfly.git] / contrib / less / edit.c
1 /*
2  * Copyright (C) 1984-2008  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 #if HAVE_STAT
14 #include <sys/stat.h>
15 #endif
16
17 public int fd0 = 0;
18
19 extern int new_file;
20 extern int errmsgs;
21 extern int cbufs;
22 extern char *every_first_cmd;
23 extern int any_display;
24 extern int force_open;
25 extern int is_tty;
26 extern int sigs;
27 extern IFILE curr_ifile;
28 extern IFILE old_ifile;
29 extern struct scrpos initial_scrpos;
30 extern void constant *ml_examine;
31 #if SPACES_IN_FILENAMES
32 extern char openquote;
33 extern char closequote;
34 #endif
35
36 #if LOGFILE
37 extern int logfile;
38 extern int force_logfile;
39 extern char *namelogfile;
40 #endif
41
42 #if HAVE_STAT_INO
43 public dev_t curr_dev;
44 public ino_t curr_ino;
45 #endif
46
47 char *curr_altfilename = NULL;
48 static void *curr_altpipe;
49
50
51 /*
52  * Textlist functions deal with a list of words separated by spaces.
53  * init_textlist sets up a textlist structure.
54  * forw_textlist uses that structure to iterate thru the list of
55  * words, returning each one as a standard null-terminated string.
56  * back_textlist does the same, but runs thru the list backwards.
57  */
58         public void
59 init_textlist(tlist, str)
60         struct textlist *tlist;
61         char *str;
62 {
63         char *s;
64 #if SPACES_IN_FILENAMES
65         int meta_quoted = 0;
66         int delim_quoted = 0;
67         char *esc = get_meta_escape();
68         int esclen = strlen(esc);
69 #endif
70         
71         tlist->string = skipsp(str);
72         tlist->endstring = tlist->string + strlen(tlist->string);
73         for (s = str;  s < tlist->endstring;  s++)
74         {
75 #if SPACES_IN_FILENAMES
76                 if (meta_quoted)
77                 {
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 *
103 forw_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 *
127 back_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
156 close_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
200 edit(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
213 edit_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
456 edit_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
514 edit_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
524 edit_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
535 edit_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
577 edit_inext(h, n)
578         IFILE h;
579         int n;
580 {
581         return (edit_istep(h, n, +1));
582 }
583
584         public int
585 edit_next(n)
586         int n;
587 {
588         return edit_istep(curr_ifile, n, +1);
589 }
590
591         static int
592 edit_iprev(h, n)
593         IFILE h;
594         int n;
595 {
596         return (edit_istep(h, n, -1));
597 }
598
599         public int
600 edit_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
610 edit_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
631 save_curr_ifile()
632 {
633         if (curr_ifile != NULL_IFILE)
634                 hold_ifile(curr_ifile, 1);
635         return (curr_ifile);
636 }
637
638         public void
639 unsave_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
650 reedit_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
684 reopen_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
695 edit_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
710 cat_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
727 use_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
767 loop:
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