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