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