Merge from vendor branch DIFFUTILS:
[dragonfly.git] / contrib / nvi / common / main.c
1 /*-
2  * Copyright (c) 1992, 1993, 1994
3  *      The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1992, 1993, 1994, 1995, 1996
5  *      Keith Bostic.  All rights reserved.
6  *
7  * See the LICENSE file for redistribution information.
8  */
9
10 #include "config.h"
11
12 #ifndef lint
13 static const char copyright[] =
14 "@(#) Copyright (c) 1992, 1993, 1994\n\
15         The Regents of the University of California.  All rights reserved.\n\
16 @(#) Copyright (c) 1992, 1993, 1994, 1995, 1996\n\
17         Keith Bostic.  All rights reserved.\n";
18 #endif /* not lint */
19
20 #ifndef lint
21 static const char sccsid[] = "@(#)main.c        10.48 (Berkeley) 10/11/96";
22 #endif /* not lint */
23
24 #include <sys/types.h>
25 #include <sys/queue.h>
26 #include <sys/stat.h>
27 #include <sys/time.h>
28
29 #include <bitstring.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <limits.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37
38 #include "common.h"
39 #include "../vi/vi.h"
40 #include "pathnames.h"
41
42 static void      attach __P((GS *));
43 static void      v_estr __P((char *, int, char *));
44 static int       v_obsolete __P((char *, char *[]));
45
46 /*
47  * editor --
48  *      Main editor routine.
49  *
50  * PUBLIC: int editor __P((GS *, int, char *[]));
51  */
52 int
53 editor(gp, argc, argv)
54         GS *gp;
55         int argc;
56         char *argv[];
57 {
58         extern int optind;
59         extern char *optarg;
60         const char *p;
61         EVENT ev;
62         FREF *frp;
63         SCR *sp;
64         size_t len;
65         u_int flags;
66         int ch, flagchk, lflag, secure, startup, readonly, rval, silent;
67 #ifdef GTAGS
68         int gtags = 0;
69 #endif
70         char *tag_f, *wsizearg, path[256];
71
72         /* Initialize the busy routine, if not defined by the screen. */
73         if (gp->scr_busy == NULL)
74                 gp->scr_busy = vs_busy;
75         /* Initialize the message routine, if not defined by the screen. */
76         if (gp->scr_msg == NULL)
77                 gp->scr_msg = vs_msg;
78
79         /* Common global structure initialization. */
80         CIRCLEQ_INIT(&gp->dq);
81         CIRCLEQ_INIT(&gp->hq);
82         LIST_INIT(&gp->ecq);
83         LIST_INSERT_HEAD(&gp->ecq, &gp->excmd, q);
84         gp->noprint = DEFAULT_NOPRINT;
85
86         /* Structures shared by screens so stored in the GS structure. */
87         CIRCLEQ_INIT(&gp->frefq);
88         CIRCLEQ_INIT(&gp->dcb_store.textq);
89         LIST_INIT(&gp->cutq);
90         LIST_INIT(&gp->seqq);
91
92         /* Set initial screen type and mode based on the program name. */
93         readonly = 0;
94         if (!strcmp(gp->progname, "ex") || !strcmp(gp->progname, "nex"))
95                 LF_INIT(SC_EX);
96         else {
97                 /* Nview, view are readonly. */
98                 if (!strcmp(gp->progname, "nview") ||
99                     !strcmp(gp->progname, "view"))
100                         readonly = 1;
101                 
102                 /* Vi is the default. */
103                 LF_INIT(SC_VI);
104         }
105
106         /* Convert old-style arguments into new-style ones. */
107         if (v_obsolete(gp->progname, argv))
108                 return (1);
109
110         /* Parse the arguments. */
111         flagchk = '\0';
112         tag_f = wsizearg = NULL;
113         lflag = secure = silent = 0;
114         startup = 1;
115
116         /* Set the file snapshot flag. */
117         F_SET(gp, G_SNAPSHOT);
118
119 #ifdef GTAGS
120 #ifdef DEBUG
121         while ((ch = getopt(argc, argv, "c:D:eFGlRrSsT:t:vw:")) != EOF)
122 #else
123         while ((ch = getopt(argc, argv, "c:eFGlRrSst:vw:")) != EOF)
124 #endif
125 #else
126 #ifdef DEBUG
127         while ((ch = getopt(argc, argv, "c:D:eFlRrSsT:t:vw:")) != EOF)
128 #else
129         while ((ch = getopt(argc, argv, "c:eFlRrSst:vw:")) != EOF)
130 #endif
131 #endif
132                 switch (ch) {
133                 case 'c':               /* Run the command. */
134                         /*
135                          * XXX
136                          * We should support multiple -c options.
137                          */
138                         if (gp->c_option != NULL) {
139                                 v_estr(gp->progname, 0,
140                                     "only one -c command may be specified.");
141                                 return (1);
142                         }
143                         gp->c_option = optarg;
144                         break;
145 #ifdef DEBUG
146                 case 'D':
147                         switch (optarg[0]) {
148                         case 's':
149                                 startup = 0;
150                                 break;
151                         case 'w':
152                                 attach(gp);
153                                 break;
154                         default:
155                                 v_estr(gp->progname, 0,
156                                     "usage: -D requires s or w argument.");
157                                 return (1);
158                         }
159                         break;
160 #endif
161                 case 'e':               /* Ex mode. */
162                         LF_CLR(SC_VI);
163                         LF_SET(SC_EX);
164                         break;
165                 case 'F':               /* No snapshot. */
166                         F_CLR(gp, G_SNAPSHOT);
167                         break;
168 #ifdef GTAGS
169                 case 'G':               /* gtags mode. */
170                         gtags = 1;
171                         break;
172 #endif
173                 case 'l':               /* Set lisp, showmatch options. */
174                         lflag = 1;
175                         break;
176                 case 'R':               /* Readonly. */
177                         readonly = 1;
178                         break;
179                 case 'r':               /* Recover. */
180                         if (flagchk == 't') {
181                                 v_estr(gp->progname, 0,
182                                     "only one of -r and -t may be specified.");
183                                 return (1);
184                         }
185                         flagchk = 'r';
186                         break;
187                 case 'S':
188                         secure = 1;
189                         break;
190                 case 's':
191                         silent = 1;
192                         break;
193 #ifdef DEBUG
194                 case 'T':               /* Trace. */
195                         if ((gp->tracefp = fopen(optarg, "w")) == NULL) {
196                                 v_estr(gp->progname, errno, optarg);
197                                 goto err;
198                         }
199                         (void)fprintf(gp->tracefp,
200                             "\n===\ntrace: open %s\n", optarg);
201                         break;
202 #endif
203                 case 't':               /* Tag. */
204                         if (flagchk == 'r') {
205                                 v_estr(gp->progname, 0,
206                                     "only one of -r and -t may be specified.");
207                                 return (1);
208                         }
209                         if (flagchk == 't') {
210                                 v_estr(gp->progname, 0,
211                                     "only one tag file may be specified.");
212                                 return (1);
213                         }
214                         flagchk = 't';
215                         tag_f = optarg;
216                         break;
217                 case 'v':               /* Vi mode. */
218                         LF_CLR(SC_EX);
219                         LF_SET(SC_VI);
220                         break;
221                 case 'w':
222                         wsizearg = optarg;
223                         break;
224                 case '?':
225                 default:
226                         (void)gp->scr_usage();
227                         return (1);
228                 }
229         argc -= optind;
230         argv += optind;
231
232         /*
233          * -s option is only meaningful to ex.
234          *
235          * If not reading from a terminal, it's like -s was specified.
236          */
237         if (silent && !LF_ISSET(SC_EX)) {
238                 v_estr(gp->progname, 0, "-s option is only applicable to ex.");
239                 goto err;
240         }
241         if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED))
242                 silent = 1;
243
244         /*
245          * Build and initialize the first/current screen.  This is a bit
246          * tricky.  If an error is returned, we may or may not have a
247          * screen structure.  If we have a screen structure, put it on a
248          * display queue so that the error messages get displayed.
249          *
250          * !!!
251          * Everything we do until we go interactive is done in ex mode.
252          */
253         if (screen_init(gp, NULL, &sp)) {
254                 if (sp != NULL)
255                         CIRCLEQ_INSERT_HEAD(&gp->dq, sp, q);
256                 goto err;
257         }
258         F_SET(sp, SC_EX);
259         CIRCLEQ_INSERT_HEAD(&gp->dq, sp, q);
260
261         if (v_key_init(sp))             /* Special key initialization. */
262                 goto err;
263
264         { int oargs[5], *oargp = oargs;
265         if (lflag) {                    /* Command-line options. */
266                 *oargp++ = O_LISP;
267                 *oargp++ = O_SHOWMATCH;
268         }
269         if (readonly)
270                 *oargp++ = O_READONLY;
271 #ifdef GTAGS
272         if (gtags)
273                 *oargp++ = O_GTAGSMODE;
274 #endif
275         if (secure)
276                 *oargp++ = O_SECURE;
277         *oargp = -1;                    /* Options initialization. */
278         if (opts_init(sp, oargs))
279                 goto err;
280         }
281         if (wsizearg != NULL) {
282                 ARGS *av[2], a, b;
283                 (void)snprintf(path, sizeof(path), "window=%s", wsizearg);
284                 a.bp = (CHAR_T *)path;
285                 a.len = strlen(path);
286                 b.bp = NULL;
287                 b.len = 0;
288                 av[0] = &a;
289                 av[1] = &b;
290                 (void)opts_set(sp, av, NULL);
291         }
292         if (silent) {                   /* Ex batch mode option values. */
293                 O_CLR(sp, O_AUTOPRINT);
294                 O_CLR(sp, O_PROMPT);
295                 O_CLR(sp, O_VERBOSE);
296                 O_CLR(sp, O_WARN);
297                 F_SET(sp, SC_EX_SILENT);
298         }
299
300         sp->rows = O_VAL(sp, O_LINES);  /* Make ex formatting work. */
301         sp->cols = O_VAL(sp, O_COLUMNS);
302
303         if (!silent && startup) {       /* Read EXINIT, exrc files. */
304                 if (ex_exrc(sp))
305                         goto err;
306                 if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) {
307                         if (screen_end(sp))
308                                 goto err;
309                         goto done;
310                 }
311         }
312
313         /*
314          * List recovery files if -r specified without file arguments.
315          * Note, options must be initialized and startup information
316          * read before doing this.
317          */
318         if (flagchk == 'r' && argv[0] == NULL) {
319                 if (rcv_list(sp))
320                         goto err;
321                 if (screen_end(sp))
322                         goto err;
323                 goto done;
324         }
325
326         /*
327          * !!!
328          * Initialize the default ^D, ^U scrolling value here, after the
329          * user has had every opportunity to set the window option.
330          *
331          * It's historic practice that changing the value of the window
332          * option did not alter the default scrolling value, only giving
333          * a count to ^D/^U did that.
334          */
335         sp->defscroll = (O_VAL(sp, O_WINDOW) + 1) / 2;
336
337         /*
338          * If we don't have a command-line option, switch into the right
339          * editor now, so that we position default files correctly, and
340          * so that any tags file file-already-locked messages are in the
341          * vi screen, not the ex screen.
342          *
343          * XXX
344          * If we have a command-line option, the error message can end
345          * up in the wrong place, but I think that the combination is
346          * unlikely.
347          */
348         if (gp->c_option == NULL) {
349                 F_CLR(sp, SC_EX | SC_VI);
350                 F_SET(sp, LF_ISSET(SC_EX | SC_VI));
351         }
352
353         /* Open a tag file if specified. */
354         if (tag_f != NULL && ex_tag_first(sp, tag_f))
355                 goto err;
356
357         /*
358          * Append any remaining arguments as file names.  Files are recovery
359          * files if -r specified.  If the tag option or ex startup commands
360          * loaded a file, then any file arguments are going to come after it.
361          */
362         if (*argv != NULL) {
363                 if (sp->frp != NULL) {
364                         /* Cheat -- we know we have an extra argv slot. */
365                         MALLOC_NOMSG(sp,
366                             *--argv, char *, strlen(sp->frp->name) + 1);
367                         if (*argv == NULL) {
368                                 v_estr(gp->progname, errno, NULL);
369                                 goto err;
370                         }
371                         (void)strcpy(*argv, sp->frp->name);
372                 }
373                 sp->argv = sp->cargv = argv;
374                 F_SET(sp, SC_ARGNOFREE);
375                 if (flagchk == 'r')
376                         F_SET(sp, SC_ARGRECOVER);
377         }
378
379         /*
380          * If the ex startup commands and or/the tag option haven't already
381          * created a file, create one.  If no command-line files were given,
382          * use a temporary file.
383          */
384         if (sp->frp == NULL) {
385                 if (sp->argv == NULL) {
386                         if ((frp = file_add(sp, NULL)) == NULL)
387                                 goto err;
388                 } else  {
389                         if ((frp = file_add(sp, (CHAR_T *)sp->argv[0])) == NULL)
390                                 goto err;
391                         if (F_ISSET(sp, SC_ARGRECOVER))
392                                 F_SET(frp, FR_RECOVER);
393                 }
394
395                 if (file_init(sp, frp, NULL, 0))
396                         goto err;
397                 if (EXCMD_RUNNING(gp)) {
398                         (void)ex_cmd(sp);
399                         if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) {
400                                 if (screen_end(sp))
401                                         goto err;
402                                 goto done;
403                         }
404                 }
405         }
406
407         /*
408          * Check to see if we need to wait for ex.  If SC_SCR_EX is set, ex
409          * was forced to initialize the screen during startup.  We'd like to
410          * wait for a single character from the user, but we can't because
411          * we're not in raw mode.  We can't switch to raw mode because the
412          * vi initialization will switch to xterm's alternate screen, causing
413          * us to lose the messages we're pausing to make sure the user read.
414          * So, wait for a complete line.  
415          */
416         if (F_ISSET(sp, SC_SCR_EX)) {
417                 p = msg_cmsg(sp, CMSG_CONT_R, &len);
418                 (void)write(STDOUT_FILENO, p, len);
419                 for (;;) {
420                         if (v_event_get(sp, &ev, 0, 0))
421                                 goto err;
422                         if (ev.e_event == E_INTERRUPT ||
423                             ev.e_event == E_CHARACTER &&
424                             (ev.e_value == K_CR || ev.e_value == K_NL))
425                                 break;
426                         (void)gp->scr_bell(sp);
427                 }
428         }
429
430         /* Switch into the right editor, regardless. */
431         F_CLR(sp, SC_EX | SC_VI);
432         F_SET(sp, LF_ISSET(SC_EX | SC_VI) | SC_STATUS_CNT);
433
434         /*
435          * Main edit loop.  Vi handles split screens itself, we only return
436          * here when switching editor modes or restarting the screen.
437          */
438         while (sp != NULL)
439                 if (F_ISSET(sp, SC_EX) ? ex(&sp) : vi(&sp))
440                         goto err;
441
442 done:   rval = 0;
443         if (0)
444 err:            rval = 1;
445
446         /* Clean out the global structure. */
447         v_end(gp);
448
449         return (rval);
450 }
451
452 /*
453  * v_end --
454  *      End the program, discarding screens and most of the global area.
455  *
456  * PUBLIC: void v_end __P((GS *));
457  */
458 void
459 v_end(gp)
460         GS *gp;
461 {
462         MSGS *mp;
463         SCR *sp;
464
465         /* If there are any remaining screens, kill them off. */
466         if (gp->ccl_sp != NULL) {
467                 (void)file_end(gp->ccl_sp, NULL, 1);
468                 (void)screen_end(gp->ccl_sp);
469         }
470         while ((sp = gp->dq.cqh_first) != (void *)&gp->dq)
471                 (void)screen_end(sp);
472         while ((sp = gp->hq.cqh_first) != (void *)&gp->hq)
473                 (void)screen_end(sp);
474
475 #ifdef HAVE_PERL_INTERP
476         perl_end(gp);
477 #endif
478
479 #if defined(DEBUG) || defined(PURIFY) || defined(LIBRARY)
480         { FREF *frp;
481                 /* Free FREF's. */
482                 while ((frp = gp->frefq.cqh_first) != (FREF *)&gp->frefq) {
483                         CIRCLEQ_REMOVE(&gp->frefq, frp, q);
484                         if (frp->name != NULL)
485                                 free(frp->name);
486                         if (frp->tname != NULL)
487                                 free(frp->tname);
488                         free(frp);
489                 }
490         }
491
492         /* Free key input queue. */
493         if (gp->i_event != NULL)
494                 free(gp->i_event);
495
496         /* Free cut buffers. */
497         cut_close(gp);
498
499         /* Free map sequences. */
500         seq_close(gp);
501
502         /* Free default buffer storage. */
503         (void)text_lfree(&gp->dcb_store.textq);
504
505         /* Close message catalogs. */
506         msg_close(gp);
507 #endif
508
509         /* Ring the bell if scheduled. */
510         if (F_ISSET(gp, G_BELLSCHED))
511                 (void)fprintf(stderr, "\07");           /* \a */
512
513         /*
514          * Flush any remaining messages.  If a message is here, it's almost
515          * certainly the message about the event that killed us (although
516          * it's possible that the user is sourcing a file that exits from the
517          * editor).
518          */
519         while ((mp = gp->msgq.lh_first) != NULL) {
520                 (void)fprintf(stderr, "%s%.*s",
521                     mp->mtype == M_ERR ? "ex/vi: " : "", (int)mp->len, mp->buf);
522                 LIST_REMOVE(mp, q);
523 #if defined(DEBUG) || defined(PURIFY) || defined(LIBRARY)
524                 free(mp->buf);
525                 free(mp);
526 #endif
527         }
528
529 #if defined(DEBUG) || defined(PURIFY) || defined(LIBRARY)
530         /* Free any temporary space. */
531         if (gp->tmp_bp != NULL)
532                 free(gp->tmp_bp);
533
534 #if defined(DEBUG)
535         /* Close debugging file descriptor. */
536         if (gp->tracefp != NULL)
537                 (void)fclose(gp->tracefp);
538 #endif
539 #endif
540 }
541
542 /*
543  * v_obsolete --
544  *      Convert historic arguments into something getopt(3) will like.
545  */
546 static int
547 v_obsolete(name, argv)
548         char *name, *argv[];
549 {
550         size_t len;
551         char *p;
552
553         /*
554          * Translate old style arguments into something getopt will like.
555          * Make sure it's not text space memory, because ex modifies the
556          * strings.
557          *      Change "+" into "-c$".
558          *      Change "+<anything else>" into "-c<anything else>".
559          *      Change "-" into "-s"
560          *      The c, T, t and w options take arguments so they can't be
561          *          special arguments.
562          *
563          * Stop if we find "--" as an argument, the user may want to edit
564          * a file named "+foo".
565          */
566         while (*++argv && strcmp(argv[0], "--"))
567                 if (argv[0][0] == '+') {
568                         if (argv[0][1] == '\0') {
569                                 MALLOC_NOMSG(NULL, argv[0], char *, 4);
570                                 if (argv[0] == NULL)
571                                         goto nomem;
572                                 (void)strcpy(argv[0], "-c$");
573                         } else  {
574                                 p = argv[0];
575                                 len = strlen(argv[0]);
576                                 MALLOC_NOMSG(NULL, argv[0], char *, len + 2);
577                                 if (argv[0] == NULL)
578                                         goto nomem;
579                                 argv[0][0] = '-';
580                                 argv[0][1] = 'c';
581                                 (void)strcpy(argv[0] + 2, p + 1);
582                         }
583                 } else if (argv[0][0] == '-')
584                         if (argv[0][1] == '\0') {
585                                 MALLOC_NOMSG(NULL, argv[0], char *, 3);
586                                 if (argv[0] == NULL) {
587 nomem:                                  v_estr(name, errno, NULL);
588                                         return (1);
589                                 }
590                                 (void)strcpy(argv[0], "-s");
591                         } else
592                                 if ((argv[0][1] == 'c' || argv[0][1] == 'T' ||
593                                     argv[0][1] == 't' || argv[0][1] == 'w') &&
594                                     argv[0][2] == '\0')
595                                         ++argv;
596         return (0);
597 }
598
599 #ifdef DEBUG
600 static void
601 attach(gp)
602         GS *gp;
603 {
604         int fd;
605         char ch;
606
607         if ((fd = open(_PATH_TTY, O_RDONLY, 0)) < 0) {
608                 v_estr(gp->progname, errno, _PATH_TTY);
609                 return;
610         }
611
612         (void)printf("process %lu waiting, enter <CR> to continue: ",
613             (u_long)getpid());
614         (void)fflush(stdout);
615
616         do {
617                 if (read(fd, &ch, 1) != 1) {
618                         (void)close(fd);
619                         return;
620                 }
621         } while (ch != '\n' && ch != '\r');
622         (void)close(fd);
623 }
624 #endif
625
626 static void
627 v_estr(name, eno, msg)
628         char *name, *msg;
629         int eno;
630 {
631         (void)fprintf(stderr, "%s", name);
632         if (msg != NULL)
633                 (void)fprintf(stderr, ": %s", msg);
634         if (eno)
635                 (void)fprintf(stderr, ": %s", strerror(errno));
636         (void)fprintf(stderr, "\n");
637 }