Annotation of src/usr.bin/less/edit.c, Revision 1.1.1.2
1.1 etheisen 1: /*
1.1.1.2 ! millert 2: * Copyright (C) 1984-2002 Mark Nudelman
1.1 etheisen 3: *
1.1.1.2 ! 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.1.1.2 ! 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.1.1.2 ! 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.1.1.2 ! 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.1.1.2 ! 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.1.1.2 ! 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.1.1.2 ! 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.1.1.2 ! 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.1.1.2 ! 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.1.1.2 ! 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.1.1.2 ! millert 235: was_curr_ifile = save_curr_ifile();
1.1 etheisen 236: if (curr_ifile != NULL_IFILE)
237: {
1.1.1.2 ! millert 238: chflags = ch_getflags();
1.1 etheisen 239: close_file();
1.1.1.2 ! millert 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: }
1.1 etheisen 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: */
1.1.1.2 ! millert 258: unsave_ifile(was_curr_ifile);
1.1 etheisen 259: return (0);
260: }
261:
1.1.1.2 ! millert 262: filename = save(get_filename(ifile));
1.1 etheisen 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;
1.1.1.2 ! millert 269: qopen_filename = shell_unquote(open_filename);
1.1 etheisen 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;
1.1.1.2 ! millert 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;
1.1 etheisen 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);
1.1.1.2 ! millert 320: free(qopen_filename);
! 321: free(filename);
1.1 etheisen 322: /*
323: * Re-open the current file.
324: */
1.1.1.2 ! millert 325: reedit_ifile(was_curr_ifile);
1.1 etheisen 326: return (1);
1.1.1.2 ! millert 327: } else if ((f = open(qopen_filename, OPEN_READ)) < 0)
1.1 etheisen 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;
1.1.1.2 ! millert 336: } else
1.1 etheisen 337: {
1.1.1.2 ! millert 338: chflags |= CH_CANSEEK;
! 339: if (!force_open && !opened(ifile) && bin_file(f))
1.1 etheisen 340: {
1.1.1.2 ! millert 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: }
1.1 etheisen 353: }
354: }
1.1.1.2 ! millert 355: free(qopen_filename);
1.1 etheisen 356:
357: /*
358: * Get the new ifile.
359: * Get the saved position for the file.
360: */
361: if (was_curr_ifile != NULL_IFILE)
1.1.1.2 ! millert 362: {
1.1 etheisen 363: old_ifile = was_curr_ifile;
1.1.1.2 ! millert 364: unsave_ifile(was_curr_ifile);
! 365: }
1.1 etheisen 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);
1.1.1.2 ! millert 373:
! 374: if (!(chflags & CH_HELPFILE))
! 375: {
1.1 etheisen 376: #if LOGFILE
1.1.1.2 ! millert 377: if (namelogfile != NULL && is_tty)
! 378: use_logfile(namelogfile);
1.1 etheisen 379: #endif
1.1.1.2 ! millert 380: if (every_first_cmd != NULL)
! 381: ungetsc(every_first_cmd);
! 382: }
1.1 etheisen 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
1.1.1.2 ! millert 402: cmd_addhist(ml_examine, filename);
1.1 etheisen 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: }
1.1.1.2 ! millert 415: free(filename);
1.1 etheisen 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
425: edit_list(filelist)
426: char *filelist;
427: {
1.1.1.2 ! millert 428: IFILE save_ifile;
1.1 etheisen 429: char *good_filename;
430: char *filename;
431: char *gfilelist;
432: char *gfilename;
433: struct textlist tl_files;
434: struct textlist tl_gfiles;
435:
1.1.1.2 ! millert 436: save_ifile = save_curr_ifile();
1.1 etheisen 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: {
1.1.1.2 ! millert 449: gfilelist = lglob(filename);
1.1 etheisen 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)
1.1.1.2 ! millert 463: {
! 464: unsave_ifile(save_ifile);
1.1 etheisen 465: return (1);
1.1.1.2 ! millert 466: }
1.1 etheisen 467: if (get_ifile(good_filename, curr_ifile) == curr_ifile)
1.1.1.2 ! millert 468: {
1.1 etheisen 469: /*
470: * Trying to edit the current file; don't reopen it.
471: */
1.1.1.2 ! millert 472: unsave_ifile(save_ifile);
1.1 etheisen 473: return (0);
1.1.1.2 ! millert 474: }
! 475: reedit_ifile(save_ifile);
1.1 etheisen 476: return (edit(good_filename));
477: }
478:
479: /*
480: * Edit the first file in the command line (ifile) list.
481: */
482: public int
483: edit_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
493: edit_last()
494: {
495: curr_ifile = NULL_IFILE;
496: return (edit_prev(1));
497: }
498:
499:
500: /*
1.1.1.2 ! millert 501: * Edit the next or previous file in the command line (ifile) list.
1.1 etheisen 502: */
1.1.1.2 ! millert 503: static int
! 504: edit_istep(h, n, dir)
! 505: IFILE h;
1.1 etheisen 506: int n;
1.1.1.2 ! millert 507: int dir;
1.1 etheisen 508: {
509: IFILE next;
510:
511: /*
512: * Skip n filenames, then try to edit each filename.
513: */
514: for (;;)
515: {
1.1.1.2 ! millert 516: next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
1.1 etheisen 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: }
1.1.1.2 ! millert 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: }
1.1 etheisen 537: h = next;
538: }
539: /*
540: * Found a file that we can edit.
541: */
542: return (0);
543: }
544:
1.1.1.2 ! millert 545: static int
! 546: edit_inext(h, n)
! 547: IFILE h;
! 548: int n;
! 549: {
! 550: return (edit_istep(h, n, 1));
! 551: }
! 552:
1.1 etheisen 553: public int
1.1.1.2 ! millert 554: edit_next(n)
1.1 etheisen 555: int n;
556: {
1.1.1.2 ! millert 557: return edit_istep(curr_ifile, n, 1);
! 558: }
! 559:
! 560: static int
! 561: edit_iprev(h, n)
1.1 etheisen 562: IFILE h;
1.1.1.2 ! millert 563: int n;
! 564: {
! 565: return (edit_istep(h, n, -1));
! 566: }
1.1 etheisen 567:
1.1.1.2 ! millert 568: public int
! 569: edit_prev(n)
! 570: int n;
! 571: {
! 572: return edit_istep(curr_ifile, n, -1);
1.1 etheisen 573: }
574:
575: /*
576: * Edit a specific file in the command line (ifile) list.
577: */
578: public int
579: edit_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:
1.1.1.2 ! millert 599: public IFILE
! 600: save_curr_ifile()
! 601: {
! 602: if (curr_ifile != NULL_IFILE)
! 603: hold_ifile(curr_ifile, 1);
! 604: return (curr_ifile);
! 605: }
! 606:
! 607: public void
! 608: unsave_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
! 619: reedit_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:
1.1 etheisen 652: /*
653: * Edit standard input.
654: */
655: public int
656: edit_stdin()
657: {
658: if (isatty(fd0))
659: {
1.1.1.2 ! millert 660: error("Missing filename (\"less --help\" for help)", NULL_PARG);
1.1 etheisen 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
671: cat_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
688: use_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: */
1.1.1.2 ! millert 704: filename = shell_unquote(filename);
1.1 etheisen 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:
728: loop:
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: */
1.1.1.2 ! millert 752: free(filename);
1.1 etheisen 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);
1.1.1.2 ! millert 772: free(filename);
! 773: return;
1.1 etheisen 774: }
1.1.1.2 ! millert 775: free(filename);
! 776: SET_BINARY(logfile);
1.1 etheisen 777: }
778:
779: #endif