--- /dev/null
+/* $OpenBSD: tty.c,v 1.30 2008/09/15 16:11:35 kjell Exp $ */
+
+/* This file is in the public domain. */
+
+/*
+ * Terminfo display driver
+ *
+ * Terminfo is a terminal information database and routines to describe
+ * terminals on most modern UNIX systems. Many other systems have adopted
+ * this as a reasonable way to allow for widely varying and ever changing
+ * varieties of terminal types. This should be used where practical.
+ */
+/*
+ * Known problems: If you have a terminal with no clear to end of screen and
+ * memory of lines below the ones visible on the screen, display will be
+ * wrong in some cases. I doubt that any such terminal was ever made, but I
+ * thought everyone with delete line would have clear to end of screen too...
+ *
+ * Code for terminals without clear to end of screen and/or clear to end of line
+ * has not been extensively tested.
+ *
+ * Cost calculations are very rough. Costs of insert/delete line may be far
+ * from the truth. This is accentuated by display.c not knowing about
+ * multi-line insert/delete.
+ *
+ * Using scrolling region vs insert/delete line should probably be based on cost
+ * rather than the assumption that scrolling region operations look better.
+ */
+
+#include "def.h"
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+
+#include <term.h>
+
+static int charcost(char *);
+
+static int cci;
+static int insdel; /* Do we have both insert & delete line? */
+static char *scroll_fwd; /* How to scroll forward. */
+
+static void winchhandler(int);
+
+/* ARGSUSED */
+static void
+winchhandler(int sig)
+{
+ winch_flag = 1;
+}
+
+/*
+ * Initialize the terminal when the editor
+ * gets started up.
+ */
+void
+ttinit(void)
+{
+ int errret;
+
+ if (setupterm(NULL, 1, &errret))
+ panic("Terminal setup failed");
+
+ signal(SIGWINCH, winchhandler);
+ signal(SIGCONT, winchhandler);
+ siginterrupt(SIGWINCH, 1);
+
+ scroll_fwd = scroll_forward;
+ if (scroll_fwd == NULL || *scroll_fwd == '\0') {
+ /* this is what GNU Emacs does */
+ scroll_fwd = parm_down_cursor;
+ if (scroll_fwd == NULL || *scroll_fwd == '\0')
+ scroll_fwd = "\n";
+ }
+
+ if (cursor_address == NULL || cursor_up == NULL)
+ panic("This terminal is too stupid to run mg");
+
+ /* set nrow & ncol */
+ ttresize();
+
+ if (!clr_eol)
+ tceeol = ncol;
+ else
+ tceeol = charcost(clr_eol);
+
+ /* Estimate cost of inserting a line */
+ if (change_scroll_region && scroll_reverse)
+ tcinsl = charcost(change_scroll_region) * 2 +
+ charcost(scroll_reverse);
+ else if (parm_insert_line)
+ tcinsl = charcost(parm_insert_line);
+ else if (insert_line)
+ tcinsl = charcost(insert_line);
+ else
+ /* make this cost high enough */
+ tcinsl = nrow * ncol;
+
+ /* Estimate cost of deleting a line */
+ if (change_scroll_region)
+ tcdell = charcost(change_scroll_region) * 2 +
+ charcost(scroll_fwd);
+ else if (parm_delete_line)
+ tcdell = charcost(parm_delete_line);
+ else if (delete_line)
+ tcdell = charcost(delete_line);
+ else
+ /* make this cost high enough */
+ tcdell = nrow * ncol;
+
+ /* Flag to indicate that we can both insert and delete lines */
+ insdel = (insert_line || parm_insert_line) &&
+ (delete_line || parm_delete_line);
+
+ if (enter_ca_mode)
+ /* enter application mode */
+ putpad(enter_ca_mode, 1);
+
+ ttresize();
+}
+
+/*
+ * Re-initialize the terminal when the editor is resumed.
+ * The keypad_xmit doesn't really belong here but...
+ */
+void
+ttreinit(void)
+{
+ /* check if file was modified while we were gone */
+ if (fchecktime(curbp) != TRUE) {
+ curbp->b_flag |= BFDIRTY;
+ }
+
+ if (enter_ca_mode)
+ /* enter application mode */
+ putpad(enter_ca_mode, 1);
+
+ if (keypad_xmit)
+ /* turn on keypad */
+ putpad(keypad_xmit, 1);
+
+ ttresize();
+}
+
+/*
+ * Clean up the terminal, in anticipation of a return to the command
+ * interpreter. This is a no-op on the ANSI display. On the SCALD display,
+ * it sets the window back to half screen scrolling. Perhaps it should
+ * query the display for the increment, and put it back to what it was.
+ */
+void
+tttidy(void)
+{
+#ifdef XKEYS
+ ttykeymaptidy();
+#endif /* XKEYS */
+
+ /* set the term back to normal mode */
+ if (exit_ca_mode)
+ putpad(exit_ca_mode, 1);
+}
+
+/*
+ * Move the cursor to the specified origin 0 row and column position. Try to
+ * optimize out extra moves; redisplay may have left the cursor in the right
+ * location last time!
+ */
+void
+ttmove(int row, int col)
+{
+ if (ttrow != row || ttcol != col) {
+ putpad(tgoto(cursor_address, col, row), 1);
+ ttrow = row;
+ ttcol = col;
+ }
+}
+
+/*
+ * Erase to end of line.
+ */
+void
+tteeol(void)
+{
+ int i;
+
+ if (clr_eol)
+ putpad(clr_eol, 1);
+ else {
+ i = ncol - ttcol;
+ while (i--)
+ ttputc(' ');
+ ttrow = ttcol = HUGE;
+ }
+}
+
+/*
+ * Erase to end of page.
+ */
+void
+tteeop(void)
+{
+ int line;
+
+ if (clr_eos)
+ putpad(clr_eos, nrow - ttrow);
+ else {
+ putpad(clr_eol, 1);
+ if (insdel)
+ ttdell(ttrow + 1, lines, lines - ttrow - 1);
+ else {
+ /* do it by hand */
+ for (line = ttrow + 1; line <= lines; ++line) {
+ ttmove(line, 0);
+ tteeol();
+ }
+ }
+ ttrow = ttcol = HUGE;
+ }
+}
+
+/*
+ * Make a noise.
+ */
+void
+ttbeep(void)
+{
+ putpad(bell, 1);
+ ttflush();
+}
+
+/*
+ * Insert nchunk blank line(s) onto the screen, scrolling the last line on
+ * the screen off the bottom. Use the scrolling region if possible for a
+ * smoother display. If there is no scrolling region, use a set of insert
+ * and delete line sequences.
+ */
+void
+ttinsl(int row, int bot, int nchunk)
+{
+ int i, nl;
+
+ /* Case of one line insert is special. */
+ if (row == bot) {
+ ttmove(row, 0);
+ tteeol();
+ return;
+ }
+ if (change_scroll_region && scroll_reverse) {
+ /* Use scroll region and back index */
+ nl = bot - row;
+ ttwindow(row, bot);
+ ttmove(row, 0);
+ while (nchunk--)
+ putpad(scroll_reverse, nl);
+ ttnowindow();
+ return;
+ } else if (insdel) {
+ ttmove(1 + bot - nchunk, 0);
+ nl = nrow - ttrow;
+ if (parm_delete_line)
+ putpad(tgoto(parm_delete_line, 0, nchunk), nl);
+ else
+ /* For all lines in the chunk... */
+ for (i = 0; i < nchunk; i++)
+ putpad(delete_line, nl);
+ ttmove(row, 0);
+
+ /* ttmove() changes ttrow */
+ nl = nrow - ttrow;
+
+ if (parm_insert_line)
+ putpad(tgoto(parm_insert_line, 0, nchunk), nl);
+ else
+ /* For all lines in the chunk */
+ for (i = 0; i < nchunk; i++)
+ putpad(insert_line, nl);
+ ttrow = HUGE;
+ ttcol = HUGE;
+ } else
+ panic("ttinsl: Can't insert/delete line");
+}
+
+/*
+ * Delete nchunk line(s) from "row", replacing the bottom line on the
+ * screen with a blank line. Unless we're using the scrolling region,
+ * this is done with crafty sequences of insert and delete lines. The
+ * presence of the echo area makes a boundary condition go away.
+ */
+void
+ttdell(int row, int bot, int nchunk)
+{
+ int i, nl;
+
+ /* One line special cases */
+ if (row == bot) {
+ ttmove(row, 0);
+ tteeol();
+ return;
+ }
+ /* scrolling region */
+ if (change_scroll_region) {
+ nl = bot - row;
+ ttwindow(row, bot);
+ ttmove(bot, 0);
+ while (nchunk--)
+ putpad(scroll_fwd, nl);
+ ttnowindow();
+ /* else use insert/delete line */
+ } else if (insdel) {
+ ttmove(row, 0);
+ nl = nrow - ttrow;
+ if (parm_delete_line)
+ putpad(tgoto(parm_delete_line, 0, nchunk), nl);
+ else
+ /* For all lines in the chunk */
+ for (i = 0; i < nchunk; i++)
+ putpad(delete_line, nl);
+ ttmove(1 + bot - nchunk, 0);
+
+ /* ttmove() changes ttrow */
+ nl = nrow - ttrow;
+ if (parm_insert_line)
+ putpad(tgoto(parm_insert_line, 0, nchunk), nl);
+ else
+ /* For all lines in the chunk */
+ for (i = 0; i < nchunk; i++)
+ putpad(insert_line, nl);
+ ttrow = HUGE;
+ ttcol = HUGE;
+ } else
+ panic("ttdell: Can't insert/delete line");
+}
+
+/*
+ * This routine sets the scrolling window on the display to go from line
+ * "top" to line "bot" (origin 0, inclusive). The caller checks for the
+ * pathological 1-line scroll window which doesn't work right and avoids
+ * it. The "ttrow" and "ttcol" variables are set to a crazy value to
+ * ensure that the next call to "ttmove" does not turn into a no-op (the
+ * window adjustment moves the cursor).
+ */
+void
+ttwindow(int top, int bot)
+{
+ if (change_scroll_region && (tttop != top || ttbot != bot)) {
+ putpad(tgoto(change_scroll_region, bot, top), nrow - ttrow);
+ ttrow = HUGE; /* Unknown. */
+ ttcol = HUGE;
+ tttop = top; /* Remember region. */
+ ttbot = bot;
+ }
+}
+
+/*
+ * Switch to full screen scroll. This is used by "spawn.c" just before it
+ * suspends the editor and by "display.c" when it is getting ready to
+ * exit. This function does a full screen scroll by telling the terminal
+ * to set a scrolling region that is lines or nrow rows high, whichever is
+ * larger. This behavior seems to work right on systems where you can set
+ * your terminal size.
+ */
+void
+ttnowindow(void)
+{
+ if (change_scroll_region) {
+ putpad(tgoto(change_scroll_region,
+ (nrow > lines ? nrow : lines) - 1, 0), nrow - ttrow);
+ ttrow = HUGE; /* Unknown. */
+ ttcol = HUGE;
+ tttop = HUGE; /* No scroll region. */
+ ttbot = HUGE;
+ }
+}
+
+/*
+ * Set the current writing color to the specified color. Watch for color
+ * changes that are not going to do anything (the color is already right)
+ * and don't send anything to the display. The rainbow version does this
+ * in putline.s on a line by line basis, so don't bother sending out the
+ * color shift.
+ */
+void
+ttcolor(int color)
+{
+ if (color != tthue) {
+ if (color == CTEXT)
+ /* normal video */
+ putpad(exit_standout_mode, 1);
+ else if (color == CMODE)
+ /* reverse video */
+ putpad(enter_standout_mode, 1);
+ /* save the color */
+ tthue = color;
+ }
+}
+
+/*
+ * This routine is called by the "refresh the screen" command to try
+ * to resize the display. Look in "window.c" to see how
+ * the caller deals with a change.
+ *
+ * We use `newrow' and `newcol' so vtresize() know the difference between the
+ * new and old settings.
+ */
+void
+ttresize(void)
+{
+ int newrow = 0, newcol = 0;
+
+#ifdef TIOCGWINSZ
+ struct winsize winsize;
+
+ if (ioctl(0, TIOCGWINSZ, &winsize) == 0) {
+ newrow = winsize.ws_row;
+ newcol = winsize.ws_col;
+ }
+#endif
+ if ((newrow <= 0 || newcol <= 0) &&
+ ((newrow = lines) <= 0 || (newcol = columns) <= 0)) {
+ newrow = 24;
+ newcol = 80;
+ }
+ if (vtresize(1, newrow, newcol) != TRUE)
+ panic("vtresize failed");
+}
+
+/*
+ * fake char output for charcost()
+ */
+/* ARGSUSED */
+static int
+fakec(int c)
+{
+ cci++;
+ return (0);
+}
+
+/* calculate the cost of doing string s */
+static int
+charcost(char *s)
+{
+ cci = 0;
+
+ tputs(s, nrow, fakec);
+ return (cci);
+}