Annotation of src/usr.bin/less/edit.c, Revision 1.6
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:
118: public char *
119: back_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
148: close_file()
149: {
150: struct scrpos scrpos;
151:
152: if (curr_ifile == NULL_IFILE)
153: return;
1.4 millert 154:
1.1 etheisen 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),
1.4 millert 176: curr_altpipe);
1.1 etheisen 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
189: edit(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
202: edit_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;
1.4 millert 211: char *qopen_filename;
1.1 etheisen 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
1.4 millert 235: was_curr_ifile = save_curr_ifile();
1.1 etheisen 236: if (curr_ifile != NULL_IFILE)
237: {
1.4 millert 238: chflags = ch_getflags();
1.1 etheisen 239: close_file();
240: }
241:
242: if (ifile == NULL_IFILE)
243: {
244: /*
245: * No new file to open.
246: * (Don't set old_ifile, because if you call edit_ifile(NULL),
247: * you're supposed to have saved curr_ifile yourself,
248: * and you'll restore it if necessary.)
249: */
1.4 millert 250: unsave_ifile(was_curr_ifile);
1.1 etheisen 251: return (0);
252: }
253:
1.4 millert 254: filename = save(get_filename(ifile));
1.1 etheisen 255: /*
256: * See if LESSOPEN specifies an "alternate" file to open.
257: */
258: alt_pipe = NULL;
259: alt_filename = open_altfile(filename, &f, &alt_pipe);
260: open_filename = (alt_filename != NULL) ? alt_filename : filename;
1.4 millert 261: qopen_filename = shell_unquote(open_filename);
1.1 etheisen 262:
263: chflags = 0;
264: if (alt_pipe != NULL)
265: {
266: /*
267: * The alternate "file" is actually a pipe.
268: * f has already been set to the file descriptor of the pipe
269: * in the call to open_altfile above.
270: * Keep the file descriptor open because it was opened
271: * via popen(), and pclose() wants to close it.
272: */
273: chflags |= CH_POPENED;
274: } else if (strcmp(open_filename, "-") == 0)
275: {
276: /*
277: * Use standard input.
278: * Keep the file descriptor open because we can't reopen it.
279: */
280: f = fd0;
281: chflags |= CH_KEEPOPEN;
1.4 millert 282: /*
283: * Must switch stdin to BINARY mode.
284: */
285: SET_BINARY(f);
286: #if MSDOS_COMPILER==DJGPPC
287: /*
288: * Setting stdin to binary by default causes
289: * Ctrl-C to not raise SIGINT. We must undo
290: * that side-effect.
291: */
292: __djgpp_set_ctrl_c(1);
293: #endif
1.1 etheisen 294: } else if ((parg.p_string = bad_file(open_filename)) != NULL)
295: {
296: /*
297: * It looks like a bad file. Don't try to open it.
298: */
299: error("%s", &parg);
300: free(parg.p_string);
301: err1:
302: if (alt_filename != NULL)
303: {
304: close_altfile(alt_filename, filename, alt_pipe);
305: free(alt_filename);
306: }
307: del_ifile(ifile);
1.4 millert 308: free(qopen_filename);
309: free(filename);
1.1 etheisen 310: /*
311: * Re-open the current file.
312: */
1.4 millert 313: reedit_ifile(was_curr_ifile);
1.1 etheisen 314: return (1);
1.4 millert 315: } else if ((f = open(qopen_filename, OPEN_READ)) < 0)
1.1 etheisen 316: {
317: /*
318: * Got an error trying to open it.
319: */
320: parg.p_string = errno_message(filename);
321: error("%s", &parg);
322: free(parg.p_string);
323: goto err1;
1.4 millert 324: } else
1.1 etheisen 325: {
1.4 millert 326: chflags |= CH_CANSEEK;
327: if (!force_open && !opened(ifile) && bin_file(f))
1.1 etheisen 328: {
1.4 millert 329: /*
330: * Looks like a binary file.
331: * Ask user if we should proceed.
332: */
333: parg.p_string = filename;
334: answer = query("\"%s\" may be a binary file. See it anyway? ",
335: &parg);
336: if (answer != 'y' && answer != 'Y')
337: {
338: close(f);
339: goto err1;
340: }
1.1 etheisen 341: }
342: }
1.4 millert 343: free(qopen_filename);
1.1 etheisen 344:
345: /*
346: * Get the new ifile.
347: * Get the saved position for the file.
348: */
349: if (was_curr_ifile != NULL_IFILE)
1.4 millert 350: {
1.1 etheisen 351: old_ifile = was_curr_ifile;
1.4 millert 352: unsave_ifile(was_curr_ifile);
353: }
1.1 etheisen 354: curr_ifile = ifile;
355: curr_altfilename = alt_filename;
356: curr_altpipe = alt_pipe;
357: set_open(curr_ifile); /* File has been opened */
358: get_pos(curr_ifile, &initial_scrpos);
359: new_file = TRUE;
360: ch_init(f, chflags);
1.4 millert 361:
1.1 etheisen 362: #if LOGFILE
1.5 millert 363: if (namelogfile != NULL && is_tty)
364: use_logfile(namelogfile);
1.1 etheisen 365: #endif
1.5 millert 366: if (every_first_cmd != NULL)
367: ungetsc(every_first_cmd);
1.1 etheisen 368:
369: no_display = !any_display;
370: flush();
371: any_display = TRUE;
372:
373: if (is_tty)
374: {
375: /*
376: * Output is to a real tty.
377: */
378:
379: /*
380: * Indicate there is nothing displayed yet.
381: */
382: pos_clear();
383: clr_linenum();
384: #if HILITE_SEARCH
385: clr_hilite();
386: #endif
1.4 millert 387: cmd_addhist(ml_examine, filename);
1.1 etheisen 388: if (no_display && errmsgs > 0)
389: {
390: /*
391: * We displayed some messages on error output
392: * (file descriptor 2; see error() function).
393: * Before erasing the screen contents,
394: * display the file name and wait for a keystroke.
395: */
396: parg.p_string = filename;
397: error("%s", &parg);
398: }
399: }
1.4 millert 400: free(filename);
1.1 etheisen 401: return (0);
402: }
403:
404: /*
405: * Edit a space-separated list of files.
406: * For each filename in the list, enter it into the ifile list.
407: * Then edit the first one.
408: */
409: public int
410: edit_list(filelist)
411: char *filelist;
412: {
1.4 millert 413: IFILE save_ifile;
1.1 etheisen 414: char *good_filename;
415: char *filename;
416: char *gfilelist;
417: char *gfilename;
418: struct textlist tl_files;
419: struct textlist tl_gfiles;
420:
1.4 millert 421: save_ifile = save_curr_ifile();
1.1 etheisen 422: good_filename = NULL;
423:
424: /*
425: * Run thru each filename in the list.
426: * Try to glob the filename.
427: * If it doesn't expand, just try to open the filename.
428: * If it does expand, try to open each name in that list.
429: */
430: init_textlist(&tl_files, filelist);
431: filename = NULL;
432: while ((filename = forw_textlist(&tl_files, filename)) != NULL)
433: {
1.4 millert 434: gfilelist = lglob(filename);
1.1 etheisen 435: init_textlist(&tl_gfiles, gfilelist);
436: gfilename = NULL;
437: while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
438: {
439: if (edit(gfilename) == 0 && good_filename == NULL)
440: good_filename = get_filename(curr_ifile);
441: }
442: free(gfilelist);
443: }
444: /*
445: * Edit the first valid filename in the list.
446: */
447: if (good_filename == NULL)
1.4 millert 448: {
449: unsave_ifile(save_ifile);
1.1 etheisen 450: return (1);
1.4 millert 451: }
1.1 etheisen 452: if (get_ifile(good_filename, curr_ifile) == curr_ifile)
1.4 millert 453: {
1.1 etheisen 454: /*
455: * Trying to edit the current file; don't reopen it.
456: */
1.4 millert 457: unsave_ifile(save_ifile);
1.1 etheisen 458: return (0);
1.4 millert 459: }
460: reedit_ifile(save_ifile);
1.1 etheisen 461: return (edit(good_filename));
462: }
463:
464: /*
465: * Edit the first file in the command line (ifile) list.
466: */
467: public int
468: edit_first()
469: {
470: curr_ifile = NULL_IFILE;
471: return (edit_next(1));
472: }
473:
474: /*
475: * Edit the last file in the command line (ifile) list.
476: */
477: public int
478: edit_last()
479: {
480: curr_ifile = NULL_IFILE;
481: return (edit_prev(1));
482: }
483:
484:
485: /*
1.4 millert 486: * Edit the next or previous file in the command line (ifile) list.
1.1 etheisen 487: */
1.4 millert 488: static int
489: edit_istep(h, n, dir)
490: IFILE h;
1.1 etheisen 491: int n;
1.4 millert 492: int dir;
1.1 etheisen 493: {
494: IFILE next;
495:
496: /*
497: * Skip n filenames, then try to edit each filename.
498: */
499: for (;;)
500: {
1.4 millert 501: next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
1.1 etheisen 502: if (--n < 0)
503: {
504: if (edit_ifile(h) == 0)
505: break;
506: }
507: if (next == NULL_IFILE)
508: {
509: /*
510: * Reached end of the ifile list.
511: */
512: return (1);
513: }
1.4 millert 514: if (ABORT_SIGS())
515: {
516: /*
517: * Interrupt breaks out, if we're in a long
518: * list of files that can't be opened.
519: */
520: return (1);
521: }
1.1 etheisen 522: h = next;
523: }
524: /*
525: * Found a file that we can edit.
526: */
527: return (0);
528: }
529:
1.4 millert 530: static int
531: edit_inext(h, n)
532: IFILE h;
533: int n;
534: {
535: return (edit_istep(h, n, 1));
536: }
537:
1.1 etheisen 538: public int
1.4 millert 539: edit_next(n)
1.1 etheisen 540: int n;
541: {
1.4 millert 542: return edit_istep(curr_ifile, n, 1);
543: }
544:
545: static int
546: edit_iprev(h, n)
1.1 etheisen 547: IFILE h;
1.4 millert 548: int n;
549: {
550: return (edit_istep(h, n, -1));
551: }
1.1 etheisen 552:
1.4 millert 553: public int
554: edit_prev(n)
555: int n;
556: {
557: return edit_istep(curr_ifile, n, -1);
1.1 etheisen 558: }
559:
560: /*
561: * Edit a specific file in the command line (ifile) list.
562: */
563: public int
564: edit_index(n)
565: int n;
566: {
567: IFILE h;
568:
569: h = NULL_IFILE;
570: do
571: {
572: if ((h = next_ifile(h)) == NULL_IFILE)
573: {
574: /*
575: * Reached end of the list without finding it.
576: */
577: return (1);
578: }
579: } while (get_index(h) != n);
580:
581: return (edit_ifile(h));
582: }
583:
1.4 millert 584: public IFILE
585: save_curr_ifile()
586: {
587: if (curr_ifile != NULL_IFILE)
588: hold_ifile(curr_ifile, 1);
589: return (curr_ifile);
590: }
591:
592: public void
593: unsave_ifile(save_ifile)
594: IFILE save_ifile;
595: {
596: if (save_ifile != NULL_IFILE)
597: hold_ifile(save_ifile, -1);
598: }
599:
600: /*
601: * Reedit the ifile which was previously open.
602: */
603: public void
604: reedit_ifile(save_ifile)
605: IFILE save_ifile;
606: {
607: IFILE next;
608: IFILE prev;
609:
610: /*
611: * Try to reopen the ifile.
612: * Note that opening it may fail (maybe the file was removed),
613: * in which case the ifile will be deleted from the list.
614: * So save the next and prev ifiles first.
615: */
616: unsave_ifile(save_ifile);
617: next = next_ifile(save_ifile);
618: prev = prev_ifile(save_ifile);
619: if (edit_ifile(save_ifile) == 0)
620: return;
621: /*
622: * If can't reopen it, open the next input file in the list.
623: */
624: if (next != NULL_IFILE && edit_inext(next, 0) == 0)
625: return;
626: /*
627: * If can't open THAT one, open the previous input file in the list.
628: */
629: if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
630: return;
631: /*
632: * If can't even open that, we're stuck. Just quit.
633: */
634: quit(QUIT_ERROR);
635: }
636:
1.1 etheisen 637: /*
638: * Edit standard input.
639: */
640: public int
641: edit_stdin()
642: {
643: if (isatty(fd0))
644: {
1.4 millert 645: error("Missing filename (\"less --help\" for help)", NULL_PARG);
1.1 etheisen 646: quit(QUIT_OK);
647: }
648: return (edit("-"));
649: }
650:
651: /*
652: * Copy a file directly to standard output.
653: * Used if standard output is not a tty.
654: */
655: public void
656: cat_file()
657: {
1.4 millert 658: register int c;
1.1 etheisen 659:
660: while ((c = ch_forw_get()) != EOI)
661: putchr(c);
662: flush();
663: }
664:
665: #if LOGFILE
666:
667: /*
668: * If the user asked for a log file and our input file
669: * is standard input, create the log file.
670: * We take care not to blindly overwrite an existing file.
671: */
672: public void
673: use_logfile(filename)
674: char *filename;
675: {
1.4 millert 676: register int exists;
677: register int answer;
1.1 etheisen 678: PARG parg;
679:
680: if (ch_getflags() & CH_CANSEEK)
681: /*
682: * Can't currently use a log file on a file that can seek.
683: */
684: return;
685:
686: /*
687: * {{ We could use access() here. }}
688: */
1.4 millert 689: filename = shell_unquote(filename);
1.1 etheisen 690: exists = open(filename, OPEN_READ);
691: close(exists);
692: exists = (exists >= 0);
693:
694: /*
695: * Decide whether to overwrite the log file or append to it.
696: * If it doesn't exist we "overwrite" it.
697: */
698: if (!exists || force_logfile)
699: {
700: /*
701: * Overwrite (or create) the log file.
702: */
703: answer = 'O';
704: } else
705: {
706: /*
707: * Ask user what to do.
708: */
709: parg.p_string = filename;
710: answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg);
711: }
712:
713: loop:
714: switch (answer)
715: {
716: case 'O': case 'o':
717: /*
718: * Overwrite: create the file.
719: */
720: logfile = creat(filename, 0644);
721: break;
722: case 'A': case 'a':
723: /*
724: * Append: open the file and seek to the end.
725: */
726: logfile = open(filename, OPEN_APPEND);
1.6 ! deraadt 727: if (lseek(logfile, (off_t)0, SEEK_END) == BAD_LSEEK)
1.1 etheisen 728: {
729: close(logfile);
730: logfile = -1;
731: }
732: break;
733: case 'D': case 'd':
734: /*
735: * Don't do anything.
736: */
1.4 millert 737: free(filename);
1.1 etheisen 738: return;
739: case 'q':
740: quit(QUIT_OK);
741: /*NOTREACHED*/
742: default:
743: /*
744: * Eh?
745: */
746: answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG);
747: goto loop;
748: }
749:
750: if (logfile < 0)
751: {
752: /*
753: * Error in opening logfile.
754: */
755: parg.p_string = filename;
756: error("Cannot write to \"%s\"", &parg);
1.4 millert 757: free(filename);
758: return;
1.1 etheisen 759: }
1.4 millert 760: free(filename);
761: SET_BINARY(logfile);
1.1 etheisen 762: }
763:
764: #endif