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