summaryrefslogtreecommitdiff
path: root/usr.bin/vi/cl
diff options
context:
space:
mode:
authorTheo de Raadt <deraadt@cvs.openbsd.org>1996-05-22 11:37:15 +0000
committerTheo de Raadt <deraadt@cvs.openbsd.org>1996-05-22 11:37:15 +0000
commit0157a77a51c5e35e093ae03581f66dea010edcc8 (patch)
tree5e8bd32aa4d2b5ed37b7cf3ad26e8bdfc7f20a04 /usr.bin/vi/cl
parent806021be093ad00ce2022a532c0f4cc99b0065ac (diff)
new vi
Diffstat (limited to 'usr.bin/vi/cl')
-rw-r--r--usr.bin/vi/cl/README.signal174
-rw-r--r--usr.bin/vi/cl/cl.h81
-rw-r--r--usr.bin/vi/cl/cl_bsd.c309
-rw-r--r--usr.bin/vi/cl/cl_funcs.c608
-rw-r--r--usr.bin/vi/cl/cl_main.c386
-rw-r--r--usr.bin/vi/cl/cl_read.c334
-rw-r--r--usr.bin/vi/cl/cl_screen.c597
-rw-r--r--usr.bin/vi/cl/cl_term.c436
8 files changed, 2925 insertions, 0 deletions
diff --git a/usr.bin/vi/cl/README.signal b/usr.bin/vi/cl/README.signal
new file mode 100644
index 00000000000..7faa45673b7
--- /dev/null
+++ b/usr.bin/vi/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.)
diff --git a/usr.bin/vi/cl/cl.h b/usr.bin/vi/cl/cl.h
new file mode 100644
index 00000000000..dc0c74b3cf5
--- /dev/null
+++ b/usr.bin/vi/cl/cl.h
@@ -0,0 +1,81 @@
+/*-
+ * 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.15 (Berkeley) 5/16/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 in_ex; /* XXX: Currently running ex. */
+
+ 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_SCR_EX_INIT 0x001 /* Ex screen initialized. */
+#define CL_SCR_VI_INIT 0x002 /* Vi screen initialized. */
+#define CL_SIGHUP 0x004 /* SIGHUP arrived. */
+#define CL_SIGINT 0x008 /* SIGINT arrived. */
+#define CL_SIGTERM 0x010 /* SIGTERM arrived. */
+#define CL_SIGWINCH 0x020 /* SIGWINCH arrived. */
+ 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)
+
+/* Some functions can be safely ignored until the screen is running. */
+#define VI_INIT_IGNORE(sp) \
+ if (F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_SCR_VI)) \
+ return (0);
+#define EX_INIT_IGNORE(sp) \
+ if (F_ISSET(sp, SC_EX) && !F_ISSET(sp, SC_SCR_EX)) \
+ return (0);
+
+/*
+ * 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/usr.bin/vi/cl/cl_bsd.c b/usr.bin/vi/cl/cl_bsd.c
new file mode 100644
index 00000000000..cd0ef9c87e9
--- /dev/null
+++ b/usr.bin/vi/cl/cl_bsd.c
@@ -0,0 +1,309 @@
+/*-
+ * 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.25 (Berkeley) 4/30/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. */
+ "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. */
+};
+
+/*
+ * !!!
+ * 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',
+};
+
+/*
+ * lcmp --
+ * list comparison routine for bsearch.
+ */
+static int
+lcmp(a, b)
+ const void *a, *b;
+{
+ return (strcmp(a, ((TL *)b)->terminfo));
+}
+
+/*
+ * tigetstr --
+ *
+ * PUBLIC: #ifndef HAVE_CURSES_TIGETSTR
+ * 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) {
+ 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';
+ name = keyname;
+ }
+ } else
+ name = tlp->termcap;
+
+ p = sbuf;
+ return (tgetstr(name, &p) == NULL ? (char *)-1 : sbuf);
+}
+
+/*
+ * 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/usr.bin/vi/cl/cl_funcs.c b/usr.bin/vi/cl/cl_funcs.c
new file mode 100644
index 00000000000..774f14fa236
--- /dev/null
+++ b/usr.bin/vi/cl/cl_funcs.c
@@ -0,0 +1,608 @@
+/*-
+ * 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.40 (Berkeley) 5/16/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;
+
+ switch (attribute) {
+ case SA_INVERSE:
+ if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) {
+ clp = CLP(sp);
+ 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;
+{
+ /*
+ * 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 *));
+ */
+int
+cl_rename(sp)
+ SCR *sp;
+{
+ return (0); /* Curses doesn't care. */
+}
+
+/*
+ * 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(gp, G_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(gp, G_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);
+
+#ifdef HAVE_BSD_CURSES
+ /* Send the terminal end sequence. */
+ 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);
+#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 ... */
+
+#ifdef HAVE_BSD_CURSES
+ /* Restore terminal settings. */
+ if (F_ISSET(gp, G_STDIN_TTY))
+ (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &t);
+
+ /* Send the terminal initialization sequence. */
+ if (clp->smcup == NULL)
+ (void)cl_getcap(sp, "smcup", &clp->smcup);
+ if (clp->smcup != NULL)
+ (void)tputs(clp->smcup, 1, cl_putchar);
+ (void)fflush(stdout);
+#endif
+ /* 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 [-eFRrsv] [-c command] [-t tag] [-w size] [file ...]\n\
+usage: vi [-eFlRrv] [-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/usr.bin/vi/cl/cl_main.c b/usr.bin/vi/cl/cl_main.c
new file mode 100644
index 00000000000..1a436924aaf
--- /dev/null
+++ b/usr.bin/vi/cl/cl_main.c
@@ -0,0 +1,386 @@
+/*-
+ * 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.27 (Berkeley) 4/28/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"
+#include "cl.h"
+#include "pathnames.h"
+
+GS *__global_list; /* GLOBAL: List of screens. */
+sigset_t __sigblockset; /* GLOBAL: Blocked signals. */
+
+static GS *gs_init __P((char *));
+static void nomem __P((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 **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]);
+ clp = GCLP(gp);
+
+#ifdef NOT_NEEDED_FOR_CURSES
+ /*
+ * 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.
+ */
+ for (p_av = t_av = argv;;) {
+ if (*t_av == NULL) {
+ *p_av = NULL;
+ break;
+ }
+ *p_av++ = *t_av++;
+ }
+#endif
+
+ /*
+ * 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)
+ nomem(gp->progname);
+
+ /* 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);
+
+ /* 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;
+ int fd;
+ 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));
+
+ /* Allocate the CL private structure. */
+ if (gp != NULL)
+ CALLOC_NOMSG(NULL, clp, CL_PRIVATE *, 1, sizeof(CL_PRIVATE));
+ if (gp == NULL || clp == NULL)
+ nomem(name);
+ gp->cl_private = clp;
+
+ /* Initialize the list of curses functions. */
+ 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;
+
+ /*
+ * Set the G_STDIN_TTY flag. It's purpose is to avoid setting and
+ * resetting the tty if the input isn't from there.
+ */
+ if (isatty(STDIN_FILENO))
+ F_SET(gp, G_STDIN_TTY);
+
+ /*
+ * We expect that if we've lost our controlling terminal that the
+ * open() (but not the tcgetattr()) will fail.
+ */
+ if (F_ISSET(gp, G_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: (void)fprintf(stderr,
+ "%s: tcgetattr: %s\n", name, strerror(errno));
+ exit (1);
+ }
+ (void)close(fd);
+ }
+
+ gp->progname = name;
+ return (gp);
+}
+
+/*
+ * 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) ||
+ sigaddset(&__sigblockset, SIGWINCH) ||
+ setsig(SIGWINCH, &clp->oact[INDX_WINCH], h_winch)) {
+ (void)fprintf(stderr,
+ "%s: %s\n", gp->progname, strerror(errno));
+ return (1);
+ }
+ } else
+ if (setsig(SIGHUP, NULL, h_hup) ||
+ setsig(SIGINT, NULL, h_int) ||
+ setsig(SIGTERM, NULL, h_term) ||
+ setsig(SIGWINCH, NULL, h_winch)) {
+ 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]);
+ (void)sigaction(SIGWINCH, NULL, &clp->oact[INDX_WINCH]);
+}
+
+/*
+ * nomem --
+ * No memory error.
+ */
+static void
+nomem(name)
+ char *name;
+{
+ (void)fprintf(stderr, "%s: %s\n", name, strerror(errno));
+ exit(1);
+}
diff --git a/usr.bin/vi/cl/cl_read.c b/usr.bin/vi/cl/cl_read.c
new file mode 100644
index 00000000000..bfbaca7ff0e
--- /dev/null
+++ b/usr.bin/vi/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.13 (Berkeley) 5/3/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_INTERRUPT;
+ 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;
+
+ 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.
+ */
+ gp = sp->gp;
+ if (!F_ISSET(gp, G_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_SCRIPT)) {
+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/usr.bin/vi/cl/cl_screen.c b/usr.bin/vi/cl/cl_screen.c
new file mode 100644
index 00000000000..bd66ad3e65b
--- /dev/null
+++ b/usr.bin/vi/cl/cl_screen.c
@@ -0,0 +1,597 @@
+/*-
+ * 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.44 (Berkeley) 5/16/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);
+ clp->in_ex = 1;
+ F_SET(clp, 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);
+ clp->in_ex = 0;
+ 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(gp, G_STDIN_TTY) &&
+ F_ISSET(clp, CL_SCR_VI_INIT) && cl_vi_end(gp))
+ rval = 1;
+
+ /* Really leave ex mode. */
+ if (F_ISSET(gp, G_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(gp, G_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() isn't reentrant. Don't even think about using
+ * it. It fails in subtle ways (e.g. select(2) on fileno(stdin) stops
+ * working).
+ *
+ * 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);
+
+ /* The screen TI sequence just got sent. */
+ 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);
+ }
+
+ /* If not already done, send the terminal initialization sequence. */
+ if (clp->ti_te == TE_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);
+ (void)fflush(stdout);
+ }
+ 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 (!clp->in_ex) {
+ (void)move(0, 0);
+ (void)deleteln();
+ (void)move(LINES - 1, 0);
+ (void)refresh();
+ }
+
+ cl_freecap(clp);
+
+ /* End curses window. */
+ (void)endwin();
+
+ /* The screen TE sequence just got sent. */
+ 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(sp->gp, G_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);
+ }
+
+ /* If not already done, send the terminal end sequence. */
+ if (clp->ti_te == TI_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);
+ }
+ 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. We use putenv(3) because it's
+ * more portable. The following hack is because some moron decided
+ * to keep a reference to the memory passed to putenv(3), instead of
+ * having it allocate its own. Someone clearly needs to get promoted
+ * into management.
+ */
+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/usr.bin/vi/cl/cl_term.c b/usr.bin/vi/cl/cl_term.c
new file mode 100644
index 00000000000..8f9088aebfb
--- /dev/null
+++ b/usr.bin/vi/cl/cl_term.c
@@ -0,0 +1,436 @@
+/*-
+ * 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.20 (Berkeley) 5/3/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"},
+ {"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;
+{
+ EX_INIT_IGNORE(sp);
+ VI_INIT_IGNORE(sp);
+
+ 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;
+{
+ 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:
+ cl_omesg(sp, CLP(sp), !*valp);
+ 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));
+ */
+void
+cl_putchar(ch)
+ int ch;
+{
+ putchar(ch);
+}