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