]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/less/edit.c
MFV:
[FreeBSD/FreeBSD.git] / contrib / less / edit.c
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