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