xref: /freebsd/contrib/nvi/common/main.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
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 }
638