Annotation of src/usr.bin/less/edit.c, Revision 1.3
1.3 ! mpech 1: /* $OpenBSD: edit.c,v 1.2 2001/01/29 01:58:01 niklas Exp $ */
1.2 niklas 2:
1.1 etheisen 3: /*
4: * Copyright (c) 1984,1985,1989,1994,1995 Mark Nudelman
5: * All rights reserved.
6: *
7: * Redistribution and use in source and binary forms, with or without
8: * modification, are permitted provided that the following conditions
9: * are met:
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. Redistributions in binary form must reproduce the above copyright
13: * notice in the documentation and/or other materials provided with
14: * the distribution.
15: *
16: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
17: * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE
20: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
22: * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
23: * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
26: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27: */
28:
29:
30: #include "less.h"
31:
32: public int fd0 = 0;
33:
34: extern int new_file;
35: extern int errmsgs;
36: extern int quit_at_eof;
37: extern int cbufs;
38: extern char *every_first_cmd;
39: extern int any_display;
40: extern int force_open;
41: extern int is_tty;
42: extern IFILE curr_ifile;
43: extern IFILE old_ifile;
44: extern struct scrpos initial_scrpos;
45:
46: #if LOGFILE
47: extern int logfile;
48: extern int force_logfile;
49: extern char *namelogfile;
50: #endif
51:
52: char *curr_altfilename = NULL;
53: static void *curr_altpipe;
54:
55:
56: /*
57: * Textlist functions deal with a list of words separated by spaces.
58: * init_textlist sets up a textlist structure.
59: * forw_textlist uses that structure to iterate thru the list of
60: * words, returning each one as a standard null-terminated string.
61: * back_textlist does the same, but runs thru the list backwards.
62: */
63: public void
64: init_textlist(tlist, str)
65: struct textlist *tlist;
66: char *str;
67: {
68: char *s;
69:
70: tlist->string = skipsp(str);
71: tlist->endstring = tlist->string + strlen(tlist->string);
72: for (s = str; s < tlist->endstring; s++)
73: {
74: if (*s == ' ')
75: *s = '\0';
76: }
77: }
78:
79: public char *
80: forw_textlist(tlist, prev)
81: struct textlist *tlist;
82: char *prev;
83: {
84: char *s;
85:
86: /*
87: * prev == NULL means return the first word in the list.
88: * Otherwise, return the word after "prev".
89: */
90: if (prev == NULL)
91: s = tlist->string;
92: else
93: s = prev + strlen(prev);
94: if (s >= tlist->endstring)
95: return (NULL);
96: while (*s == '\0')
97: s++;
98: if (s >= tlist->endstring)
99: return (NULL);
100: return (s);
101: }
102:
103: public char *
104: back_textlist(tlist, prev)
105: struct textlist *tlist;
106: char *prev;
107: {
108: char *s;
109:
110: /*
111: * prev == NULL means return the last word in the list.
112: * Otherwise, return the word before "prev".
113: */
114: if (prev == NULL)
115: s = tlist->endstring;
116: else if (prev <= tlist->string)
117: return (NULL);
118: else
119: s = prev - 1;
120: while (*s == '\0')
121: s--;
122: if (s <= tlist->string)
123: return (NULL);
124: while (s[-1] != '\0' && s > tlist->string)
125: s--;
126: return (s);
127: }
128:
129: /*
130: * Close the current input file.
131: */
132: static void
133: close_file()
134: {
135: struct scrpos scrpos;
136:
137: if (curr_ifile == NULL_IFILE)
138: return;
139: /*
140: * Save the current position so that we can return to
141: * the same position if we edit this file again.
142: */
143: get_scrpos(&scrpos);
144: if (scrpos.pos != NULL_POSITION)
145: {
146: store_pos(curr_ifile, &scrpos);
147: lastmark();
148: }
149: /*
150: * Close the file descriptor, unless it is a pipe.
151: */
152: ch_close();
153: /*
154: * If we opened a file using an alternate name,
155: * do special stuff to close it.
156: */
157: if (curr_altfilename != NULL)
158: {
159: close_altfile(curr_altfilename, get_filename(curr_ifile),
160: curr_altpipe);
161: free(curr_altfilename);
162: curr_altfilename = NULL;
163: }
164: curr_ifile = NULL_IFILE;
165: }
166:
167: /*
168: * Edit a new file (given its name).
169: * Filename == "-" means standard input.
170: * Filename == NULL means just close the current file.
171: */
172: public int
173: edit(filename)
174: char *filename;
175: {
176: if (filename == NULL)
177: return (edit_ifile(NULL_IFILE));
178: return (edit_ifile(get_ifile(filename, curr_ifile)));
179: }
180:
181: /*
182: * Edit a new file (given its IFILE).
183: * ifile == NULL means just close the current file.
184: */
185: public int
186: edit_ifile(ifile)
187: IFILE ifile;
188: {
189: int f;
190: int answer;
191: int no_display;
192: int chflags;
193: char *filename;
194: char *open_filename;
195: char *alt_filename;
196: void *alt_pipe;
197: IFILE was_curr_ifile;
198: PARG parg;
199:
200: if (ifile == curr_ifile)
201: {
202: /*
203: * Already have the correct file open.
204: */
205: return (0);
206: }
207:
208: /*
209: * We must close the currently open file now.
210: * This is necessary to make the open_altfile/close_altfile pairs
211: * nest properly (or rather to avoid nesting at all).
212: * {{ Some stupid implementations of popen() mess up if you do:
213: * fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }}
214: */
215: #if LOGFILE
216: end_logfile();
217: #endif
218: was_curr_ifile = curr_ifile;
219: if (curr_ifile != NULL_IFILE)
220: {
221: close_file();
222: }
223:
224: if (ifile == NULL_IFILE)
225: {
226: /*
227: * No new file to open.
228: * (Don't set old_ifile, because if you call edit_ifile(NULL),
229: * you're supposed to have saved curr_ifile yourself,
230: * and you'll restore it if necessary.)
231: */
232: return (0);
233: }
234:
235: filename = get_filename(ifile);
236: /*
237: * See if LESSOPEN specifies an "alternate" file to open.
238: */
239: alt_pipe = NULL;
240: alt_filename = open_altfile(filename, &f, &alt_pipe);
241: open_filename = (alt_filename != NULL) ? alt_filename : filename;
242:
243: chflags = 0;
244: if (alt_pipe != NULL)
245: {
246: /*
247: * The alternate "file" is actually a pipe.
248: * f has already been set to the file descriptor of the pipe
249: * in the call to open_altfile above.
250: * Keep the file descriptor open because it was opened
251: * via popen(), and pclose() wants to close it.
252: */
253: chflags |= CH_POPENED;
254: } else if (strcmp(open_filename, "-") == 0)
255: {
256: /*
257: * Use standard input.
258: * Keep the file descriptor open because we can't reopen it.
259: */
260: f = fd0;
261: chflags |= CH_KEEPOPEN;
262: } else if ((parg.p_string = bad_file(open_filename)) != NULL)
263: {
264: /*
265: * It looks like a bad file. Don't try to open it.
266: */
267: error("%s", &parg);
268: free(parg.p_string);
269: err1:
270: if (alt_filename != NULL)
271: {
272: close_altfile(alt_filename, filename, alt_pipe);
273: free(alt_filename);
274: }
275: del_ifile(ifile);
276: /*
277: * Re-open the current file.
278: */
279: (void) edit_ifile(was_curr_ifile);
280: return (1);
281: } else if ((f = open(open_filename, OPEN_READ)) < 0)
282: {
283: /*
284: * Got an error trying to open it.
285: */
286: parg.p_string = errno_message(filename);
287: error("%s", &parg);
288: free(parg.p_string);
289: goto err1;
290: } else if (!force_open && !opened(ifile) && bin_file(f))
291: {
292: /*
293: * Looks like a binary file. Ask user if we should proceed.
294: */
295: parg.p_string = filename;
296: answer = query("\"%s\" may be a binary file. See it anyway? ",
297: &parg);
298: if (answer != 'y' && answer != 'Y')
299: {
300: close(f);
301: goto err1;
302: }
303: }
304:
305: /*
306: * Get the new ifile.
307: * Get the saved position for the file.
308: */
309: if (was_curr_ifile != NULL_IFILE)
310: old_ifile = was_curr_ifile;
311: curr_ifile = ifile;
312: curr_altfilename = alt_filename;
313: curr_altpipe = alt_pipe;
314: set_open(curr_ifile); /* File has been opened */
315: get_pos(curr_ifile, &initial_scrpos);
316: new_file = TRUE;
317: ch_init(f, chflags);
318: #if LOGFILE
319: if (namelogfile != NULL && is_tty)
320: use_logfile(namelogfile);
321: #endif
322:
323: if (every_first_cmd != NULL)
324: ungetsc(every_first_cmd);
325:
326: no_display = !any_display;
327: flush();
328: any_display = TRUE;
329:
330: if (is_tty)
331: {
332: /*
333: * Output is to a real tty.
334: */
335:
336: /*
337: * Indicate there is nothing displayed yet.
338: */
339: pos_clear();
340: clr_linenum();
341: #if HILITE_SEARCH
342: clr_hilite();
343: #endif
344: if (no_display && errmsgs > 0)
345: {
346: /*
347: * We displayed some messages on error output
348: * (file descriptor 2; see error() function).
349: * Before erasing the screen contents,
350: * display the file name and wait for a keystroke.
351: */
352: parg.p_string = filename;
353: error("%s", &parg);
354: }
355: }
356: return (0);
357: }
358:
359: /*
360: * Edit a space-separated list of files.
361: * For each filename in the list, enter it into the ifile list.
362: * Then edit the first one.
363: */
364: public int
365: edit_list(filelist)
366: char *filelist;
367: {
368: IFILE save_curr_ifile;
369: char *good_filename;
370: char *filename;
371: char *gfilelist;
372: char *gfilename;
373: struct textlist tl_files;
374: struct textlist tl_gfiles;
375:
376: save_curr_ifile = curr_ifile;
377: good_filename = NULL;
378:
379: /*
380: * Run thru each filename in the list.
381: * Try to glob the filename.
382: * If it doesn't expand, just try to open the filename.
383: * If it does expand, try to open each name in that list.
384: */
385: init_textlist(&tl_files, filelist);
386: filename = NULL;
387: while ((filename = forw_textlist(&tl_files, filename)) != NULL)
388: {
389: gfilelist = glob(filename);
390: init_textlist(&tl_gfiles, gfilelist);
391: gfilename = NULL;
392: while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
393: {
394: if (edit(gfilename) == 0 && good_filename == NULL)
395: good_filename = get_filename(curr_ifile);
396: }
397: free(gfilelist);
398: }
399: /*
400: * Edit the first valid filename in the list.
401: */
402: if (good_filename == NULL)
403: return (1);
404: if (get_ifile(good_filename, curr_ifile) == curr_ifile)
405: /*
406: * Trying to edit the current file; don't reopen it.
407: */
408: return (0);
409: if (edit_ifile(save_curr_ifile))
410: quit(QUIT_ERROR);
411: return (edit(good_filename));
412: }
413:
414: /*
415: * Edit the first file in the command line (ifile) list.
416: */
417: public int
418: edit_first()
419: {
420: curr_ifile = NULL_IFILE;
421: return (edit_next(1));
422: }
423:
424: /*
425: * Edit the last file in the command line (ifile) list.
426: */
427: public int
428: edit_last()
429: {
430: curr_ifile = NULL_IFILE;
431: return (edit_prev(1));
432: }
433:
434:
435: /*
436: * Edit the next file in the command line (ifile) list.
437: */
438: public int
439: edit_next(n)
440: int n;
441: {
442: IFILE h;
443: IFILE next;
444:
445: h = curr_ifile;
446: /*
447: * Skip n filenames, then try to edit each filename.
448: */
449: for (;;)
450: {
451: next = next_ifile(h);
452: if (--n < 0)
453: {
454: if (edit_ifile(h) == 0)
455: break;
456: }
457: if (next == NULL_IFILE)
458: {
459: /*
460: * Reached end of the ifile list.
461: */
462: return (1);
463: }
464: h = next;
465: }
466: /*
467: * Found a file that we can edit.
468: */
469: return (0);
470: }
471:
472: /*
473: * Edit the previous file in the command line list.
474: */
475: public int
476: edit_prev(n)
477: int n;
478: {
479: IFILE h;
480: IFILE next;
481:
482: h = curr_ifile;
483: /*
484: * Skip n filenames, then try to edit each filename.
485: */
486: for (;;)
487: {
488: next = prev_ifile(h);
489: if (--n < 0)
490: {
491: if (edit_ifile(h) == 0)
492: break;
493: }
494: if (next == NULL_IFILE)
495: {
496: /*
497: * Reached beginning of the ifile list.
498: */
499: return (1);
500: }
501: h = next;
502: }
503: /*
504: * Found a file that we can edit.
505: */
506: return (0);
507: }
508:
509: /*
510: * Edit a specific file in the command line (ifile) list.
511: */
512: public int
513: edit_index(n)
514: int n;
515: {
516: IFILE h;
517:
518: h = NULL_IFILE;
519: do
520: {
521: if ((h = next_ifile(h)) == NULL_IFILE)
522: {
523: /*
524: * Reached end of the list without finding it.
525: */
526: return (1);
527: }
528: } while (get_index(h) != n);
529:
530: return (edit_ifile(h));
531: }
532:
533: /*
534: * Edit standard input.
535: */
536: public int
537: edit_stdin()
538: {
539: if (isatty(fd0))
540: {
541: #if MSOFTC || OS2
542: error("Missing filename (\"less -?\" for help)", NULL_PARG);
543: #else
544: error("Missing filename (\"less -\\?\" for help)", NULL_PARG);
545: #endif
546: quit(QUIT_OK);
547: }
548: return (edit("-"));
549: }
550:
551: /*
552: * Copy a file directly to standard output.
553: * Used if standard output is not a tty.
554: */
555: public void
556: cat_file()
557: {
1.3 ! mpech 558: int c;
1.1 etheisen 559:
560: while ((c = ch_forw_get()) != EOI)
561: putchr(c);
562: flush();
563: }
564:
565: #if LOGFILE
566:
567: /*
568: * If the user asked for a log file and our input file
569: * is standard input, create the log file.
570: * We take care not to blindly overwrite an existing file.
571: */
572: public void
573: use_logfile(filename)
574: char *filename;
575: {
1.3 ! mpech 576: int exists;
! 577: int answer;
1.1 etheisen 578: PARG parg;
579:
580: if (ch_getflags() & CH_CANSEEK)
581: /*
582: * Can't currently use a log file on a file that can seek.
583: */
584: return;
585:
586: /*
587: * {{ We could use access() here. }}
588: */
589: exists = open(filename, OPEN_READ);
590: close(exists);
591: exists = (exists >= 0);
592:
593: /*
594: * Decide whether to overwrite the log file or append to it.
595: * If it doesn't exist we "overwrite" it.
596: */
597: if (!exists || force_logfile)
598: {
599: /*
600: * Overwrite (or create) the log file.
601: */
602: answer = 'O';
603: } else
604: {
605: /*
606: * Ask user what to do.
607: */
608: parg.p_string = filename;
609: answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg);
610: }
611:
612: loop:
613: switch (answer)
614: {
615: case 'O': case 'o':
616: /*
617: * Overwrite: create the file.
618: */
619: logfile = creat(filename, 0644);
620: break;
621: case 'A': case 'a':
622: /*
623: * Append: open the file and seek to the end.
624: */
625: logfile = open(filename, OPEN_APPEND);
626: if (lseek(logfile, (off_t)0, 2) == BAD_LSEEK)
627: {
628: close(logfile);
629: logfile = -1;
630: }
631: break;
632: case 'D': case 'd':
633: /*
634: * Don't do anything.
635: */
636: return;
637: case 'q':
638: quit(QUIT_OK);
639: /*NOTREACHED*/
640: default:
641: /*
642: * Eh?
643: */
644: answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG);
645: goto loop;
646: }
647:
648: if (logfile < 0)
649: {
650: /*
651: * Error in opening logfile.
652: */
653: parg.p_string = filename;
654: error("Cannot write to \"%s\"", &parg);
655: }
656: }
657:
658: #endif