diff options
Diffstat (limited to 'cl')
-rw-r--r-- | cl/README.signal | 174 | ||||
-rw-r--r-- | cl/cl.h | 78 | ||||
-rw-r--r-- | cl/cl_bsd.c | 346 | ||||
-rw-r--r-- | cl/cl_funcs.c | 704 | ||||
-rw-r--r-- | cl/cl_main.c | 471 | ||||
-rw-r--r-- | cl/cl_read.c | 334 | ||||
-rw-r--r-- | cl/cl_screen.c | 581 | ||||
-rw-r--r-- | cl/cl_term.c | 460 |
8 files changed, 3148 insertions, 0 deletions
diff --git a/cl/README.signal b/cl/README.signal new file mode 100644 index 0000000..7faa456 --- /dev/null +++ b/cl/README.signal @@ -0,0 +1,174 @@ +# @(#)README.signal 10.1 (Berkeley) 6/23/95 + +There are six (normally) asynchronous actions about which vi cares: +SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP and SIGWINCH. + +The assumptions: + 1: The DB routines are not reentrant. + 2: The curses routines may not be reentrant. + 3: Neither DB nor curses will restart system calls. + +XXX +Note, most C library functions don't restart system calls. So, we should +*probably* start blocking around any imported function that we don't know +doesn't make a system call. This is going to be a genuine annoyance... + +SIGHUP, SIGTERM + Used for file recovery. The DB routines can't be reentered, nor + can they handle interrupted system calls, so the vi routines that + call DB block signals. This means that DB routines could be + called at interrupt time, if necessary. + +SIGQUIT + Disabled by the signal initialization routines. Historically, ^\ + switched vi into ex mode, and we continue that practice. + +SIGWINCH: + The interrupt routine sets a global bit which is checked by the + key-read routine, so there are no reentrancy issues. This means + that the screen will not resize until vi runs out of keys, but + that doesn't seem like a problem. + +SIGINT and SIGTSTP are a much more difficult issue to resolve. Vi has +to permit the user to interrupt long-running operations. Generally, a +search, substitution or read/write is done on a large file, or, the user +creates a key mapping with an infinite loop. This problem will become +worse as more complex semantics are added to vi, especially things like +making it a pure text widget. There are four major solutions on the table, +each of which have minor permutations. + +1: Run in raw mode. + + The up side is that there's no asynchronous behavior to worry about, + and obviously no reentrancy problems. The down side is that it's easy + to misinterpret characters (e.g. :w big_file^Mi^V^C is going to look + like an interrupt) and it's easy to get into places where we won't see + interrupt characters (e.g. ":map a ixx^[hxxaXXX" infinitely loops in + historic implementations of vi). Periodically reading the terminal + input buffer might solve the latter problem, but it's not going to be + pretty. + + Also, we're going to be checking for ^C's and ^Z's both, all over + the place -- I hate to litter the source code with that. For example, + the historic version of vi didn't permit you to suspend the screen if + you were on the colon command line. This isn't right. ^Z isn't a vi + command, it's a terminal event. (Dammit.) + +2: Run in cbreak mode. There are two problems in this area. First, the + current curses implementations (both System V and Berkeley) don't give + you clean cbreak modes. For example, the IEXTEN bit is left on, turning + on DISCARD and LNEXT. To clarify, what vi WANTS is 8-bit clean, with + the exception that flow control and signals are turned on, and curses + cbreak mode doesn't give you this. + + We can either set raw mode and twiddle the tty, or cbreak mode and + twiddle the tty. I chose to use raw mode, on the grounds that raw + mode is better defined and I'm less likely to be surprised by a curses + implementation down the road. The twiddling consists of setting ISIG, + IXON/IXOFF, and disabling some of the interrupt characters (see the + comments in cl_init.c). This is all found in historic System V (SVID + 3) and POSIX 1003.1-1992, so it should be fairly portable. + + The second problem is that vi permits you to enter literal signal + characters, e.g. ^V^C. There are two possible solutions. First, you + can turn off signals when you get a ^V, but that means that a network + packet containing ^V and ^C will lose, since the ^C may take effect + before vi reads the ^V. (This is particularly problematic if you're + talking over a protocol that recognizes signals locally and sends OOB + packets when it sees them.) Second, you can turn the ^C into a literal + character in vi, but that means that there's a race between entering + ^V<character>^C, i.e. the sequence may end up being ^V^C<character>. + Also, the second solution doesn't work for flow control characters, as + they aren't delivered to the program as signals. + + Generally, this is what historic vi did. (It didn't have the curses + problems because it didn't use curses.) It entered signals following + ^V characters into the input stream, (which is why there's no way to + enter a literal flow control character). + +3: Run in mostly raw mode; turn signals on when doing an operation the + user might want to interrupt, but leave them off most of the time. + + This works well for things like file reads and writes. This doesn't + work well for trying to detect infinite maps. The problem is that + you can write the code so that you don't have to turn on interrupts + per keystroke, but the code isn't pretty and it's hard to make sure + that an optimization doesn't cover up an infinite loop. This also + requires interaction or state between the vi parser and the key + reading routines, as an infinite loop may still be returning keys + to the parser. + + Also, if the user inserts an interrupt into the tty queue while the + interrupts are turned off, the key won't be treated as an interrupt, + and requiring the user to pound the keyboard to catch an interrupt + window is nasty. + +4: Run in mostly raw mode, leaving signals on all of the time. Done + by setting raw mode, and twiddling the tty's termios ISIG bit. + + This works well for the interrupt cases, because the code only has + to check to see if the interrupt flag has been set, and can otherwise + ignore signals. It's also less likely that we'll miss a case, and we + don't have to worry about synchronizing between the vi parser and the + key read routines. + + The down side is that we have to turn signals off if the user wants + to enter a literal character (e.g. ^V^C). If the user enters the + combination fast enough, or as part of a single network packet, + the text input routines will treat it as a signal instead of as a + literal character. To some extent, we have this problem already, + since we turn off flow control so that the user can enter literal + XON/XOFF characters. + + This is probably the easiest to code, and provides the smoothest + programming interface. + +There are a couple of other problems to consider. + +First, System V's curses doesn't handle SIGTSTP correctly. If you use the +newterm() interface, the TSTP signal will leave you in raw mode, and the +final endwin() will leave you in the correct shell mode. If you use the +initscr() interface, the TSTP signal will return you to the correct shell +mode, but the final endwin() will leave you in raw mode. There you have +it: proof that drug testing is not making any significant headway in the +computer industry. The 4BSD curses is deficient in that it does not have +an interface to the terminal keypad. So, regardless, we have to do our +own SIGTSTP handling. + +The problem with this is that if we do our own SIGTSTP handling, in either +models #3 or #4, we're going to have to call curses routines at interrupt +time, which means that we might be reentering curses, which is something we +don't want to do. + +Second, SIGTSTP has its own little problems. It's broadcast to the entire +process group, not sent to a single process. The scenario goes something +like this: the shell execs the mail program, which execs vi. The user hits +^Z, and all three programs get the signal, in some random order. The mail +program goes to sleep immediately (since it probably didn't have a SIGTSTP +handler in place). The shell gets a SIGCHLD, does a wait, and finds out +that the only child in its foreground process group (of which it's aware) +is asleep. It then optionally resets the terminal (because the modes aren't +how it left them), and starts prompting the user for input. The problem is +that somewhere in the middle of all of this, vi is resetting the terminal, +and getting ready to send a SIGTSTP to the process group in order to put +itself to sleep. There's a solution to all of this: when vi starts, it puts +itself into its own process group, and then only it (and possible child +processes) receive the SIGTSTP. This permits it to clean up the terminal +and switch back to the original process group, where it sends that process +group a SIGTSTP, putting everyone to sleep and waking the shell. + +Third, handing SIGTSTP asynchronously is further complicated by the child +processes vi may fork off. If vi calls ex, ex resets the terminal and +starts running some filter, and SIGTSTP stops them both, vi has to know +when it restarts that it can't repaint the screen until ex's child has +finished running. This is solveable, but it's annoying. + +Well, somebody had to make a decision, and this is the way it's going to be +(unless I get talked out of it). SIGINT is handled asynchronously, so +that we can pretty much guarantee that the user can interrupt any operation +at any time. SIGTSTP is handled synchronously, so that we don't have to +reenter curses and so that we don't have to play the process group games. +^Z is recognized in the standard text input and command modes. (^Z should +also be recognized during operations that may potentially take a long time. +The simplest solution is probably to twiddle the tty, install a handler for +SIGTSTP, and then restore normal tty modes when the operation is complete.) @@ -0,0 +1,78 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + * + * @(#)cl.h 10.19 (Berkeley) 9/24/96 + */ + +typedef struct _cl_private { + CHAR_T ibuf[256]; /* Input keys. */ + + int eof_count; /* EOF count. */ + + struct termios orig; /* Original terminal values. */ + struct termios ex_enter;/* Terminal values to enter ex. */ + struct termios vi_enter;/* Terminal values to enter vi. */ + + char *el; /* Clear to EOL terminal string. */ + char *cup; /* Cursor movement terminal string. */ + char *cuu1; /* Cursor up terminal string. */ + char *rmso, *smso; /* Inverse video terminal strings. */ + char *smcup, *rmcup; /* Terminal start/stop strings. */ + + int killersig; /* Killer signal. */ +#define INDX_HUP 0 +#define INDX_INT 1 +#define INDX_TERM 2 +#define INDX_WINCH 3 +#define INDX_MAX 4 /* Original signal information. */ + struct sigaction oact[INDX_MAX]; + + enum { /* Tty group write mode. */ + TGW_UNKNOWN=0, TGW_SET, TGW_UNSET } tgw; + + enum { /* Terminal initialization strings. */ + TE_SENT=0, TI_SENT } ti_te; + +#define CL_IN_EX 0x0001 /* Currently running ex. */ +#define CL_RENAME 0x0002 /* X11 xterm icon/window renamed. */ +#define CL_RENAME_OK 0x0004 /* User wants the windows renamed. */ +#define CL_SCR_EX_INIT 0x0008 /* Ex screen initialized. */ +#define CL_SCR_VI_INIT 0x0010 /* Vi screen initialized. */ +#define CL_SIGHUP 0x0020 /* SIGHUP arrived. */ +#define CL_SIGINT 0x0040 /* SIGINT arrived. */ +#define CL_SIGTERM 0x0080 /* SIGTERM arrived. */ +#define CL_SIGWINCH 0x0100 /* SIGWINCH arrived. */ +#define CL_STDIN_TTY 0x0200 /* Talking to a terminal. */ + u_int32_t flags; +} CL_PRIVATE; + +#define CLP(sp) ((CL_PRIVATE *)((sp)->gp->cl_private)) +#define GCLP(gp) ((CL_PRIVATE *)gp->cl_private) + +/* Return possibilities from the keyboard read routine. */ +typedef enum { INP_OK=0, INP_EOF, INP_ERR, INP_INTR, INP_TIMEOUT } input_t; + +/* The screen line relative to a specific window. */ +#define RLNO(sp, lno) (sp)->woff + (lno) + +/* X11 xterm escape sequence to rename the icon/window. */ +#define XTERM_RENAME "\033]0;%s\007" + +/* + * XXX + * Some implementations of curses.h don't define these for us. Used for + * compatibility only. + */ +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#include "cl_extern.h" diff --git a/cl/cl_bsd.c b/cl/cl_bsd.c new file mode 100644 index 0000000..22124e5 --- /dev/null +++ b/cl/cl_bsd.c @@ -0,0 +1,346 @@ +/*- + * Copyright (c) 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)cl_bsd.c 8.29 (Berkeley) 7/1/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <curses.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "../common/common.h" +#include "../vi/vi.h" +#include "cl.h" + +static char *ke; /* Keypad on. */ +static char *ks; /* Keypad off. */ +static char *vb; /* Visible bell string. */ + +/* + * HP's support the entire System V curses package except for the tigetstr + * and tigetnum functions. Ultrix supports the BSD curses package except + * for the idlok function. Cthulu only knows why. Break things up into a + * minimal set of functions. + */ + +#ifndef HAVE_CURSES_ADDNSTR +/* + * addnstr -- + * + * PUBLIC: #ifndef HAVE_CURSES_ADDNSTR + * PUBLIC: int addnstr __P((char *, int)); + * PUBLIC: #endif + */ +int +addnstr(s, n) + char *s; + int n; +{ + int ch; + + while (n-- && (ch = *s++)) + addch(ch); + return (OK); +} +#endif + +#ifndef HAVE_CURSES_BEEP +/* + * beep -- + * + * PUBLIC: #ifndef HAVE_CURSES_BEEP + * PUBLIC: void beep __P((void)); + * PUBLIC: #endif + */ +void +beep() +{ + (void)write(1, "\007", 1); /* '\a' */ +} +#endif /* !HAVE_CURSES_BEEP */ + +#ifndef HAVE_CURSES_FLASH +/* + * flash -- + * Flash the screen. + * + * PUBLIC: #ifndef HAVE_CURSES_FLASH + * PUBLIC: void flash __P((void)); + * PUBLIC: #endif + */ +void +flash() +{ + if (vb != NULL) { + (void)tputs(vb, 1, cl_putchar); + (void)fflush(stdout); + } else + beep(); +} +#endif /* !HAVE_CURSES_FLASH */ + +#ifndef HAVE_CURSES_IDLOK +/* + * idlok -- + * Turn on/off hardware line insert/delete. + * + * PUBLIC: #ifndef HAVE_CURSES_IDLOK + * PUBLIC: void idlok __P((WINDOW *, int)); + * PUBLIC: #endif + */ +void +idlok(win, bf) + WINDOW *win; + int bf; +{ + return; +} +#endif /* !HAVE_CURSES_IDLOK */ + +#ifndef HAVE_CURSES_KEYPAD +/* + * keypad -- + * Put the keypad/cursor arrows into or out of application mode. + * + * PUBLIC: #ifndef HAVE_CURSES_KEYPAD + * PUBLIC: int keypad __P((void *, int)); + * PUBLIC: #endif + */ +int +keypad(a, on) + void *a; + int on; +{ + char *p; + + if ((p = tigetstr(on ? "smkx" : "rmkx")) != (char *)-1) { + (void)tputs(p, 0, cl_putchar); + (void)fflush(stdout); + } + return (0); +} +#endif /* !HAVE_CURSES_KEYPAD */ + +#ifndef HAVE_CURSES_NEWTERM +/* + * newterm -- + * Create a new curses screen. + * + * PUBLIC: #ifndef HAVE_CURSES_NEWTERM + * PUBLIC: void *newterm __P((const char *, FILE *, FILE *)); + * PUBLIC: #endif + */ +void * +newterm(a, b, c) + const char *a; + FILE *b, *c; +{ + return (initscr()); +} +#endif /* !HAVE_CURSES_NEWTERM */ + +#ifndef HAVE_CURSES_SETUPTERM +/* + * setupterm -- + * Set up terminal. + * + * PUBLIC: #ifndef HAVE_CURSES_SETUPTERM + * PUBLIC: void setupterm __P((char *, int, int *)); + * PUBLIC: #endif + */ +void +setupterm(ttype, fno, errp) + char *ttype; + int fno, *errp; +{ + static char buf[2048]; + char *p; + + if ((*errp = tgetent(buf, ttype)) > 0) { + if (ke != NULL) + free(ke); + ke = ((p = tigetstr("rmkx")) == (char *)-1) ? + NULL : strdup(p); + if (ks != NULL) + free(ks); + ks = ((p = tigetstr("smkx")) == (char *)-1) ? + NULL : strdup(p); + if (vb != NULL) + free(vb); + vb = ((p = tigetstr("flash")) == (char *)-1) ? + NULL : strdup(p); + } +} +#endif /* !HAVE_CURSES_SETUPTERM */ + +#ifndef HAVE_CURSES_TIGETSTR +/* Terminfo-to-termcap translation table. */ +typedef struct _tl { + char *terminfo; /* Terminfo name. */ + char *termcap; /* Termcap name. */ +} TL; +static const TL list[] = { + "cols", "co", /* Terminal columns. */ + "cup", "cm", /* Cursor up. */ + "cuu1", "up", /* Cursor up. */ + "el", "ce", /* Clear to end-of-line. */ + "flash", "vb", /* Visible bell. */ + "kcub1", "kl", /* Cursor left. */ + "kcud1", "kd", /* Cursor down. */ + "kcuf1", "kr", /* Cursor right. */ + "kcuu1", "ku", /* Cursor up. */ + "kdch1", "kD", /* Delete character. */ + "kdl1", "kL", /* Delete line. */ + "ked", "kS", /* Delete to end of screen. */ + "kel", "kE", /* Delete to eol. */ + "kend", "@7", /* Go to eol. */ + "khome", "kh", /* Go to sol. */ + "kich1", "kI", /* Insert at cursor. */ + "kil1", "kA", /* Insert line. */ + "kind", "kF", /* Scroll down. */ + "kll", "kH", /* Go to eol. */ + "knp", "kN", /* Page down. */ + "kpp", "kP", /* Page up. */ + "kri", "kR", /* Scroll up. */ + "lines", "li", /* Terminal lines. */ + "rmcup", "te", /* Terminal end string. */ + "rmkx", "ke", /* Exit "keypad-transmit" mode. */ + "rmso", "se", /* Standout end. */ + "smcup", "ti", /* Terminal initialization string. */ + "smkx", "ks", /* Enter "keypad-transmit" mode. */ + "smso", "so", /* Standout begin. */ +}; + +#ifdef _AIX +/* + * AIX's implementation for function keys greater than 10 is different and + * only goes as far as 36. + */ +static const char codes[] = { +/* 0-10 */ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';', +/* 11-20 */ '<', '>', '!', '@', '#', '$', '%', '^', '&', '*', +/* 21-30 */ '(', ')', '-', '_', '+', ',', ':', '?', '[', ']', +/* 31-36 */ '{', '}', '|', '~', '/', '=' +}; + +#else + +/* + * !!! + * Historically, the 4BSD termcap code didn't support functions keys greater + * than 9. This was silently enforced -- asking for key k12 would return the + * value for k1. We try and get around this by using the tables specified in + * the terminfo(TI_ENV) man page from the 3rd Edition SVID. This assumes the + * implementors of any System V compatibility code or an extended termcap used + * those codes. + */ +static const char codes[] = { +/* 0-10 */ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';', +/* 11-19 */ '1', '2', '3', '4', '5', '6', '7', '8', '9', +/* 20-63 */ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', +}; +#endif /* _AIX */ + +/* + * lcmp -- + * list comparison routine for bsearch. + */ +static int +lcmp(a, b) + const void *a, *b; +{ + return (strcmp(a, ((TL *)b)->terminfo)); +} + +/* + * tigetstr -- + * + * Vendors put the prototype for tigetstr into random include files, including + * <term.h>, which we can't include because it makes other systems unhappy. + * Try and work around the problem, since we only care about the return value. + * + * PUBLIC: #ifdef HAVE_CURSES_TIGETSTR + * PUBLIC: char *tigetstr(); + * PUBLIC: #else + * PUBLIC: char *tigetstr __P((char *)); + * PUBLIC: #endif + */ +char * +tigetstr(name) + char *name; +{ + static char sbuf[256]; + TL *tlp; + int n; + char *p, keyname[3]; + + if ((tlp = bsearch(name, + list, sizeof(list) / sizeof(TL), sizeof(TL), lcmp)) == NULL) { +#ifdef _AIX + if (name[0] == 'k' && + name[1] == 'f' && (n = atoi(name + 2)) <= 36) { + keyname[0] = 'k'; + keyname[1] = codes[n]; + keyname[2] = '\0'; +#else + if (name[0] == 'k' && + name[1] == 'f' && (n = atoi(name + 2)) <= 63) { + keyname[0] = n <= 10 ? 'k' : 'F'; + keyname[1] = codes[n]; + keyname[2] = '\0'; +#endif + name = keyname; + } + } else + name = tlp->termcap; + + p = sbuf; +#ifdef _AIX + return ((p = tgetstr(name, &p)) == NULL ? (char *)-1 : strcpy(sbuf, p)); +#else + return (tgetstr(name, &p) == NULL ? (char *)-1 : sbuf); +#endif +} + +/* + * tigetnum -- + * + * PUBLIC: #ifndef HAVE_CURSES_TIGETSTR + * PUBLIC: int tigetnum __P((char *)); + * PUBLIC: #endif + */ +int +tigetnum(name) + char *name; +{ + TL *tlp; + int val; + + if ((tlp = bsearch(name, + list, sizeof(list) / sizeof(TL), sizeof(TL), lcmp)) != NULL) { + name = tlp->termcap; + } + + return ((val = tgetnum(name)) == -1 ? -2 : val); +} +#endif /* !HAVE_CURSES_TIGETSTR */ diff --git a/cl/cl_funcs.c b/cl/cl_funcs.c new file mode 100644 index 0000000..40315ee --- /dev/null +++ b/cl/cl_funcs.c @@ -0,0 +1,704 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)cl_funcs.c 10.50 (Berkeley) 9/24/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <curses.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "../common/common.h" +#include "../vi/vi.h" +#include "cl.h" + +/* + * cl_addstr -- + * Add len bytes from the string at the cursor, advancing the cursor. + * + * PUBLIC: int cl_addstr __P((SCR *, const char *, size_t)); + */ +int +cl_addstr(sp, str, len) + SCR *sp; + const char *str; + size_t len; +{ + CL_PRIVATE *clp; + size_t oldy, oldx; + int iv; + + clp = CLP(sp); + + /* + * If ex isn't in control, it's the last line of the screen and + * it's a split screen, use inverse video. + */ + iv = 0; + getyx(stdscr, oldy, oldx); + if (!F_ISSET(sp, SC_SCR_EXWROTE) && + oldy == RLNO(sp, LASTLINE(sp)) && IS_SPLIT(sp)) { + iv = 1; + (void)standout(); + } + + if (addnstr(str, len) == ERR) + return (1); + + if (iv) + (void)standend(); + return (0); +} + +/* + * cl_attr -- + * Toggle a screen attribute on/off. + * + * PUBLIC: int cl_attr __P((SCR *, scr_attr_t, int)); + */ +int +cl_attr(sp, attribute, on) + SCR *sp; + scr_attr_t attribute; + int on; +{ + CL_PRIVATE *clp; + + clp = CLP(sp); + + switch (attribute) { + case SA_ALTERNATE: + /* + * !!! + * There's a major layering violation here. The problem is that the + * X11 xterm screen has what's known as an "alternate" screen. Some + * xterm termcap/terminfo entries include sequences to switch to/from + * that alternate screen as part of the ti/te (smcup/rmcup) strings. + * Vi runs in the alternate screen, so that you are returned to the + * same screen contents on exit from vi that you had when you entered + * vi. Further, when you run :shell, or :!date or similar ex commands, + * you also see the original screen contents. This wasn't deliberate + * on vi's part, it's just that it historically sent terminal init/end + * sequences at those times, and the addition of the alternate screen + * sequences to the strings changed the behavior of vi. The problem + * caused by this is that we don't want to switch back to the alternate + * screen while getting a new command from the user, when the user is + * continuing to enter ex commands, e.g.: + * + * :!date <<< switch to original screen + * [Hit return to continue] <<< prompt user to continue + * :command <<< get command from user + * + * Note that the :command input is a true vi input mode, e.g., input + * maps and abbreviations are being done. So, we need to be able to + * switch back into the vi screen mode, without flashing the screen. + * + * To make matters worse, the curses initscr() and endwin() calls will + * do this automatically -- so, this attribute isn't as controlled by + * the higher level screen as closely as one might like. + */ + if (on) { + if (clp->ti_te != TI_SENT) { + clp->ti_te = TI_SENT; + if (clp->smcup == NULL) + (void)cl_getcap(sp, "smcup", &clp->smcup); + if (clp->smcup != NULL) + (void)tputs(clp->smcup, 1, cl_putchar); + } + } else + if (clp->ti_te != TE_SENT) { + clp->ti_te = TE_SENT; + if (clp->rmcup == NULL) + (void)cl_getcap(sp, "rmcup", &clp->rmcup); + if (clp->rmcup != NULL) + (void)tputs(clp->rmcup, 1, cl_putchar); + (void)fflush(stdout); + } + (void)fflush(stdout); + break; + case SA_INVERSE: + if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) { + if (clp->smso == NULL) + return (1); + if (on) + (void)tputs(clp->smso, 1, cl_putchar); + else + (void)tputs(clp->rmso, 1, cl_putchar); + (void)fflush(stdout); + } else { + if (on) + (void)standout(); + else + (void)standend(); + } + break; + default: + abort(); + } + return (0); +} + +/* + * cl_baud -- + * Return the baud rate. + * + * PUBLIC: int cl_baud __P((SCR *, u_long *)); + */ +int +cl_baud(sp, ratep) + SCR *sp; + u_long *ratep; +{ + CL_PRIVATE *clp; + + /* + * XXX + * There's no portable way to get a "baud rate" -- cfgetospeed(3) + * returns the value associated with some #define, which we may + * never have heard of, or which may be a purely local speed. Vi + * only cares if it's SLOW (w300), slow (w1200) or fast (w9600). + * Try and detect the slow ones, and default to fast. + */ + clp = CLP(sp); + switch (cfgetospeed(&clp->orig)) { + case B50: + case B75: + case B110: + case B134: + case B150: + case B200: + case B300: + case B600: + *ratep = 600; + break; + case B1200: + *ratep = 1200; + break; + default: + *ratep = 9600; + break; + } + return (0); +} + +/* + * cl_bell -- + * Ring the bell/flash the screen. + * + * PUBLIC: int cl_bell __P((SCR *)); + */ +int +cl_bell(sp) + SCR *sp; +{ + if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) + (void)write(STDOUT_FILENO, "\07", 1); /* \a */ + else { + /* + * Vi has an edit option which determines if the terminal + * should be beeped or the screen flashed. + */ + if (O_ISSET(sp, O_FLASH)) + (void)flash(); + else + (void)beep(); + } + return (0); +} + +/* + * cl_clrtoeol -- + * Clear from the current cursor to the end of the line. + * + * PUBLIC: int cl_clrtoeol __P((SCR *)); + */ +int +cl_clrtoeol(sp) + SCR *sp; +{ + return (clrtoeol() == ERR); +} + +/* + * cl_cursor -- + * Return the current cursor position. + * + * PUBLIC: int cl_cursor __P((SCR *, size_t *, size_t *)); + */ +int +cl_cursor(sp, yp, xp) + SCR *sp; + size_t *yp, *xp; +{ + /* + * The curses screen support splits a single underlying curses screen + * into multiple screens to support split screen semantics. For this + * reason the returned value must be adjusted to be relative to the + * current screen, and not absolute. Screens that implement the split + * using physically distinct screens won't need this hack. + */ + getyx(stdscr, *yp, *xp); + *yp -= sp->woff; + return (0); +} + +/* + * cl_deleteln -- + * Delete the current line, scrolling all lines below it. + * + * PUBLIC: int cl_deleteln __P((SCR *)); + */ +int +cl_deleteln(sp) + SCR *sp; +{ + CHAR_T ch; + CL_PRIVATE *clp; + size_t col, lno, spcnt, oldy, oldx; + + clp = CLP(sp); + + /* + * This clause is required because the curses screen uses reverse + * video to delimit split screens. If the screen does not do this, + * this code won't be necessary. + * + * If the bottom line was in reverse video, rewrite it in normal + * video before it's scrolled. + * + * Check for the existence of a chgat function; XSI requires it, but + * historic implementations of System V curses don't. If it's not + * a #define, we'll fall back to doing it by hand, which is slow but + * acceptable. + * + * By hand means walking through the line, retrieving and rewriting + * each character. Curses has no EOL marker, so track strings of + * spaces, and copy the trailing spaces only if there's a non-space + * character following. + */ + if (!F_ISSET(sp, SC_SCR_EXWROTE) && IS_SPLIT(sp)) { + getyx(stdscr, oldy, oldx); +#ifdef mvchgat + mvchgat(RLNO(sp, LASTLINE(sp)), 0, -1, A_NORMAL, 0, NULL); +#else + for (lno = RLNO(sp, LASTLINE(sp)), col = spcnt = 0;;) { + (void)move(lno, col); + ch = winch(stdscr); + if (isblank(ch)) + ++spcnt; + else { + (void)move(lno, col - spcnt); + for (; spcnt > 0; --spcnt) + (void)addch(' '); + (void)addch(ch); + } + if (++col >= sp->cols) + break; + } +#endif + (void)move(oldy, oldx); + } + + /* + * The bottom line is expected to be blank after this operation, + * and other screens must support that semantic. + */ + return (deleteln() == ERR); +} + +/* + * cl_ex_adjust -- + * Adjust the screen for ex. This routine is purely for standalone + * ex programs. All special purpose, all special case. + * + * PUBLIC: int cl_ex_adjust __P((SCR *, exadj_t)); + */ +int +cl_ex_adjust(sp, action) + SCR *sp; + exadj_t action; +{ + CL_PRIVATE *clp; + int cnt; + + clp = CLP(sp); + switch (action) { + case EX_TERM_SCROLL: + /* Move the cursor up one line if that's possible. */ + if (clp->cuu1 != NULL) + (void)tputs(clp->cuu1, 1, cl_putchar); + else if (clp->cup != NULL) + (void)tputs(tgoto(clp->cup, + 0, LINES - 2), 1, cl_putchar); + else + return (0); + /* FALLTHROUGH */ + case EX_TERM_CE: + /* Clear the line. */ + if (clp->el != NULL) { + (void)putchar('\r'); + (void)tputs(clp->el, 1, cl_putchar); + } else { + /* + * Historically, ex didn't erase the line, so, if the + * displayed line was only a single glyph, and <eof> + * was more than one glyph, the output would not fully + * overwrite the user's input. To fix this, output + * the maxiumum character number of spaces. Note, + * this won't help if the user entered extra prompt + * or <blank> characters before the command character. + * We'd have to do a lot of work to make that work, and + * it's almost certainly not worth the effort. + */ + for (cnt = 0; cnt < MAX_CHARACTER_COLUMNS; ++cnt) + (void)putchar('\b'); + for (cnt = 0; cnt < MAX_CHARACTER_COLUMNS; ++cnt) + (void)putchar(' '); + (void)putchar('\r'); + (void)fflush(stdout); + } + break; + default: + abort(); + } + return (0); +} + +/* + * cl_insertln -- + * Push down the current line, discarding the bottom line. + * + * PUBLIC: int cl_insertln __P((SCR *)); + */ +int +cl_insertln(sp) + SCR *sp; +{ + /* + * The current line is expected to be blank after this operation, + * and the screen must support that semantic. + */ + return (insertln() == ERR); +} + +/* + * cl_keyval -- + * Return the value for a special key. + * + * PUBLIC: int cl_keyval __P((SCR *, scr_keyval_t, CHAR_T *, int *)); + */ +int +cl_keyval(sp, val, chp, dnep) + SCR *sp; + scr_keyval_t val; + CHAR_T *chp; + int *dnep; +{ + CL_PRIVATE *clp; + + /* + * VEOF, VERASE and VKILL are required by POSIX 1003.1-1990, + * VWERASE is a 4BSD extension. + */ + clp = CLP(sp); + switch (val) { + case KEY_VEOF: + *dnep = (*chp = clp->orig.c_cc[VEOF]) == _POSIX_VDISABLE; + break; + case KEY_VERASE: + *dnep = (*chp = clp->orig.c_cc[VERASE]) == _POSIX_VDISABLE; + break; + case KEY_VKILL: + *dnep = (*chp = clp->orig.c_cc[VKILL]) == _POSIX_VDISABLE; + break; +#ifdef VWERASE + case KEY_VWERASE: + *dnep = (*chp = clp->orig.c_cc[VWERASE]) == _POSIX_VDISABLE; + break; +#endif + default: + *dnep = 1; + break; + } + return (0); +} + +/* + * cl_move -- + * Move the cursor. + * + * PUBLIC: int cl_move __P((SCR *, size_t, size_t)); + */ +int +cl_move(sp, lno, cno) + SCR *sp; + size_t lno, cno; +{ + /* See the comment in cl_cursor. */ + if (move(RLNO(sp, lno), cno) == ERR) { + msgq(sp, M_ERR, + "Error: move: l(%u) c(%u) o(%u)", lno, cno, sp->woff); + return (1); + } + return (0); +} + +/* + * cl_refresh -- + * Refresh the screen. + * + * PUBLIC: int cl_refresh __P((SCR *, int)); + */ +int +cl_refresh(sp, repaint) + SCR *sp; + int repaint; +{ + CL_PRIVATE *clp; + + clp = CLP(sp); + + /* + * If we received a killer signal, we're done, there's no point + * in refreshing the screen. + */ + if (clp->killersig) + return (0); + + /* + * If repaint is set, the editor is telling us that we don't know + * what's on the screen, so we have to repaint from scratch. + * + * In the curses library, doing wrefresh(curscr) is okay, but the + * screen flashes when we then apply the refresh() to bring it up + * to date. So, use clearok(). + */ + if (repaint) + clearok(curscr, 1); + return (refresh() == ERR); +} + +/* + * cl_rename -- + * Rename the file. + * + * PUBLIC: int cl_rename __P((SCR *, char *, int)); + */ +int +cl_rename(sp, name, on) + SCR *sp; + char *name; + int on; +{ + GS *gp; + CL_PRIVATE *clp; + char *ttype; + + gp = sp->gp; + clp = CLP(sp); + + ttype = OG_STR(gp, GO_TERM); + + /* + * XXX + * We can only rename windows for xterm. + */ + if (on) { + if (F_ISSET(clp, CL_RENAME_OK) && + !strncmp(ttype, "xterm", sizeof("xterm") - 1)) { + F_SET(clp, CL_RENAME); + (void)printf(XTERM_RENAME, name); + (void)fflush(stdout); + } + } else + if (F_ISSET(clp, CL_RENAME)) { + F_CLR(clp, CL_RENAME); + (void)printf(XTERM_RENAME, ttype); + (void)fflush(stdout); + } + return (0); +} + +/* + * cl_suspend -- + * Suspend a screen. + * + * PUBLIC: int cl_suspend __P((SCR *, int *)); + */ +int +cl_suspend(sp, allowedp) + SCR *sp; + int *allowedp; +{ + struct termios t; + CL_PRIVATE *clp; + GS *gp; + size_t oldy, oldx; + int changed; + + gp = sp->gp; + clp = CLP(sp); + *allowedp = 1; + + /* + * The ex implementation of this function isn't needed by screens not + * supporting ex commands that require full terminal canonical mode + * (e.g. :suspend). + * + * The vi implementation of this function isn't needed by screens not + * supporting vi process suspension, i.e. any screen that isn't backed + * by a UNIX shell. + * + * Setting allowedp to 0 will cause the editor to reject the command. + */ + if (F_ISSET(sp, SC_EX)) { + /* Save the terminal settings, and restore the original ones. */ + if (F_ISSET(clp, CL_STDIN_TTY)) { + (void)tcgetattr(STDIN_FILENO, &t); + (void)tcsetattr(STDIN_FILENO, + TCSASOFT | TCSADRAIN, &clp->orig); + } + + /* Stop the process group. */ + (void)kill(0, SIGTSTP); + + /* Time passes ... */ + + /* Restore terminal settings. */ + if (F_ISSET(clp, CL_STDIN_TTY)) + (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &t); + return (0); + } + + /* + * Move to the lower left-hand corner of the screen. + * + * XXX + * Not sure this is necessary in System V implementations, but it + * shouldn't hurt. + */ + getyx(stdscr, oldy, oldx); + (void)move(LINES - 1, 0); + (void)refresh(); + + /* + * Temporarily end the screen. System V introduced a semantic where + * endwin() could be restarted. We use it because restarting curses + * from scratch often fails in System V. 4BSD curses didn't support + * restarting after endwin(), so we have to do what clean up we can + * without calling it. + */ +#ifdef HAVE_BSD_CURSES + /* Save the terminal settings. */ + (void)tcgetattr(STDIN_FILENO, &t); +#endif + + /* Restore the cursor keys to normal mode. */ + (void)keypad(stdscr, FALSE); + + /* Restore the window name. */ + (void)cl_rename(sp, NULL, 0); + +#ifdef HAVE_BSD_CURSES + (void)cl_attr(sp, SA_ALTERNATE, 0); +#else + (void)endwin(); +#endif + /* + * XXX + * Restore the original terminal settings. This is bad -- the + * reset can cause character loss from the tty queue. However, + * we can't call endwin() in BSD curses implementations, and too + * many System V curses implementations don't get it right. + */ + (void)tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->orig); + + /* Stop the process group. */ + (void)kill(0, SIGTSTP); + + /* Time passes ... */ + + /* + * If we received a killer signal, we're done. Leave everything + * unchanged. In addition, the terminal has already been reset + * correctly, so leave it alone. + */ + if (clp->killersig) { + F_CLR(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT); + return (0); + } + +#ifdef HAVE_BSD_CURSES + /* Restore terminal settings. */ + if (F_ISSET(clp, CL_STDIN_TTY)) + (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &t); + + (void)cl_attr(sp, SA_ALTERNATE, 1); +#endif + + /* Set the window name. */ + (void)cl_rename(sp, sp->frp->name, 1); + + /* Put the cursor keys into application mode. */ + (void)keypad(stdscr, TRUE); + + /* Refresh and repaint the screen. */ + (void)move(oldy, oldx); + (void)cl_refresh(sp, 1); + + /* If the screen changed size, set the SIGWINCH bit. */ + if (cl_ssize(sp, 1, NULL, NULL, &changed)) + return (1); + if (changed) + F_SET(CLP(sp), CL_SIGWINCH); + + return (0); +} + +/* + * cl_usage -- + * Print out the curses usage messages. + * + * PUBLIC: void cl_usage __P((void)); + */ +void +cl_usage() +{ +#define USAGE "\ +usage: ex [-eFRrSsv] [-c command] [-t tag] [-w size] [file ...]\n\ +usage: vi [-eFlRrSv] [-c command] [-t tag] [-w size] [file ...]\n" + (void)fprintf(stderr, "%s", USAGE); +#undef USAGE +} + +#ifdef DEBUG +/* + * gdbrefresh -- + * Stub routine so can flush out curses screen changes using gdb. + */ +int +gdbrefresh() +{ + refresh(); + return (0); /* XXX Convince gdb to run it. */ +} +#endif diff --git a/cl/cl_main.c b/cl/cl_main.c new file mode 100644 index 0000000..2889f70 --- /dev/null +++ b/cl/cl_main.c @@ -0,0 +1,471 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)cl_main.c 10.36 (Berkeley) 10/14/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <curses.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "../common/common.h" +#ifdef RUNNING_IP +#include "../ip/ip.h" +#endif +#include "cl.h" +#include "pathnames.h" + +GS *__global_list; /* GLOBAL: List of screens. */ +sigset_t __sigblockset; /* GLOBAL: Blocked signals. */ + +static void cl_func_std __P((GS *)); +static CL_PRIVATE *cl_init __P((GS *)); +static GS *gs_init __P((char *)); +static void perr __P((char *, char *)); +static int setsig __P((int, struct sigaction *, void (*)(int))); +static void sig_end __P((GS *)); +static void term_init __P((char *, char *)); + +/* + * main -- + * This is the main loop for the standalone curses editor. + */ +int +main(argc, argv) + int argc; + char *argv[]; +{ + static int reenter; + CL_PRIVATE *clp; + GS *gp; + size_t rows, cols; + int rval; + char *ip_arg, **p_av, **t_av, *ttype; + + /* If loaded at 0 and jumping through a NULL pointer, stop. */ + if (reenter++) + abort(); + + /* Create and initialize the global structure. */ + __global_list = gp = gs_init(argv[0]); + + /* + * Strip out any arguments that vi isn't going to understand. There's + * no way to portably call getopt twice, so arguments parsed here must + * be removed from the argument list. + */ +#ifdef RUNNING_IP + ip_arg = NULL; + for (p_av = t_av = argv;;) { + if (*t_av == NULL) { + *p_av = NULL; + break; + } + if (!strcmp(*t_av, "--")) { + while ((*p_av++ = *t_av++) != NULL); + break; + } + if (!memcmp(*t_av, "-I", sizeof("-I") - 1)) { + if (t_av[0][2] != '\0') { + ip_arg = t_av[0] + 2; + ++t_av; + --argc; + continue; + } + if (t_av[1] != NULL) { + ip_arg = t_av[1]; + t_av += 2; + argc -= 2; + continue; + } + } + *p_av++ = *t_av++; + } + + /* + * If we're being called as an editor library, we're done here, we + * get loaded with the curses screen, we don't share much code. + */ + if (ip_arg != NULL) + exit (ip_main(argc, argv, gp, ip_arg)); +#else + ip_arg = argv[0]; +#endif + + /* Create and initialize the CL_PRIVATE structure. */ + clp = cl_init(gp); + + /* + * Initialize the terminal information. + * + * We have to know what terminal it is from the start, since we may + * have to use termcap/terminfo to find out how big the screen is. + */ + if ((ttype = getenv("TERM")) == NULL) + ttype = "unknown"; + term_init(gp->progname, ttype); + + /* Add the terminal type to the global structure. */ + if ((OG_D_STR(gp, GO_TERM) = + OG_STR(gp, GO_TERM) = strdup(ttype)) == NULL) + perr(gp->progname, NULL); + + /* Figure out how big the screen is. */ + if (cl_ssize(NULL, 0, &rows, &cols, NULL)) + exit (1); + + /* Add the rows and columns to the global structure. */ + OG_VAL(gp, GO_LINES) = OG_D_VAL(gp, GO_LINES) = rows; + OG_VAL(gp, GO_COLUMNS) = OG_D_VAL(gp, GO_COLUMNS) = cols; + + /* Ex wants stdout to be buffered. */ + (void)setvbuf(stdout, NULL, _IOFBF, 0); + + /* Start catching signals. */ + if (sig_init(gp, NULL)) + exit (1); + + /* Run ex/vi. */ + rval = editor(gp, argc, argv); + + /* Clean up signals. */ + sig_end(gp); + + /* Clean up the terminal. */ + (void)cl_quit(gp); + + /* + * XXX + * Reset the O_MESG option. + */ + if (clp->tgw != TGW_UNKNOWN) + (void)cl_omesg(NULL, clp, clp->tgw == TGW_SET); + + /* + * XXX + * Reset the X11 xterm icon/window name. + */ + if (F_ISSET(clp, CL_RENAME)) { + (void)printf(XTERM_RENAME, ttype); + (void)fflush(stdout); + } + + /* If a killer signal arrived, pretend we just got it. */ + if (clp->killersig) { + (void)signal(clp->killersig, SIG_DFL); + (void)kill(getpid(), clp->killersig); + /* NOTREACHED */ + } + + /* Free the global and CL private areas. */ +#if defined(DEBUG) || defined(PURIFY) || defined(LIBRARY) + free(clp); + free(gp); +#endif + + exit (rval); +} + +/* + * gs_init -- + * Create and partially initialize the GS structure. + */ +static GS * +gs_init(name) + char *name; +{ + CL_PRIVATE *clp; + GS *gp; + char *p; + + /* Figure out what our name is. */ + if ((p = strrchr(name, '/')) != NULL) + name = p + 1; + + /* Allocate the global structure. */ + CALLOC_NOMSG(NULL, gp, GS *, 1, sizeof(GS)); + if (gp == NULL) + perr(name, NULL); + + + gp->progname = name; + return (gp); +} + +/* + * cl_init -- + * Create and partially initialize the CL structure. + */ +static CL_PRIVATE * +cl_init(gp) + GS *gp; +{ + CL_PRIVATE *clp; + int fd; + + /* Allocate the CL private structure. */ + CALLOC_NOMSG(NULL, clp, CL_PRIVATE *, 1, sizeof(CL_PRIVATE)); + if (clp == NULL) + perr(gp->progname, NULL); + gp->cl_private = clp; + + /* + * Set the CL_STDIN_TTY flag. It's purpose is to avoid setting + * and resetting the tty if the input isn't from there. We also + * use the same test to determine if we're running a script or + * not. + */ + if (isatty(STDIN_FILENO)) + F_SET(clp, CL_STDIN_TTY); + else + F_SET(gp, G_SCRIPTED); + + /* + * We expect that if we've lost our controlling terminal that the + * open() (but not the tcgetattr()) will fail. + */ + if (F_ISSET(clp, CL_STDIN_TTY)) { + if (tcgetattr(STDIN_FILENO, &clp->orig) == -1) + goto tcfail; + } else if ((fd = open(_PATH_TTY, O_RDONLY, 0)) != -1) { + if (tcgetattr(fd, &clp->orig) == -1) { +tcfail: perr(gp->progname, "tcgetattr"); + exit (1); + } + (void)close(fd); + } + + /* Initialize the list of curses functions. */ + cl_func_std(gp); + + return (clp); +} + +/* + * term_init -- + * Initialize terminal information. + */ +static void +term_init(name, ttype) + char *name, *ttype; +{ + int err; + + /* Set up the terminal database information. */ + setupterm(ttype, STDOUT_FILENO, &err); + switch (err) { + case -1: + (void)fprintf(stderr, + "%s: No terminal database found\n", name); + exit (1); + case 0: + (void)fprintf(stderr, + "%s: %s: unknown terminal type\n", name, ttype); + exit (1); + } +} + +#define GLOBAL_CLP \ + CL_PRIVATE *clp = GCLP(__global_list); +static void +h_hup(signo) + int signo; +{ + GLOBAL_CLP; + + F_SET(clp, CL_SIGHUP); + clp->killersig = SIGHUP; +} + +static void +h_int(signo) + int signo; +{ + GLOBAL_CLP; + + F_SET(clp, CL_SIGINT); +} + +static void +h_term(signo) + int signo; +{ + GLOBAL_CLP; + + F_SET(clp, CL_SIGTERM); + clp->killersig = SIGTERM; +} + +static void +h_winch(signo) + int signo; +{ + GLOBAL_CLP; + + F_SET(clp, CL_SIGWINCH); +} +#undef GLOBAL_CLP + +/* + * sig_init -- + * Initialize signals. + * + * PUBLIC: int sig_init __P((GS *, SCR *)); + */ +int +sig_init(gp, sp) + GS *gp; + SCR *sp; +{ + CL_PRIVATE *clp; + + clp = GCLP(gp); + + if (sp == NULL) { + (void)sigemptyset(&__sigblockset); + if (sigaddset(&__sigblockset, SIGHUP) || + setsig(SIGHUP, &clp->oact[INDX_HUP], h_hup) || + sigaddset(&__sigblockset, SIGINT) || + setsig(SIGINT, &clp->oact[INDX_INT], h_int) || + sigaddset(&__sigblockset, SIGTERM) || + setsig(SIGTERM, &clp->oact[INDX_TERM], h_term) +#ifdef SIGWINCH + || + sigaddset(&__sigblockset, SIGWINCH) || + setsig(SIGWINCH, &clp->oact[INDX_WINCH], h_winch) +#endif + ) { + perr(gp->progname, NULL); + return (1); + } + } else + if (setsig(SIGHUP, NULL, h_hup) || + setsig(SIGINT, NULL, h_int) || + setsig(SIGTERM, NULL, h_term) +#ifdef SIGWINCH + || + setsig(SIGWINCH, NULL, h_winch) +#endif + ) { + msgq(sp, M_SYSERR, "signal-reset"); + } + return (0); +} + +/* + * setsig -- + * Set a signal handler. + */ +static int +setsig(signo, oactp, handler) + int signo; + struct sigaction *oactp; + void (*handler) __P((int)); +{ + struct sigaction act; + + /* + * Use sigaction(2), not signal(3), since we don't always want to + * restart system calls. The example is when waiting for a command + * mode keystroke and SIGWINCH arrives. Besides, you can't portably + * restart system calls (thanks, POSIX!). On the other hand, you + * can't portably NOT restart system calls (thanks, Sun!). SunOS + * used SA_INTERRUPT as their extension to NOT restart read calls. + * We sure hope nobody else used it for anything else. Mom told me + * there'd be days like this. She just never told me that there'd + * be so many. + */ + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + +#ifdef SA_INTERRUPT + act.sa_flags = SA_INTERRUPT; +#else + act.sa_flags = 0; +#endif + return (sigaction(signo, &act, oactp)); +} + +/* + * sig_end -- + * End signal setup. + */ +static void +sig_end(gp) + GS *gp; +{ + CL_PRIVATE *clp; + + clp = GCLP(gp); + (void)sigaction(SIGHUP, NULL, &clp->oact[INDX_HUP]); + (void)sigaction(SIGINT, NULL, &clp->oact[INDX_INT]); + (void)sigaction(SIGTERM, NULL, &clp->oact[INDX_TERM]); +#ifdef SIGWINCH + (void)sigaction(SIGWINCH, NULL, &clp->oact[INDX_WINCH]); +#endif +} + +/* + * cl_func_std -- + * Initialize the standard curses functions. + */ +static void +cl_func_std(gp) + GS *gp; +{ + gp->scr_addstr = cl_addstr; + gp->scr_attr = cl_attr; + gp->scr_baud = cl_baud; + gp->scr_bell = cl_bell; + gp->scr_busy = NULL; + gp->scr_clrtoeol = cl_clrtoeol; + gp->scr_cursor = cl_cursor; + gp->scr_deleteln = cl_deleteln; + gp->scr_event = cl_event; + gp->scr_ex_adjust = cl_ex_adjust; + gp->scr_fmap = cl_fmap; + gp->scr_insertln = cl_insertln; + gp->scr_keyval = cl_keyval; + gp->scr_move = cl_move; + gp->scr_msg = NULL; + gp->scr_optchange = cl_optchange; + gp->scr_refresh = cl_refresh; + gp->scr_rename = cl_rename; + gp->scr_screen = cl_screen; + gp->scr_suspend = cl_suspend; + gp->scr_usage = cl_usage; +} + +/* + * perr -- + * Print system error. + */ +static void +perr(name, msg) + char *name, *msg; +{ + (void)fprintf(stderr, "%s:", name); + if (msg != NULL) + (void)fprintf(stderr, "%s:", msg); + (void)fprintf(stderr, "%s\n", strerror(errno)); + exit(1); +} diff --git a/cl/cl_read.c b/cl/cl_read.c new file mode 100644 index 0000000..8a95a77 --- /dev/null +++ b/cl/cl_read.c @@ -0,0 +1,334 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)cl_read.c 10.15 (Berkeley) 9/24/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#include <sys/time.h> + +#include <bitstring.h> +#include <curses.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "../common/common.h" +#include "../ex/script.h" +#include "cl.h" + +static input_t cl_read __P((SCR *, + u_int32_t, CHAR_T *, size_t, int *, struct timeval *)); +static int cl_resize __P((SCR *, size_t, size_t)); + +/* + * cl_event -- + * Return a single event. + * + * PUBLIC: int cl_event __P((SCR *, EVENT *, u_int32_t, int)); + */ +int +cl_event(sp, evp, flags, ms) + SCR *sp; + EVENT *evp; + u_int32_t flags; + int ms; +{ + struct timeval t, *tp; + CL_PRIVATE *clp; + size_t lines, columns; + int changed, nr; + + /* + * Queue signal based events. We never clear SIGHUP or SIGTERM events, + * so that we just keep returning them until the editor dies. + */ + clp = CLP(sp); +retest: if (LF_ISSET(EC_INTERRUPT) || F_ISSET(clp, CL_SIGINT)) { + if (F_ISSET(clp, CL_SIGINT)) { + F_CLR(clp, CL_SIGINT); + evp->e_event = E_INTERRUPT; + } else + evp->e_event = E_TIMEOUT; + return (0); + } + if (F_ISSET(clp, CL_SIGHUP | CL_SIGTERM | CL_SIGWINCH)) { + if (F_ISSET(clp, CL_SIGHUP)) { + evp->e_event = E_SIGHUP; + return (0); + } + if (F_ISSET(clp, CL_SIGTERM)) { + evp->e_event = E_SIGTERM; + return (0); + } + if (F_ISSET(clp, CL_SIGWINCH)) { + F_CLR(clp, CL_SIGWINCH); + if (cl_ssize(sp, 1, &lines, &columns, &changed)) + return (1); + if (changed) { + (void)cl_resize(sp, lines, columns); + evp->e_event = E_WRESIZE; + return (0); + } + /* No real change, ignore the signal. */ + } + } + + /* Set timer. */ + if (ms == 0) + tp = NULL; + else { + t.tv_sec = ms / 1000; + t.tv_usec = (ms % 1000) * 1000; + tp = &t; + } + + /* Read input characters. */ + switch (cl_read(sp, LF_ISSET(EC_QUOTED | EC_RAW), + clp->ibuf, sizeof(clp->ibuf), &nr, tp)) { + case INP_OK: + evp->e_csp = clp->ibuf; + evp->e_len = nr; + evp->e_event = E_STRING; + break; + case INP_EOF: + evp->e_event = E_EOF; + break; + case INP_ERR: + evp->e_event = E_ERR; + break; + case INP_INTR: + goto retest; + case INP_TIMEOUT: + evp->e_event = E_TIMEOUT; + break; + default: + abort(); + } + return (0); +} + +/* + * cl_read -- + * Read characters from the input. + */ +static input_t +cl_read(sp, flags, bp, blen, nrp, tp) + SCR *sp; + u_int32_t flags; + CHAR_T *bp; + size_t blen; + int *nrp; + struct timeval *tp; +{ + struct termios term1, term2; + struct timeval poll; + CL_PRIVATE *clp; + GS *gp; + SCR *tsp; + fd_set rdfd; + input_t rval; + int maxfd, nr, term_reset; + + gp = sp->gp; + clp = CLP(sp); + term_reset = 0; + + /* + * 1: A read from a file or a pipe. In this case, the reads + * never timeout regardless. This means that we can hang + * when trying to complete a map, but we're going to hang + * on the next read anyway. + */ + if (!F_ISSET(clp, CL_STDIN_TTY)) { + switch (nr = read(STDIN_FILENO, bp, blen)) { + case 0: + return (INP_EOF); + case -1: + goto err; + default: + *nrp = nr; + return (INP_OK); + } + /* NOTREACHED */ + } + + /* + * 2: A read with an associated timeout, e.g., trying to complete + * a map sequence. If input exists, we fall into #3. + */ + FD_ZERO(&rdfd); + poll.tv_sec = 0; + poll.tv_usec = 0; + if (tp != NULL) { + FD_SET(STDIN_FILENO, &rdfd); + switch (select(STDIN_FILENO + 1, + &rdfd, NULL, NULL, tp == NULL ? &poll : tp)) { + case 0: + return (INP_TIMEOUT); + case -1: + goto err; + default: + break; + } + } + + /* + * The user can enter a key in the editor to quote a character. If we + * get here and the next key is supposed to be quoted, do what we can. + * Reset the tty so that the user can enter a ^C, ^Q, ^S. There's an + * obvious race here, when the key has already been entered, but there's + * nothing that we can do to fix that problem. + * + * The editor can ask for the next literal character even thought it's + * generally running in line-at-a-time mode. Do what we can. + */ + if (LF_ISSET(EC_QUOTED | EC_RAW) && !tcgetattr(STDIN_FILENO, &term1)) { + term_reset = 1; + if (LF_ISSET(EC_QUOTED)) { + term2 = term1; + term2.c_lflag &= ~ISIG; + term2.c_iflag &= ~(IXON | IXOFF); + (void)tcsetattr(STDIN_FILENO, + TCSASOFT | TCSADRAIN, &term2); + } else + (void)tcsetattr(STDIN_FILENO, + TCSASOFT | TCSADRAIN, &clp->vi_enter); + } + + /* + * 3: Wait for input. + * + * Select on the command input and scripting window file descriptors. + * It's ugly that we wait on scripting file descriptors here, but it's + * the only way to keep from locking out scripting windows. + */ + if (F_ISSET(gp, G_SCRWIN)) { +loop: FD_ZERO(&rdfd); + FD_SET(STDIN_FILENO, &rdfd); + maxfd = STDIN_FILENO; + for (tsp = gp->dq.cqh_first; + tsp != (void *)&gp->dq; tsp = tsp->q.cqe_next) + if (F_ISSET(sp, SC_SCRIPT)) { + FD_SET(sp->script->sh_master, &rdfd); + if (sp->script->sh_master > maxfd) + maxfd = sp->script->sh_master; + } + switch (select(maxfd + 1, &rdfd, NULL, NULL, NULL)) { + case 0: + abort(); + case -1: + goto err; + default: + break; + } + if (!FD_ISSET(STDIN_FILENO, &rdfd)) { + if (sscr_input(sp)) + return (INP_ERR); + goto loop; + } + } + + /* + * 4: Read the input. + * + * !!! + * What's going on here is some scary stuff. Ex runs the terminal in + * canonical mode. So, the <newline> character terminating a line of + * input is returned in the buffer, but a trailing <EOF> character is + * not similarly included. As ex uses 0<EOF> and ^<EOF> as autoindent + * commands, it has to see the trailing <EOF> characters to determine + * the difference between the user entering "0ab" and "0<EOF>ab". We + * leave an extra slot in the buffer, so that we can add a trailing + * <EOF> character if the buffer isn't terminated by a <newline>. We + * lose if the buffer is too small for the line and exactly N characters + * are entered followed by an <EOF> character. + */ +#define ONE_FOR_EOF 1 + switch (nr = read(STDIN_FILENO, bp, blen - ONE_FOR_EOF)) { + case 0: /* EOF. */ + /* + * ^D in canonical mode returns a read of 0, i.e. EOF. EOF is + * a valid command, but we don't want to loop forever because + * the terminal driver is returning EOF because the user has + * disconnected. The editor will almost certainly try to write + * something before this fires, which should kill us, but You + * Never Know. + */ + if (++clp->eof_count < 50) { + bp[0] = clp->orig.c_cc[VEOF]; + *nrp = 1; + rval = INP_OK; + + } else + rval = INP_EOF; + break; + case -1: /* Error or interrupt. */ +err: if (errno == EINTR) + rval = INP_INTR; + else { + rval = INP_ERR; + msgq(sp, M_SYSERR, "input"); + } + break; + default: /* Input characters. */ + if (F_ISSET(sp, SC_EX) && bp[nr - 1] != '\n') + bp[nr++] = clp->orig.c_cc[VEOF]; + *nrp = nr; + clp->eof_count = 0; + rval = INP_OK; + break; + } + + /* Restore the terminal state if it was modified. */ + if (term_reset) + (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &term1); + return (rval); +} + +/* + * cl_resize -- + * Reset the options for a resize event. + */ +static int +cl_resize(sp, lines, columns) + SCR *sp; + size_t lines, columns; +{ + ARGS *argv[2], a, b; + char b1[1024]; + + a.bp = b1; + b.bp = NULL; + a.len = b.len = 0; + argv[0] = &a; + argv[1] = &b; + + (void)snprintf(b1, sizeof(b1), "lines=%lu", (u_long)lines); + a.len = strlen(b1); + if (opts_set(sp, argv, NULL)) + return (1); + (void)snprintf(b1, sizeof(b1), "columns=%lu", (u_long)columns); + a.len = strlen(b1); + if (opts_set(sp, argv, NULL)) + return (1); + return (0); +} diff --git a/cl/cl_screen.c b/cl/cl_screen.c new file mode 100644 index 0000000..2ce58e8 --- /dev/null +++ b/cl/cl_screen.c @@ -0,0 +1,581 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)cl_screen.c 10.49 (Berkeley) 9/24/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <curses.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "../common/common.h" +#include "cl.h" + +static int cl_ex_end __P((GS *)); +static int cl_ex_init __P((SCR *)); +static void cl_freecap __P((CL_PRIVATE *)); +static int cl_vi_end __P((GS *)); +static int cl_vi_init __P((SCR *)); +static int cl_putenv __P((char *, char *, u_long)); + +/* + * cl_screen -- + * Switch screen types. + * + * PUBLIC: int cl_screen __P((SCR *, u_int32_t)); + */ +int +cl_screen(sp, flags) + SCR *sp; + u_int32_t flags; +{ + CL_PRIVATE *clp; + GS *gp; + + gp = sp->gp; + clp = CLP(sp); + + /* See if the current information is incorrect. */ + if (F_ISSET(gp, G_SRESTART)) { + if (cl_quit(gp)) + return (1); + F_CLR(gp, G_SRESTART); + } + + /* See if we're already in the right mode. */ + if (LF_ISSET(SC_EX) && F_ISSET(sp, SC_SCR_EX) || + LF_ISSET(SC_VI) && F_ISSET(sp, SC_SCR_VI)) + return (0); + + /* + * Fake leaving ex mode. + * + * We don't actually exit ex or vi mode unless forced (e.g. by a window + * size change). This is because many curses implementations can't be + * called twice in a single program. Plus, it's faster. If the editor + * "leaves" vi to enter ex, when it exits ex we'll just fall back into + * vi. + */ + if (F_ISSET(sp, SC_SCR_EX)) + F_CLR(sp, SC_SCR_EX); + + /* + * Fake leaving vi mode. + * + * Clear out the rest of the screen if we're in the middle of a split + * screen. Move to the last line in the current screen -- this makes + * terminal scrolling happen naturally. Note: *don't* move past the + * end of the screen, as there are ex commands (e.g., :read ! cat file) + * that don't want to. Don't clear the info line, its contents may be + * valid, e.g. :file|append. + */ + if (F_ISSET(sp, SC_SCR_VI)) { + F_CLR(sp, SC_SCR_VI); + + if (sp->q.cqe_next != (void *)&gp->dq) { + (void)move(RLNO(sp, sp->rows), 0); + clrtobot(); + } + (void)move(RLNO(sp, sp->rows) - 1, 0); + refresh(); + } + + /* Enter the requested mode. */ + if (LF_ISSET(SC_EX)) { + if (cl_ex_init(sp)) + return (1); + F_SET(clp, CL_IN_EX | CL_SCR_EX_INIT); + + /* + * If doing an ex screen for ex mode, move to the last line + * on the screen. + */ + if (F_ISSET(sp, SC_EX) && clp->cup != NULL) + tputs(tgoto(clp->cup, + 0, O_VAL(sp, O_LINES) - 1), 1, cl_putchar); + } else { + if (cl_vi_init(sp)) + return (1); + F_CLR(clp, CL_IN_EX); + F_SET(clp, CL_SCR_VI_INIT); + } + return (0); +} + +/* + * cl_quit -- + * Shutdown the screens. + * + * PUBLIC: int cl_quit __P((GS *)); + */ +int +cl_quit(gp) + GS *gp; +{ + CL_PRIVATE *clp; + int rval; + + rval = 0; + clp = GCLP(gp); + + /* + * If we weren't really running, ignore it. This happens if the + * screen changes size before we've called curses. + */ + if (!F_ISSET(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT)) + return (0); + + /* Clean up the terminal mappings. */ + if (cl_term_end(gp)) + rval = 1; + + /* Really leave vi mode. */ + if (F_ISSET(clp, CL_STDIN_TTY) && + F_ISSET(clp, CL_SCR_VI_INIT) && cl_vi_end(gp)) + rval = 1; + + /* Really leave ex mode. */ + if (F_ISSET(clp, CL_STDIN_TTY) && + F_ISSET(clp, CL_SCR_EX_INIT) && cl_ex_end(gp)) + rval = 1; + + /* + * If we were running ex when we quit, or we're using an implementation + * of curses where endwin() doesn't get this right, restore the original + * terminal modes. + * + * XXX + * We always do this because it's too hard to figure out what curses + * implementations get it wrong. It may discard type-ahead characters + * from the tty queue. + */ + (void)tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->orig); + + F_CLR(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT); + return (rval); +} + +/* + * cl_vi_init -- + * Initialize the curses vi screen. + */ +static int +cl_vi_init(sp) + SCR *sp; +{ + CL_PRIVATE *clp; + GS *gp; + char *o_cols, *o_lines, *o_term, *ttype; + + gp = sp->gp; + clp = CLP(sp); + + /* If already initialized, just set the terminal modes. */ + if (F_ISSET(clp, CL_SCR_VI_INIT)) + goto fast; + + /* Curses vi always reads from (and writes to) a terminal. */ + if (!F_ISSET(clp, CL_STDIN_TTY) || !isatty(STDOUT_FILENO)) { + msgq(sp, M_ERR, + "016|Vi's standard input and output must be a terminal"); + return (1); + } + + /* We'll need a terminal type. */ + if (opts_empty(sp, O_TERM, 0)) + return (1); + ttype = O_STR(sp, O_TERM); + + /* + * XXX + * Changing the row/column and terminal values is done by putting them + * into the environment, which is then read by curses. What this loses + * in ugliness, it makes up for in stupidity. We can't simply put the + * values into the environment ourselves, because in the presence of a + * kernel mechanism for returning the window size, entering values into + * the environment will screw up future screen resizing events, e.g. if + * the user enters a :shell command and then resizes their window. So, + * if they weren't already in the environment, we make sure to delete + * them immediately after setting them. + * + * XXX + * Putting the TERM variable into the environment is necessary, even + * though we're using newterm() here. We may be using initscr() as + * the underlying function. + */ + o_term = getenv("TERM"); + cl_putenv("TERM", ttype, 0); + o_lines = getenv("LINES"); + cl_putenv("LINES", NULL, (u_long)O_VAL(sp, O_LINES)); + o_cols = getenv("COLUMNS"); + cl_putenv("COLUMNS", NULL, (u_long)O_VAL(sp, O_COLUMNS)); + + /* + * We don't care about the SCREEN reference returned by newterm, we + * never have more than one SCREEN at a time. + * + * XXX + * The SunOS initscr() can't be called twice. Don't even think about + * using it. It fails in subtle ways (e.g. select(2) on fileno(stdin) + * stops working). (The SVID notes that applications should only call + * initscr() once.) + * + * XXX + * The HP/UX newterm doesn't support the NULL first argument, so we + * have to specify the terminal type. + */ + errno = 0; + if (newterm(ttype, stdout, stdin) == NULL) { + if (errno) + msgq(sp, M_SYSERR, "%s", ttype); + else + msgq(sp, M_ERR, "%s: unknown terminal type", ttype); + return (1); + } + + if (o_term == NULL) + unsetenv("TERM"); + if (o_lines == NULL) + unsetenv("LINES"); + if (o_cols == NULL) + unsetenv("COLUMNS"); + + /* + * XXX + * Someone got let out alone without adult supervision -- the SunOS + * newterm resets the signal handlers. There's a race, but it's not + * worth closing. + */ + (void)sig_init(sp->gp, sp); + + /* + * We use raw mode. What we want is 8-bit clean, however, signals + * and flow control should continue to work. Admittedly, it sounds + * like cbreak, but it isn't. Using cbreak() can get you additional + * things like IEXTEN, which turns on flags like DISCARD and LNEXT. + * + * !!! + * If raw isn't turning off echo and newlines, something's wrong. + * However, it shouldn't hurt. + */ + noecho(); /* No character echo. */ + nonl(); /* No CR/NL translation. */ + raw(); /* 8-bit clean. */ + idlok(stdscr, 1); /* Use hardware insert/delete line. */ + + /* Put the cursor keys into application mode. */ + (void)keypad(stdscr, TRUE); + + /* + * XXX + * The screen TI sequence just got sent. See the comment in + * cl_funcs.c:cl_attr(). + */ + clp->ti_te = TI_SENT; + + /* + * XXX + * Historic implementations of curses handled SIGTSTP signals + * in one of three ways. They either: + * + * 1: Set their own handler, regardless. + * 2: Did not set a handler if a handler was already installed. + * 3: Set their own handler, but then called any previously set + * handler after completing their own cleanup. + * + * We don't try and figure out which behavior is in place, we force + * it to SIG_DFL after initializing the curses interface, which means + * that curses isn't going to take the signal. Since curses isn't + * reentrant (i.e., the whole curses SIGTSTP interface is a fantasy), + * we're doing The Right Thing. + */ + (void)signal(SIGTSTP, SIG_DFL); + + /* + * If flow control was on, turn it back on. Turn signals on. ISIG + * turns on VINTR, VQUIT, VDSUSP and VSUSP. The main curses code + * already installed a handler for VINTR. We're going to disable the + * other three. + * + * XXX + * We want to use ^Y as a vi scrolling command. If the user has the + * DSUSP character set to ^Y (common practice) clean it up. As it's + * equally possible that the user has VDSUSP set to 'a', we disable + * it regardless. It doesn't make much sense to suspend vi at read, + * so I don't think anyone will care. Alternatively, we could look + * it up in the table of legal command characters and turn it off if + * it matches one. VDSUSP wasn't in POSIX 1003.1-1990, so we test for + * it. + * + * XXX + * We don't check to see if the user had signals enabled originally. + * If they didn't, it's unclear what we're supposed to do here, but + * it's also pretty unlikely. + */ + if (tcgetattr(STDIN_FILENO, &clp->vi_enter)) { + msgq(sp, M_SYSERR, "tcgetattr"); + goto err; + } + if (clp->orig.c_iflag & IXON) + clp->vi_enter.c_iflag |= IXON; + if (clp->orig.c_iflag & IXOFF) + clp->vi_enter.c_iflag |= IXOFF; + + clp->vi_enter.c_lflag |= ISIG; +#ifdef VDSUSP + clp->vi_enter.c_cc[VDSUSP] = _POSIX_VDISABLE; +#endif + clp->vi_enter.c_cc[VQUIT] = _POSIX_VDISABLE; + clp->vi_enter.c_cc[VSUSP] = _POSIX_VDISABLE; + + /* + * XXX + * OSF/1 doesn't turn off the <discard>, <literal-next> or <status> + * characters when curses switches into raw mode. It should be OK + * to do it explicitly for everyone. + */ +#ifdef VDISCARD + clp->vi_enter.c_cc[VDISCARD] = _POSIX_VDISABLE; +#endif +#ifdef VLNEXT + clp->vi_enter.c_cc[VLNEXT] = _POSIX_VDISABLE; +#endif +#ifdef VSTATUS + clp->vi_enter.c_cc[VSTATUS] = _POSIX_VDISABLE; +#endif + + /* Initialize terminal based information. */ + if (cl_term_init(sp)) + goto err; + +fast: /* Set the terminal modes. */ + if (tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->vi_enter)) { + msgq(sp, M_SYSERR, "tcsetattr"); +err: (void)cl_vi_end(sp->gp); + return (1); + } + return (0); +} + +/* + * cl_vi_end -- + * Shutdown the vi screen. + */ +static int +cl_vi_end(gp) + GS *gp; +{ + CL_PRIVATE *clp; + + clp = GCLP(gp); + + /* Restore the cursor keys to normal mode. */ + (void)keypad(stdscr, FALSE); + + /* + * If we were running vi when we quit, scroll the screen up a single + * line so we don't lose any information. + * + * Move to the bottom of the window (some endwin implementations don't + * do this for you). + */ + if (!F_ISSET(clp, CL_IN_EX)) { + (void)move(0, 0); + (void)deleteln(); + (void)move(LINES - 1, 0); + (void)refresh(); + } + + cl_freecap(clp); + + /* End curses window. */ + (void)endwin(); + + /* + * XXX + * The screen TE sequence just got sent. See the comment in + * cl_funcs.c:cl_attr(). + */ + clp->ti_te = TE_SENT; + + return (0); +} + +/* + * cl_ex_init -- + * Initialize the ex screen. + */ +static int +cl_ex_init(sp) + SCR *sp; +{ + CL_PRIVATE *clp; + + clp = CLP(sp); + + /* If already initialized, just set the terminal modes. */ + if (F_ISSET(clp, CL_SCR_EX_INIT)) + goto fast; + + /* If not reading from a file, we're done. */ + if (!F_ISSET(clp, CL_STDIN_TTY)) + return (0); + + /* Get the ex termcap/terminfo strings. */ + (void)cl_getcap(sp, "cup", &clp->cup); + (void)cl_getcap(sp, "smso", &clp->smso); + (void)cl_getcap(sp, "rmso", &clp->rmso); + (void)cl_getcap(sp, "el", &clp->el); + (void)cl_getcap(sp, "cuu1", &clp->cuu1); + + /* Enter_standout_mode and exit_standout_mode are paired. */ + if (clp->smso == NULL || clp->rmso == NULL) { + if (clp->smso != NULL) { + free(clp->smso); + clp->smso = NULL; + } + if (clp->rmso != NULL) { + free(clp->rmso); + clp->rmso = NULL; + } + } + + /* + * Turn on canonical mode, with normal input and output processing. + * Start with the original terminal settings as the user probably + * had them (including any local extensions) set correctly for the + * current terminal. + * + * !!! + * We can't get everything that we need portably; for example, ONLCR, + * mapping <newline> to <carriage-return> on output isn't required + * by POSIX 1003.1b-1993. If this turns out to be a problem, then + * we'll either have to play some games on the mapping, or we'll have + * to make all ex printf's output \r\n instead of \n. + */ + clp->ex_enter = clp->orig; + clp->ex_enter.c_lflag |= ECHO | ECHOE | ECHOK | ICANON | IEXTEN | ISIG; +#ifdef ECHOCTL + clp->ex_enter.c_lflag |= ECHOCTL; +#endif +#ifdef ECHOKE + clp->ex_enter.c_lflag |= ECHOKE; +#endif + clp->ex_enter.c_iflag |= ICRNL; + clp->ex_enter.c_oflag |= OPOST; +#ifdef ONLCR + clp->ex_enter.c_oflag |= ONLCR; +#endif + +fast: if (tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->ex_enter)) { + msgq(sp, M_SYSERR, "tcsetattr"); + return (1); + } + return (0); +} + +/* + * cl_ex_end -- + * Shutdown the ex screen. + */ +static int +cl_ex_end(gp) + GS *gp; +{ + CL_PRIVATE *clp; + + clp = GCLP(gp); + + cl_freecap(clp); + + return (0); +} + +/* + * cl_getcap -- + * Retrieve termcap/terminfo strings. + * + * PUBLIC: int cl_getcap __P((SCR *, char *, char **)); + */ +int +cl_getcap(sp, name, elementp) + SCR *sp; + char *name, **elementp; +{ + size_t len; + char *t; + + if ((t = tigetstr(name)) != NULL && + t != (char *)-1 && (len = strlen(t)) != 0) { + MALLOC_RET(sp, *elementp, char *, len + 1); + memmove(*elementp, t, len + 1); + } + return (0); +} + +/* + * cl_freecap -- + * Free any allocated termcap/terminfo strings. + */ +static void +cl_freecap(clp) + CL_PRIVATE *clp; +{ + if (clp->el != NULL) { + free(clp->el); + clp->el = NULL; + } + if (clp->cup != NULL) { + free(clp->cup); + clp->cup = NULL; + } + if (clp->cuu1 != NULL) { + free(clp->cuu1); + clp->cuu1 = NULL; + } + if (clp->rmso != NULL) { + free(clp->rmso); + clp->rmso = NULL; + } + if (clp->smso != NULL) { + free(clp->smso); + clp->smso = NULL; + } +} + +/* + * cl_putenv -- + * Put a value into the environment. + */ +static int +cl_putenv(name, str, value) + char *name, *str; + u_long value; + +{ + char buf[40]; + + if (str == NULL) { + (void)snprintf(buf, sizeof(buf), "%lu", value); + return (setenv(name, buf, 1)); + } else + return (setenv(name, str, 1)); +} diff --git a/cl/cl_term.c b/cl/cl_term.c new file mode 100644 index 0000000..c36bb38 --- /dev/null +++ b/cl/cl_term.c @@ -0,0 +1,460 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)cl_term.c 10.22 (Berkeley) 9/15/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/queue.h> +#include <sys/stat.h> + +#include <bitstring.h> +#include <curses.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "../common/common.h" +#include "cl.h" + +static int cl_pfmap __P((SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t)); + +/* + * XXX + * THIS REQUIRES THAT ALL SCREENS SHARE A TERMINAL TYPE. + */ +typedef struct _tklist { + char *ts; /* Key's termcap string. */ + char *output; /* Corresponding vi command. */ + char *name; /* Name. */ + u_char value; /* Special value (for lookup). */ +} TKLIST; +static TKLIST const c_tklist[] = { /* Command mappings. */ + {"kil1", "O", "insert line"}, + {"kdch1", "x", "delete character"}, + {"kcud1", "j", "cursor down"}, + {"kel", "D", "delete to eol"}, + {"kind", "\004", "scroll down"}, /* ^D */ + {"kll", "$", "go to eol"}, + {"kend", "$", "go to eol"}, + {"khome", "^", "go to sol"}, + {"kich1", "i", "insert at cursor"}, + {"kdl1", "dd", "delete line"}, + {"kcub1", "h", "cursor left"}, + {"knp", "\006", "page down"}, /* ^F */ + {"kpp", "\002", "page up"}, /* ^B */ + {"kri", "\025", "scroll up"}, /* ^U */ + {"ked", "dG", "delete to end of screen"}, + {"kcuf1", "l", "cursor right"}, + {"kcuu1", "k", "cursor up"}, + {NULL}, +}; +static TKLIST const m1_tklist[] = { /* Input mappings (lookup). */ + {NULL}, +}; +static TKLIST const m2_tklist[] = { /* Input mappings (set or delete). */ + {"kcud1", "\033ja", "cursor down"}, /* ^[ja */ + {"kcub1", "\033ha", "cursor left"}, /* ^[ha */ + {"kcuu1", "\033ka", "cursor up"}, /* ^[ka */ + {"kcuf1", "\033la", "cursor right"}, /* ^[la */ + {NULL}, +}; + +/* + * cl_term_init -- + * Initialize the special keys defined by the termcap/terminfo entry. + * + * PUBLIC: int cl_term_init __P((SCR *)); + */ +int +cl_term_init(sp) + SCR *sp; +{ + KEYLIST *kp; + SEQ *qp; + TKLIST const *tkp; + char *t; + + /* Command mappings. */ + for (tkp = c_tklist; tkp->name != NULL; ++tkp) { + if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) + continue; + if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t), + tkp->output, strlen(tkp->output), SEQ_COMMAND, + SEQ_NOOVERWRITE | SEQ_SCREEN)) + return (1); + } + + /* Input mappings needing to be looked up. */ + for (tkp = m1_tklist; tkp->name != NULL; ++tkp) { + if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) + continue; + for (kp = keylist;; ++kp) + if (kp->value == tkp->value) + break; + if (kp == NULL) + continue; + if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t), + &kp->ch, 1, SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN)) + return (1); + } + + /* Input mappings that are already set or are text deletions. */ + for (tkp = m2_tklist; tkp->name != NULL; ++tkp) { + if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) + continue; + /* + * !!! + * Some terminals' <cursor_left> keys send single <backspace> + * characters. This is okay in command mapping, but not okay + * in input mapping. That combination is the only one we'll + * ever see, hopefully, so kluge it here for now. + */ + if (!strcmp(t, "\b")) + continue; + if (tkp->output == NULL) { + if (seq_set(sp, tkp->name, strlen(tkp->name), + t, strlen(t), NULL, 0, + SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN)) + return (1); + } else + if (seq_set(sp, tkp->name, strlen(tkp->name), + t, strlen(t), tkp->output, strlen(tkp->output), + SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN)) + return (1); + } + + /* + * Rework any function key mappings that were set before the + * screen was initialized. + */ + for (qp = sp->gp->seqq.lh_first; qp != NULL; qp = qp->q.le_next) + if (F_ISSET(qp, SEQ_FUNCMAP)) + (void)cl_pfmap(sp, qp->stype, + qp->input, qp->ilen, qp->output, qp->olen); + return (0); +} + +/* + * cl_term_end -- + * End the special keys defined by the termcap/terminfo entry. + * + * PUBLIC: int cl_term_end __P((GS *)); + */ +int +cl_term_end(gp) + GS *gp; +{ + SEQ *qp, *nqp; + + /* Delete screen specific mappings. */ + for (qp = gp->seqq.lh_first; qp != NULL; qp = nqp) { + nqp = qp->q.le_next; + if (F_ISSET(qp, SEQ_SCREEN)) + (void)seq_mdel(qp); + } + return (0); +} + +/* + * cl_fmap -- + * Map a function key. + * + * PUBLIC: int cl_fmap __P((SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t)); + */ +int +cl_fmap(sp, stype, from, flen, to, tlen) + SCR *sp; + seq_t stype; + CHAR_T *from, *to; + size_t flen, tlen; +{ + /* Ignore until the screen is running, do the real work then. */ + if (F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_SCR_VI)) + return (0); + if (F_ISSET(sp, SC_EX) && !F_ISSET(sp, SC_SCR_EX)) + return (0); + + return (cl_pfmap(sp, stype, from, flen, to, tlen)); +} + +/* + * cl_pfmap -- + * Map a function key (private version). + */ +static int +cl_pfmap(sp, stype, from, flen, to, tlen) + SCR *sp; + seq_t stype; + CHAR_T *from, *to; + size_t flen, tlen; +{ + size_t nlen; + char *p, keyname[64]; + + (void)snprintf(keyname, sizeof(keyname), "kf%d", atoi(from + 1)); + if ((p = tigetstr(keyname)) == NULL || + p == (char *)-1 || strlen(p) == 0) + p = NULL; + if (p == NULL) { + msgq_str(sp, M_ERR, from, "233|This terminal has no %s key"); + return (1); + } + + nlen = snprintf(keyname, + sizeof(keyname), "function key %d", atoi(from + 1)); + return (seq_set(sp, keyname, nlen, + p, strlen(p), to, tlen, stype, SEQ_NOOVERWRITE | SEQ_SCREEN)); +} + +/* + * cl_optchange -- + * Curses screen specific "option changed" routine. + * + * PUBLIC: int cl_optchange __P((SCR *, int, char *, u_long *)); + */ +int +cl_optchange(sp, opt, str, valp) + SCR *sp; + int opt; + char *str; + u_long *valp; +{ + CL_PRIVATE *clp; + + clp = CLP(sp); + + switch (opt) { + case O_COLUMNS: + case O_LINES: + case O_TERM: + /* + * Changing the columns, lines or terminal require that + * we restart the screen. + */ + F_SET(sp->gp, G_SRESTART); + F_CLR(sp, SC_SCR_EX | SC_SCR_VI); + break; + case O_MESG: + (void)cl_omesg(sp, clp, !*valp); + break; + case O_WINDOWNAME: + if (*valp) { + F_CLR(clp, CL_RENAME_OK); + + (void)cl_rename(sp, NULL, 0); + } else { + F_SET(clp, CL_RENAME_OK); + + /* + * If the screen is live, i.e. we're not reading the + * .exrc file, update the window. + */ + if (sp->frp != NULL && sp->frp->name != NULL) + (void)cl_rename(sp, sp->frp->name, 1); + } + break; + } + return (0); +} + +/* + * cl_omesg -- + * Turn the tty write permission on or off. + * + * PUBLIC: int cl_omesg __P((SCR *, CL_PRIVATE *, int)); + */ +int +cl_omesg(sp, clp, on) + SCR *sp; + CL_PRIVATE *clp; + int on; +{ + struct stat sb; + char *tty; + + /* Find the tty, get the current permissions. */ + if ((tty = ttyname(STDERR_FILENO)) == NULL) { + if (sp != NULL) + msgq(sp, M_SYSERR, "stderr"); + return (1); + } + if (stat(tty, &sb) < 0) { + if (sp != NULL) + msgq(sp, M_SYSERR, "%s", tty); + return (1); + } + + /* Save the original status if it's unknown. */ + if (clp->tgw == TGW_UNKNOWN) + clp->tgw = sb.st_mode & S_IWGRP ? TGW_SET : TGW_UNSET; + + /* Toggle the permissions. */ + if (on) { + if (chmod(tty, sb.st_mode | S_IWGRP) < 0) { + if (sp != NULL) + msgq(sp, M_SYSERR, + "046|messages not turned on: %s", tty); + return (1); + } + } else + if (chmod(tty, sb.st_mode & ~S_IWGRP) < 0) { + if (sp != NULL) + msgq(sp, M_SYSERR, + "045|messages not turned off: %s", tty); + return (1); + } + return (0); +} + +/* + * cl_ssize -- + * Return the terminal size. + * + * PUBLIC: int cl_ssize __P((SCR *, int, size_t *, size_t *, int *)); + */ +int +cl_ssize(sp, sigwinch, rowp, colp, changedp) + SCR *sp; + int sigwinch; + size_t *rowp, *colp; + int *changedp; +{ +#ifdef TIOCGWINSZ + struct winsize win; +#endif + size_t col, row; + int rval; + char *p; + + /* Assume it's changed. */ + if (changedp != NULL) + *changedp = 1; + + /* + * !!! + * sp may be NULL. + * + * Get the screen rows and columns. If the values are wrong, it's + * not a big deal -- as soon as the user sets them explicitly the + * environment will be set and the screen package will use the new + * values. + * + * Try TIOCGWINSZ. + */ + row = col = 0; +#ifdef TIOCGWINSZ + if (ioctl(STDERR_FILENO, TIOCGWINSZ, &win) != -1) { + row = win.ws_row; + col = win.ws_col; + } +#endif + /* If here because of suspend or a signal, only trust TIOCGWINSZ. */ + if (sigwinch) { + /* + * Somebody didn't get TIOCGWINSZ right, or has suspend + * without window resizing support. The user just lost, + * but there's nothing we can do. + */ + if (row == 0 || col == 0) { + if (changedp != NULL) + *changedp = 0; + return (0); + } + + /* + * SunOS systems deliver SIGWINCH when windows are uncovered + * as well as when they change size. In addition, we call + * here when continuing after being suspended since the window + * may have changed size. Since we don't want to background + * all of the screens just because the window was uncovered, + * ignore the signal if there's no change. + */ + if (sp != NULL && + row == O_VAL(sp, O_LINES) && col == O_VAL(sp, O_COLUMNS)) { + if (changedp != NULL) + *changedp = 0; + return (0); + } + + if (rowp != NULL) + *rowp = row; + if (colp != NULL) + *colp = col; + return (0); + } + + /* + * !!! + * If TIOCGWINSZ failed, or had entries of 0, try termcap. This + * routine is called before any termcap or terminal information + * has been set up. If there's no TERM environmental variable set, + * let it go, at least ex can run. + */ + if (row == 0 || col == 0) { + if ((p = getenv("TERM")) == NULL) + goto noterm; + if (row == 0) + if ((rval = tigetnum("lines")) < 0) + msgq(sp, M_SYSERR, "tigetnum: lines"); + else + row = rval; + if (col == 0) + if ((rval = tigetnum("cols")) < 0) + msgq(sp, M_SYSERR, "tigetnum: cols"); + else + col = rval; + } + + /* If nothing else, well, it's probably a VT100. */ +noterm: if (row == 0) + row = 24; + if (col == 0) + col = 80; + + /* + * !!! + * POSIX 1003.2 requires the environment to override everything. + * Often, people can get nvi to stop messing up their screen by + * deleting the LINES and COLUMNS environment variables from their + * dot-files. + */ + if ((p = getenv("LINES")) != NULL) + row = strtol(p, NULL, 10); + if ((p = getenv("COLUMNS")) != NULL) + col = strtol(p, NULL, 10); + + if (rowp != NULL) + *rowp = row; + if (colp != NULL) + *colp = col; + return (0); +} + +/* + * cl_putchar -- + * Function version of putchar, for tputs. + * + * PUBLIC: int cl_putchar __P((int)); + */ +int +cl_putchar(ch) + int ch; +{ + return (putchar(ch)); +} |