xref: /dragonfly/contrib/less/edit.c (revision e0f238eda64c20d98364903e0c003825fd0b66dd)
1 /*
2  * Copyright (C) 1984-2024  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 #include "position.h"
13 #if HAVE_STAT
14 #include <sys/stat.h>
15 #endif
16 #if HAVE_SYS_WAIT_H
17 #include <sys/wait.h>
18 #endif
19 #if OS2 || __MVS__ || (defined WIFSIGNALED && defined WTERMSIG)
20 #include <signal.h>
21 #endif
22 
23 public int fd0 = 0;
24 
25 extern lbool new_file;
26 extern char *every_first_cmd;
27 extern int force_open;
28 extern int is_tty;
29 extern int sigs;
30 extern int hshift;
31 extern int want_filesize;
32 extern int consecutive_nulls;
33 extern int modelines;
34 extern int show_preproc_error;
35 extern IFILE curr_ifile;
36 extern IFILE old_ifile;
37 extern struct scrpos initial_scrpos;
38 extern void *ml_examine;
39 #if SPACES_IN_FILENAMES
40 extern char openquote;
41 extern char closequote;
42 #endif
43 
44 #if LOGFILE
45 extern int logfile;
46 extern int force_logfile;
47 extern char *namelogfile;
48 #endif
49 
50 #if HAVE_STAT_INO
51 public dev_t curr_dev;
52 public ino_t curr_ino;
53 #endif
54 
55 /*
56  * Textlist functions deal with a list of words separated by spaces.
57  * init_textlist sets up a textlist structure.
58  * forw_textlist uses that structure to iterate thru the list of
59  * words, returning each one as a standard null-terminated string.
60  * back_textlist does the same, but runs thru the list backwards.
61  */
init_textlist(struct textlist * tlist,mutable char * str)62 public void init_textlist(struct textlist *tlist, mutable char *str)
63 {
64           char *s;
65 #if SPACES_IN_FILENAMES
66           lbool meta_quoted = FALSE;
67           lbool delim_quoted = FALSE;
68           constant char *esc = get_meta_escape();
69           size_t esclen = strlen(esc);
70 #endif
71 
72           tlist->string = skipsp(str);
73           tlist->endstring = tlist->string + strlen(tlist->string);
74           for (s = str;  s < tlist->endstring;  s++)
75           {
76 #if SPACES_IN_FILENAMES
77                     if (meta_quoted)
78                     {
79                               meta_quoted = 0;
80                     } else if (esclen > 0 && s + esclen < tlist->endstring &&
81                                strncmp(s, esc, esclen) == 0)
82                     {
83                               meta_quoted = 1;
84                               s += esclen - 1;
85                     } else if (delim_quoted)
86                     {
87                               if (*s == closequote)
88                                         delim_quoted = 0;
89                     } else /* (!delim_quoted) */
90                     {
91                               if (*s == openquote)
92                                         delim_quoted = 1;
93                               else if (*s == ' ')
94                                         *s = '\0';
95                     }
96 #else
97                     if (*s == ' ')
98                               *s = '\0';
99 #endif
100           }
101 }
102 
forw_textlist(struct textlist * tlist,constant char * prev)103 public constant char * forw_textlist(struct textlist *tlist, constant char *prev)
104 {
105           constant char *s;
106 
107           /*
108            * prev == NULL means return the first word in the list.
109            * Otherwise, return the word after "prev".
110            */
111           if (prev == NULL)
112                     s = tlist->string;
113           else
114                     s = prev + strlen(prev);
115           if (s >= tlist->endstring)
116                     return (NULL);
117           while (*s == '\0')
118                     s++;
119           if (s >= tlist->endstring)
120                     return (NULL);
121           return (s);
122 }
123 
back_textlist(struct textlist * tlist,constant char * prev)124 public constant char * back_textlist(struct textlist *tlist, constant char *prev)
125 {
126           constant char *s;
127 
128           /*
129            * prev == NULL means return the last word in the list.
130            * Otherwise, return the word before "prev".
131            */
132           if (prev == NULL)
133                     s = tlist->endstring;
134           else if (prev <= tlist->string)
135                     return (NULL);
136           else
137                     s = prev - 1;
138           while (*s == '\0')
139                     s--;
140           if (s <= tlist->string)
141                     return (NULL);
142           while (s[-1] != '\0' && s > tlist->string)
143                     s--;
144           return (s);
145 }
146 
147 /*
148  * Parse a single option setting in a modeline.
149  */
modeline_option(constant char * str,size_t opt_len)150 static void modeline_option(constant char *str, size_t opt_len)
151 {
152           struct mloption { constant char *opt_name; void (*opt_func)(constant char*,size_t); };
153           struct mloption options[] = {
154                     { "ts=",         set_tabs },
155                     { "tabstop=",    set_tabs },
156                     { NULL, NULL }
157           };
158           struct mloption *opt;
159           for (opt = options;  opt->opt_name != NULL;  opt++)
160           {
161                     size_t name_len = strlen(opt->opt_name);
162                     if (opt_len > name_len && strncmp(str, opt->opt_name, name_len) == 0)
163                     {
164                               (*opt->opt_func)(str + name_len, opt_len - name_len);
165                               break;
166                     }
167           }
168 }
169 
170 /*
171  * String length, terminated by option separator (space or colon).
172  * Space/colon can be escaped with backspace.
173  */
modeline_option_len(constant char * str)174 static size_t modeline_option_len(constant char *str)
175 {
176           lbool esc = FALSE;
177           constant char *s;
178           for (s = str;  *s != '\0';  s++)
179           {
180                     if (esc)
181                               esc = FALSE;
182                     else if (*s == '\\')
183                               esc = TRUE;
184                     else if (*s == ' ' || *s == ':') /* separator */
185                               break;
186           }
187           return ptr_diff(s, str);
188 }
189 
190 /*
191  * Parse colon- or space-separated option settings in a modeline.
192  */
modeline_options(constant char * str,char end_char)193 static void modeline_options(constant char *str, char end_char)
194 {
195           for (;;)
196           {
197                     size_t opt_len;
198                     str = skipspc(str);
199                     if (*str == '\0' || *str == end_char)
200                               break;
201                     opt_len = modeline_option_len(str);
202                     modeline_option(str, opt_len);
203                     str += opt_len;
204                     if (*str != '\0')
205                               str += 1; /* skip past the separator */
206           }
207 }
208 
209 /*
210  * See if there is a modeline string in a line.
211  */
check_modeline(constant char * line)212 static void check_modeline(constant char *line)
213 {
214 #if HAVE_STRSTR
215           static constant char *pgms[] = { "less:", "vim:", "vi:", "ex:", NULL };
216           constant char **pgm;
217           for (pgm = pgms;  *pgm != NULL;  ++pgm)
218           {
219                     constant char *pline = line;
220                     for (;;)
221                     {
222                               constant char *str;
223                               pline = strstr(pline, *pgm);
224                               if (pline == NULL) /* pgm is not in this line */
225                                         break;
226                               str = skipspc(pline + strlen(*pgm));
227                               if (pline == line || pline[-1] == ' ')
228                               {
229                                         if (strncmp(str, "set ", 4) == 0)
230                                                   modeline_options(str+4, ':');
231                                         else if (pgm != &pgms[0]) /* "less:" requires "set" */
232                                                   modeline_options(str, '\0');
233                                         break;
234                               }
235                               /* Continue searching the rest of the line. */
236                               pline = str;
237                     }
238           }
239 #endif /* HAVE_STRSTR */
240 }
241 
242 /*
243  * Read lines from start of file and check if any are modelines.
244  */
check_modelines(void)245 static void check_modelines(void)
246 {
247           POSITION pos = ch_zero();
248           int i;
249           for (i = 0;  i < modelines;  i++)
250           {
251                     constant char *line;
252                     size_t line_len;
253                     if (ABORT_SIGS())
254                               return;
255                     pos = forw_raw_line(pos, &line, &line_len);
256                     if (pos == NULL_POSITION)
257                               break;
258                     check_modeline(line);
259           }
260 }
261 
262 /*
263  * Close a pipe opened via popen.
264  */
close_pipe(FILE * pipefd)265 static void close_pipe(FILE *pipefd)
266 {
267           int status;
268           char *p;
269           PARG parg;
270 
271           if (pipefd == NULL)
272                     return;
273 #if OS2
274           /*
275            * The pclose function of OS/2 emx sometimes fails.
276            * Send SIGINT to the piped process before closing it.
277            */
278           kill(pipefd->_pid, SIGINT);
279 #endif
280           status = pclose(pipefd);
281           if (status == -1)
282           {
283                     /* An internal error in 'less', not a preprocessor error.  */
284                     p = errno_message("pclose");
285                     parg.p_string = p;
286                     error("%s", &parg);
287                     free(p);
288                     return;
289           }
290           if (!show_preproc_error)
291                     return;
292 #if defined WIFEXITED && defined WEXITSTATUS
293           if (WIFEXITED(status))
294           {
295                     int s = WEXITSTATUS(status);
296                     if (s != 0)
297                     {
298                               parg.p_int = s;
299                               error("Input preprocessor failed (status %d)", &parg);
300                     }
301                     return;
302           }
303 #endif
304 #if defined WIFSIGNALED && defined WTERMSIG
305           if (WIFSIGNALED(status))
306           {
307                     int sig = WTERMSIG(status);
308                     if (sig != SIGPIPE || ch_length() != NULL_POSITION)
309                     {
310                               parg.p_string = signal_message(sig);
311                               error("Input preprocessor terminated: %s", &parg);
312                     }
313                     return;
314           }
315 #endif
316           if (status != 0)
317           {
318                     parg.p_int = status;
319                     error("Input preprocessor exited with status %x", &parg);
320           }
321 }
322 
323 /*
324  * Drain and close an input pipe if needed.
325  */
close_altpipe(IFILE ifile)326 public void close_altpipe(IFILE ifile)
327 {
328           FILE *altpipe = get_altpipe(ifile);
329           if (altpipe != NULL && !(ch_getflags() & CH_KEEPOPEN))
330           {
331                     close_pipe(altpipe);
332                     set_altpipe(ifile, NULL);
333           }
334 }
335 
336 /*
337  * Check for error status from the current altpipe.
338  * May or may not close the pipe.
339  */
check_altpipe_error(void)340 public void check_altpipe_error(void)
341 {
342           if (!show_preproc_error)
343                     return;
344           if (curr_ifile != NULL_IFILE && get_altfilename(curr_ifile) != NULL)
345                     close_altpipe(curr_ifile);
346 }
347 
348 /*
349  * Close the current input file.
350  */
close_file(void)351 static void close_file(void)
352 {
353           struct scrpos scrpos;
354           constant char *altfilename;
355 
356           if (curr_ifile == NULL_IFILE)
357                     return;
358 
359           /*
360            * Save the current position so that we can return to
361            * the same position if we edit this file again.
362            */
363           get_scrpos(&scrpos, TOP);
364           if (scrpos.pos != NULL_POSITION)
365           {
366                     store_pos(curr_ifile, &scrpos);
367                     lastmark();
368           }
369           /*
370            * Close the file descriptor, unless it is a pipe.
371            */
372           ch_close();
373           /*
374            * If we opened a file using an alternate name,
375            * do special stuff to close it.
376            */
377           altfilename = get_altfilename(curr_ifile);
378           if (altfilename != NULL)
379           {
380                     close_altpipe(curr_ifile);
381                     close_altfile(altfilename, get_filename(curr_ifile));
382                     set_altfilename(curr_ifile, NULL);
383           }
384           curr_ifile = NULL_IFILE;
385 #if HAVE_STAT_INO
386           curr_ino = curr_dev = 0;
387 #endif
388 }
389 
390 /*
391  * Edit a new file (given its name).
392  * Filename == "-" means standard input.
393  * Filename == NULL means just close the current file.
394  */
edit(constant char * filename)395 public int edit(constant char *filename)
396 {
397           if (filename == NULL)
398                     return (edit_ifile(NULL_IFILE));
399           return (edit_ifile(get_ifile(filename, curr_ifile)));
400 }
401 
402 /*
403  * Clean up what edit_ifile did before error return.
404  */
edit_error(constant char * filename,constant char * alt_filename,void * altpipe,IFILE ifile)405 static int edit_error(constant char *filename, constant char *alt_filename, void *altpipe, IFILE ifile)
406 {
407           if (alt_filename != NULL)
408           {
409                     close_pipe(altpipe);
410                     close_altfile(alt_filename, filename);
411                     free((char*)alt_filename); /* FIXME: WTF? */
412           }
413           del_ifile(ifile);
414           /*
415            * Re-open the current file.
416            */
417           if (curr_ifile == ifile)
418           {
419                     /*
420                      * Whoops.  The "current" ifile is the one we just deleted.
421                      * Just give up.
422                      */
423                     quit(QUIT_ERROR);
424           }
425           return (1);
426 }
427 
428 /*
429  * Edit a new file (given its IFILE).
430  * ifile == NULL means just close the current file.
431  */
edit_ifile(IFILE ifile)432 public int edit_ifile(IFILE ifile)
433 {
434           int f;
435           int answer;
436           int chflags;
437           constant char *filename;
438           constant char *open_filename;
439           char *alt_filename;
440           void *altpipe;
441           IFILE was_curr_ifile;
442           char *p;
443           PARG parg;
444           ssize_t nread;
445 
446           if (ifile == curr_ifile)
447           {
448                     /*
449                      * Already have the correct file open.
450                      */
451                     return (0);
452           }
453           new_file = TRUE;
454 
455           if (ifile != NULL_IFILE)
456           {
457                     /*
458                      * See if LESSOPEN specifies an "alternate" file to open.
459                      */
460                     filename = get_filename(ifile);
461                     altpipe = get_altpipe(ifile);
462                     if (altpipe != NULL)
463                     {
464                               /*
465                                * File is already open.
466                                * chflags and f are not used by ch_init if ifile has
467                                * filestate which should be the case if we're here.
468                                * Set them here to avoid uninitialized variable warnings.
469                                */
470                               chflags = 0;
471                               f = -1;
472                               alt_filename = get_altfilename(ifile);
473                               open_filename = (alt_filename != NULL) ? alt_filename : filename;
474                     } else
475                     {
476                               if (strcmp(filename, FAKE_HELPFILE) == 0 ||
477                                         strcmp(filename, FAKE_EMPTYFILE) == 0)
478                                         alt_filename = NULL;
479                               else
480                                         alt_filename = open_altfile(filename, &f, &altpipe);
481 
482                               open_filename = (alt_filename != NULL) ? alt_filename : filename;
483 
484                               chflags = 0;
485                               if (altpipe != NULL)
486                               {
487                                         /*
488                                          * The alternate "file" is actually a pipe.
489                                          * f has already been set to the file descriptor of the pipe
490                                          * in the call to open_altfile above.
491                                          * Keep the file descriptor open because it was opened
492                                          * via popen(), and pclose() wants to close it.
493                                          */
494                                         chflags |= CH_POPENED;
495                                         if (strcmp(filename, "-") == 0)
496                                                   chflags |= CH_KEEPOPEN;
497                               } else if (strcmp(filename, "-") == 0)
498                               {
499                                         /*
500                                          * Use standard input.
501                                          * Keep the file descriptor open because we can't reopen it.
502                                          */
503                                         f = fd0;
504                                         chflags |= CH_KEEPOPEN;
505                                         /*
506                                          * Must switch stdin to BINARY mode.
507                                          */
508                                         SET_BINARY(f);
509 #if MSDOS_COMPILER==DJGPPC
510                                         /*
511                                          * Setting stdin to binary by default causes
512                                          * Ctrl-C to not raise SIGINT.  We must undo
513                                          * that side-effect.
514                                          */
515                                         __djgpp_set_ctrl_c(1);
516 #endif
517                               } else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0)
518                               {
519                                         f = -1;
520                                         chflags |= CH_NODATA;
521                               } else if (strcmp(open_filename, FAKE_HELPFILE) == 0)
522                               {
523                                         f = -1;
524                                         chflags |= CH_HELPFILE;
525                               } else if ((p = bad_file(open_filename)) != NULL)
526                               {
527                                         /*
528                                          * It looks like a bad file.  Don't try to open it.
529                                          */
530                                         parg.p_string = p;
531                                         error("%s", &parg);
532                                         free(p);
533                                         return edit_error(filename, alt_filename, altpipe, ifile);
534                               } else if ((f = open(open_filename, OPEN_READ)) < 0)
535                               {
536                                         /*
537                                          * Got an error trying to open it.
538                                          */
539                                         char *p = errno_message(filename);
540                                         parg.p_string = p;
541                                         error("%s", &parg);
542                                         free(p);
543                                         return edit_error(filename, alt_filename, altpipe, ifile);
544                               } else
545                               {
546                                         chflags |= CH_CANSEEK;
547                                         if (bin_file(f, &nread) && !force_open && !opened(ifile))
548                                         {
549                                                   /*
550                                                    * Looks like a binary file.
551                                                    * Ask user if we should proceed.
552                                                    */
553                                                   parg.p_string = filename;
554                                                   answer = query("\"%s\" may be a binary file.  See it anyway? ", &parg);
555                                                   if (answer != 'y' && answer != 'Y')
556                                                   {
557                                                             close(f);
558                                                             return edit_error(filename, alt_filename, altpipe, ifile);
559                                                   }
560                                         }
561                               }
562                     }
563                     if (!force_open && f >= 0 && isatty(f))
564                     {
565                               PARG parg;
566                               parg.p_string = filename;
567                               error("%s is a terminal (use -f to open it)", &parg);
568                               return edit_error(filename, alt_filename, altpipe, ifile);
569                     }
570           }
571 
572 #if LOGFILE
573           end_logfile();
574 #endif
575           was_curr_ifile = save_curr_ifile();
576           if (curr_ifile != NULL_IFILE)
577           {
578                     int was_helpfile = (ch_getflags() & CH_HELPFILE);
579                     close_file();
580                     if (was_helpfile && held_ifile(was_curr_ifile) <= 1)
581                     {
582                               /*
583                                * Don't keep the help file in the ifile list.
584                                */
585                               del_ifile(was_curr_ifile);
586                               was_curr_ifile = NULL_IFILE;
587                     }
588           }
589           unsave_ifile(was_curr_ifile);
590 
591           if (ifile == NULL_IFILE)
592           {
593                     /*
594                      * No new file to open.
595                      * (Don't set old_ifile, because if you call edit_ifile(NULL),
596                      *  you're supposed to have saved curr_ifile yourself,
597                      *  and you'll restore it if necessary.)
598                      */
599                     return (0);
600           }
601 
602           /*
603            * Set up the new ifile.
604            * Get the saved position for the file.
605            */
606           curr_ifile = ifile;
607           set_altfilename(curr_ifile, alt_filename);
608           set_altpipe(curr_ifile, altpipe);
609           set_open(curr_ifile); /* File has been opened */
610           get_pos(curr_ifile, &initial_scrpos);
611           ch_init(f, chflags, nread);
612           consecutive_nulls = 0;
613           check_modelines();
614 
615           if (!(chflags & CH_HELPFILE))
616           {
617                     if (was_curr_ifile != NULL_IFILE)
618                               old_ifile = was_curr_ifile;
619 #if LOGFILE
620                     if (namelogfile != NULL && is_tty)
621                               use_logfile(namelogfile);
622 #endif
623 #if HAVE_STAT_INO
624                     /* Remember the i-number and device of the opened file. */
625                     if (strcmp(open_filename, "-") != 0)
626                     {
627                               struct stat statbuf;
628                               int r = stat(open_filename, &statbuf);
629                               if (r == 0)
630                               {
631                                         curr_ino = statbuf.st_ino;
632                                         curr_dev = statbuf.st_dev;
633                               }
634                     }
635 #endif
636                     if (every_first_cmd != NULL)
637                     {
638                               ungetsc(every_first_cmd);
639                               ungetcc_end_command();
640                     }
641           }
642 
643           flush();
644 
645           if (is_tty)
646           {
647                     /*
648                      * Output is to a real tty.
649                      */
650 
651                     /*
652                      * Indicate there is nothing displayed yet.
653                      */
654                     pos_clear();
655                     clr_linenum();
656 #if HILITE_SEARCH
657                     clr_hilite();
658 #endif
659                     hshift = 0;
660                     if (strcmp(filename, FAKE_HELPFILE) && strcmp(filename, FAKE_EMPTYFILE))
661                     {
662                               char *qfilename = shell_quote(filename);
663                               cmd_addhist(ml_examine, qfilename, 1);
664                               free(qfilename);
665                     }
666                     if (want_filesize)
667                               scan_eof();
668                     set_header(ch_zero());
669           }
670           return (0);
671 }
672 
673 /*
674  * Edit a space-separated list of files.
675  * For each filename in the list, enter it into the ifile list.
676  * Then edit the first one.
677  */
edit_list(char * filelist)678 public int edit_list(char *filelist)
679 {
680           IFILE save_ifile;
681           constant char *good_filename;
682           constant char *filename;
683           char *gfilelist;
684           constant char *gfilename;
685           char *qfilename;
686           struct textlist tl_files;
687           struct textlist tl_gfiles;
688 
689           save_ifile = save_curr_ifile();
690           good_filename = NULL;
691 
692           /*
693            * Run thru each filename in the list.
694            * Try to glob the filename.
695            * If it doesn't expand, just try to open the filename.
696            * If it does expand, try to open each name in that list.
697            */
698           init_textlist(&tl_files, filelist);
699           filename = NULL;
700           while ((filename = forw_textlist(&tl_files, filename)) != NULL)
701           {
702                     gfilelist = lglob(filename);
703                     init_textlist(&tl_gfiles, gfilelist);
704                     gfilename = NULL;
705                     while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
706                     {
707                               qfilename = shell_unquote(gfilename);
708                               if (edit(qfilename) == 0 && good_filename == NULL)
709                                         good_filename = get_filename(curr_ifile);
710                               free(qfilename);
711                     }
712                     free(gfilelist);
713           }
714           /*
715            * Edit the first valid filename in the list.
716            */
717           if (good_filename == NULL)
718           {
719                     unsave_ifile(save_ifile);
720                     return (1);
721           }
722           if (get_ifile(good_filename, curr_ifile) == curr_ifile)
723           {
724                     /*
725                      * Trying to edit the current file; don't reopen it.
726                      */
727                     unsave_ifile(save_ifile);
728                     return (0);
729           }
730           reedit_ifile(save_ifile);
731           return (edit(good_filename));
732 }
733 
734 /*
735  * Edit the first file in the command line (ifile) list.
736  */
edit_first(void)737 public int edit_first(void)
738 {
739           if (nifile() == 0)
740                     return (edit_stdin());
741           curr_ifile = NULL_IFILE;
742           return (edit_next(1));
743 }
744 
745 /*
746  * Edit the last file in the command line (ifile) list.
747  */
edit_last(void)748 public int edit_last(void)
749 {
750           curr_ifile = NULL_IFILE;
751           return (edit_prev(1));
752 }
753 
754 
755 /*
756  * Edit the n-th next or previous file in the command line (ifile) list.
757  */
edit_istep(IFILE h,int n,int dir)758 static int edit_istep(IFILE h, int n, int dir)
759 {
760           IFILE next;
761 
762           /*
763            * Skip n filenames, then try to edit each filename.
764            */
765           for (;;)
766           {
767                     next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
768                     if (--n < 0)
769                     {
770                               if (edit_ifile(h) == 0)
771                                         break;
772                     }
773                     if (next == NULL_IFILE)
774                     {
775                               /*
776                                * Reached end of the ifile list.
777                                */
778                               return (1);
779                     }
780                     if (ABORT_SIGS())
781                     {
782                               /*
783                                * Interrupt breaks out, if we're in a long
784                                * list of files that can't be opened.
785                                */
786                               return (1);
787                     }
788                     h = next;
789           }
790           /*
791            * Found a file that we can edit.
792            */
793           return (0);
794 }
795 
edit_inext(IFILE h,int n)796 static int edit_inext(IFILE h, int n)
797 {
798           return (edit_istep(h, n, +1));
799 }
800 
edit_next(int n)801 public int edit_next(int n)
802 {
803           return edit_istep(curr_ifile, n, +1);
804 }
805 
edit_iprev(IFILE h,int n)806 static int edit_iprev(IFILE h, int n)
807 {
808           return (edit_istep(h, n, -1));
809 }
810 
edit_prev(int n)811 public int edit_prev(int n)
812 {
813           return edit_istep(curr_ifile, n, -1);
814 }
815 
816 /*
817  * Edit a specific file in the command line (ifile) list.
818  */
edit_index(int n)819 public int edit_index(int n)
820 {
821           IFILE h;
822 
823           h = NULL_IFILE;
824           do
825           {
826                     if ((h = next_ifile(h)) == NULL_IFILE)
827                     {
828                               /*
829                                * Reached end of the list without finding it.
830                                */
831                               return (1);
832                     }
833           } while (get_index(h) != n);
834 
835           return (edit_ifile(h));
836 }
837 
save_curr_ifile(void)838 public IFILE save_curr_ifile(void)
839 {
840           if (curr_ifile != NULL_IFILE)
841                     hold_ifile(curr_ifile, 1);
842           return (curr_ifile);
843 }
844 
unsave_ifile(IFILE save_ifile)845 public void unsave_ifile(IFILE save_ifile)
846 {
847           if (save_ifile != NULL_IFILE)
848                     hold_ifile(save_ifile, -1);
849 }
850 
851 /*
852  * Reedit the ifile which was previously open.
853  */
reedit_ifile(IFILE save_ifile)854 public void reedit_ifile(IFILE save_ifile)
855 {
856           IFILE next;
857           IFILE prev;
858 
859           /*
860            * Try to reopen the ifile.
861            * Note that opening it may fail (maybe the file was removed),
862            * in which case the ifile will be deleted from the list.
863            * So save the next and prev ifiles first.
864            */
865           unsave_ifile(save_ifile);
866           next = next_ifile(save_ifile);
867           prev = prev_ifile(save_ifile);
868           if (edit_ifile(save_ifile) == 0)
869                     return;
870           /*
871            * If can't reopen it, open the next input file in the list.
872            */
873           if (next != NULL_IFILE && edit_inext(next, 0) == 0)
874                     return;
875           /*
876            * If can't open THAT one, open the previous input file in the list.
877            */
878           if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
879                     return;
880           /*
881            * If can't even open that, we're stuck.  Just quit.
882            */
883           quit(QUIT_ERROR);
884 }
885 
reopen_curr_ifile(void)886 public void reopen_curr_ifile(void)
887 {
888           IFILE save_ifile = save_curr_ifile();
889           close_file();
890           reedit_ifile(save_ifile);
891 }
892 
893 /*
894  * Edit standard input.
895  */
edit_stdin(void)896 public int edit_stdin(void)
897 {
898           if (isatty(fd0))
899           {
900                     error("Missing filename (\"less --help\" for help)", NULL_PARG);
901                     quit(QUIT_OK);
902           }
903           return (edit("-"));
904 }
905 
906 /*
907  * Copy a file directly to standard output.
908  * Used if standard output is not a tty.
909  */
cat_file(void)910 public void cat_file(void)
911 {
912           int c;
913 
914           while ((c = ch_forw_get()) != EOI)
915                     putchr(c);
916           flush();
917 }
918 
919 #if LOGFILE
920 
921 #define OVERWRITE_OPTIONS "Overwrite, Append, Don't log, or Quit?"
922 
923 /*
924  * If the user asked for a log file and our input file
925  * is standard input, create the log file.
926  * We take care not to blindly overwrite an existing file.
927  */
use_logfile(constant char * filename)928 public void use_logfile(constant char *filename)
929 {
930           int exists;
931           int answer;
932           PARG parg;
933 
934           if (ch_getflags() & CH_CANSEEK)
935                     /*
936                      * Can't currently use a log file on a file that can seek.
937                      */
938                     return;
939 
940           /*
941            * {{ We could use access() here. }}
942            */
943           exists = open(filename, OPEN_READ);
944           if (exists >= 0)
945                     close(exists);
946           exists = (exists >= 0);
947 
948           /*
949            * Decide whether to overwrite the log file or append to it.
950            * If it doesn't exist we "overwrite" it.
951            */
952           if (!exists || force_logfile)
953           {
954                     /*
955                      * Overwrite (or create) the log file.
956                      */
957                     answer = 'O';
958           } else
959           {
960                     /*
961                      * Ask user what to do.
962                      */
963                     parg.p_string = filename;
964                     answer = query("Warning: \"%s\" exists; "OVERWRITE_OPTIONS" ", &parg);
965           }
966 
967 loop:
968           switch (answer)
969           {
970           case 'O': case 'o':
971                     /*
972                      * Overwrite: create the file.
973                      */
974                     logfile = creat(filename, CREAT_RW);
975                     break;
976           case 'A': case 'a':
977                     /*
978                      * Append: open the file and seek to the end.
979                      */
980                     logfile = open(filename, OPEN_APPEND);
981                     if (less_lseek(logfile, (less_off_t)0, SEEK_END) == BAD_LSEEK)
982                     {
983                               close(logfile);
984                               logfile = -1;
985                     }
986                     break;
987           case 'D': case 'd':
988                     /*
989                      * Don't do anything.
990                      */
991                     return;
992           default:
993                     /*
994                      * Eh?
995                      */
996 
997                     answer = query(OVERWRITE_OPTIONS" (Type \"O\", \"A\", \"D\" or \"Q\") ", NULL_PARG);
998                     goto loop;
999           }
1000 
1001           if (logfile < 0)
1002           {
1003                     /*
1004                      * Error in opening logfile.
1005                      */
1006                     parg.p_string = filename;
1007                     error("Cannot write to \"%s\"", &parg);
1008                     return;
1009           }
1010           SET_BINARY(logfile);
1011 }
1012 
1013 #endif
1014