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