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