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