freebsd-dev/usr.bin/more/screen.c
Tim Vanderhoek 20d6e5fe6e Thorough revamp of how input commands are processed. This allows customization
of user keys (documentation pending).  The only key whose semantics have
changed is the capital 'N' key, which now performs a repeat-search in the
opposite direction (just like in vi).

This commit is a little bulkier than what I had originally planned.  I'm not
completely happy with the direction it went, but it's definately an
improvement, and the alternative is to continue becoming irrelevant compared
to GNU less.  (Does anyone even _use_ /usr/bin/more these days?)
1999-09-03 22:31:21 +00:00

614 lines
13 KiB
C

/*
* Copyright (c) 1988 Mark Nudleman
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static char sccsid[] = "@(#)screen.c 8.2 (Berkeley) 4/20/94";
#endif /* not lint */
/*
* Routines which deal with the characteristics of the terminal.
* Uses termcap to be as terminal-independent as possible.
*
* {{ Someday this should be rewritten to use curses. }}
*/
#include <stdio.h>
#include <string.h>
#include <less.h>
#define TERMIOS 1
#if TERMIO
#include <termio.h>
#else
#if TERMIOS
#include <termios.h>
#define TAB3 0
#include <sys/ioctl.h>
#else
#include <sgtty.h>
#endif
#endif
#ifdef TIOCGWINSZ
#include <sys/ioctl.h>
#else
/*
* For the Unix PC (ATT 7300 & 3B1):
* Since WIOCGETD is defined in sys/window.h, we can't use that to decide
* whether to include sys/window.h. Use SIGPHONE from sys/signal.h instead.
*/
#include <sys/signal.h>
#ifdef SIGPHONE
#include <sys/window.h>
#endif
#endif
/*
* Strings passed to tputs() to do various terminal functions.
*/
static char
*sc_pad, /* Pad string */
*sc_home, /* Cursor home */
*sc_addline, /* Add line, scroll down following lines */
*sc_lower_left, /* Cursor to last line, first column */
*sc_move, /* General cursor positioning */
*sc_clear, /* Clear screen */
*sc_eol_clear, /* Clear to end of line */
*sc_s_in, /* Enter standout (highlighted) mode */
*sc_s_out, /* Exit standout mode */
*sc_u_in, /* Enter underline mode */
*sc_u_out, /* Exit underline mode */
*sc_b_in, /* Enter bold mode */
*sc_b_out, /* Exit bold mode */
*sc_backspace, /* Backspace cursor */
*sc_init, /* Startup terminal initialization */
*sc_deinit; /* Exit terminal de-intialization */
int auto_wrap; /* Terminal does \r\n when write past margin */
int ignaw; /* Terminal ignores \n immediately after wrap */
/* The user's erase and line-kill chars */
int retain_below; /* Terminal retains text below the screen */
int erase_char, kill_char, werase_char;
int sc_width, sc_height = -1; /* Height & width of screen */
int bo_width, be_width; /* Printing width of boldface sequences */
int ul_width, ue_width; /* Printing width of underline sequences */
int so_width, se_width; /* Printing width of standout sequences */
int mode_flags = 0;
#define M_SO 1
#define M_UL 2
#define M_BO 4
/*
* These two variables are sometimes defined in,
* and needed by, the termcap library.
* It may be necessary on some systems to declare them extern here.
*/
/*extern*/ short ospeed; /* Terminal output baud rate */
/*extern*/ char PC; /* Pad character */
extern int back_scroll;
char *tgetstr();
char *tgoto();
/*
* Change terminal to "raw mode", or restore to "normal" mode.
* "Raw mode" means
* 1. An outstanding read will complete on receipt of a single keystroke.
* 2. Input is not echoed.
* 3. On output, \n is mapped to \r\n.
* 4. \t is NOT expanded into spaces.
* 5. Signal-causing characters such as ctrl-C (interrupt),
* etc. are NOT disabled.
* It doesn't matter whether an input \n is mapped to \r, or vice versa.
*/
raw_mode(on)
int on;
{
#if TERMIO || TERMIOS
#if TERMIO
struct termio s;
static struct termio save_term;
#else
struct termios s;
static struct termios save_term;
#endif
if (on)
{
/*
* Get terminal modes.
*/
#if TERMIO
(void)ioctl(2, TCGETA, &s);
#else
tcgetattr(2, &s);
#endif
/*
* Save modes and set certain variables dependent on modes.
*/
save_term = s;
#if TERMIO
ospeed = s.c_cflag & CBAUD;
#else
/* more work needed here */
#endif
erase_char = s.c_cc[VERASE];
kill_char = s.c_cc[VKILL];
werase_char = s.c_cc[VWERASE];
/*
* Set the modes to the way we want them.
*/
s.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
s.c_oflag |= (OPOST|ONLCR|TAB3);
#if TERMIO
s.c_oflag &= ~(OCRNL|ONOCR|ONLRET);
#endif
s.c_cc[VMIN] = 1;
s.c_cc[VTIME] = 0;
} else
{
/*
* Restore saved modes.
*/
s = save_term;
}
#if TERMIO
(void)ioctl(2, TCSETAW, &s);
#else
tcsetattr(2, TCSADRAIN, &s);
#endif
#else
struct sgttyb s;
struct ltchars l;
static struct sgttyb save_term;
if (on)
{
/*
* Get terminal modes.
*/
(void)ioctl(2, TIOCGETP, &s);
(void)ioctl(2, TIOCGLTC, &l);
/*
* Save modes and set certain variables dependent on modes.
*/
save_term = s;
ospeed = s.sg_ospeed;
erase_char = s.sg_erase;
kill_char = s.sg_kill;
werase_char = l.t_werasc;
/*
* Set the modes to the way we want them.
*/
s.sg_flags |= CBREAK;
s.sg_flags &= ~(ECHO|XTABS);
} else
{
/*
* Restore saved modes.
*/
s = save_term;
}
(void)ioctl(2, TIOCSETN, &s);
#endif
}
/*
* Get terminal capabilities via termcap.
*/
get_term()
{
char termbuf[2048];
char *sp;
char *term;
int hard;
#ifdef TIOCGWINSZ
struct winsize w;
#else
#ifdef WIOCGETD
struct uwdata w;
#endif
#endif
static char sbuf[1024];
char *getenv(), *strcpy();
/*
* Find out what kind of terminal this is.
*/
if ((term = getenv("TERM")) == NULL)
term = "unknown";
if (tgetent(termbuf, term) <= 0)
(void)strcpy(termbuf, "dumb:co#80:hc:");
/*
* Get size of the screen.
*/
#ifdef TIOCGWINSZ
if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_row > 0)
sc_height = w.ws_row;
else
#else
#ifdef WIOCGETD
if (ioctl(2, WIOCGETD, &w) == 0 && w.uw_height > 0)
sc_height = w.uw_height/w.uw_vs;
else
#endif
#endif
sc_height = tgetnum("li");
hard = (sc_height < 0 || tgetflag("hc"));
if (hard) {
/* Oh no, this is a hardcopy terminal. */
sc_height = 24;
}
#ifdef TIOCGWINSZ
if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_col > 0)
sc_width = w.ws_col;
else
#ifdef WIOCGETD
if (ioctl(2, WIOCGETD, &w) == 0 && w.uw_width > 0)
sc_width = w.uw_width/w.uw_hs;
else
#endif
#endif
sc_width = tgetnum("co");
if (sc_width < 0)
sc_width = 80;
(void) setvari("_sc_height", (long) sc_height - 1);
(void) setvari("_sc_width", (long) sc_width);
auto_wrap = tgetflag("am");
ignaw = tgetflag("xn");
retain_below = tgetflag("db");
/*
* Assumes termcap variable "sg" is the printing width of
* the standout sequence, the end standout sequence,
* the underline sequence, the end underline sequence,
* the boldface sequence, and the end boldface sequence.
*/
if ((so_width = tgetnum("sg")) < 0)
so_width = 0;
be_width = bo_width = ue_width = ul_width = se_width = so_width;
/*
* Get various string-valued capabilities.
*/
sp = sbuf;
sc_pad = tgetstr("pc", &sp);
if (sc_pad != NULL)
PC = *sc_pad;
sc_init = tgetstr("ti", &sp);
if (sc_init == NULL)
sc_init = "";
sc_deinit= tgetstr("te", &sp);
if (sc_deinit == NULL)
sc_deinit = "";
sc_eol_clear = tgetstr("ce", &sp);
if (hard || sc_eol_clear == NULL || *sc_eol_clear == '\0')
{
sc_eol_clear = "";
}
sc_clear = tgetstr("cl", &sp);
if (hard || sc_clear == NULL || *sc_clear == '\0')
{
sc_clear = "\n\n";
}
sc_move = tgetstr("cm", &sp);
if (hard || sc_move == NULL || *sc_move == '\0')
{
/*
* This is not an error here, because we don't
* always need sc_move.
* We need it only if we don't have home or lower-left.
*/
sc_move = "";
}
sc_s_in = tgetstr("so", &sp);
if (hard || sc_s_in == NULL)
sc_s_in = "";
sc_s_out = tgetstr("se", &sp);
if (hard || sc_s_out == NULL)
sc_s_out = "";
sc_u_in = tgetstr("us", &sp);
if (hard || sc_u_in == NULL)
sc_u_in = sc_s_in;
sc_u_out = tgetstr("ue", &sp);
if (hard || sc_u_out == NULL)
sc_u_out = sc_s_out;
sc_b_in = tgetstr("md", &sp);
if (hard || sc_b_in == NULL)
{
sc_b_in = sc_s_in;
sc_b_out = sc_s_out;
} else
{
sc_b_out = tgetstr("me", &sp);
if (hard || sc_b_out == NULL)
sc_b_out = "";
}
sc_home = tgetstr("ho", &sp);
if (hard || sc_home == NULL || *sc_home == '\0')
{
if (*sc_move == '\0')
{
/*
* This last resort for sc_home is supposed to
* be an up-arrow suggesting moving to the
* top of the "virtual screen". (The one in
* your imagination as you try to use this on
* a hard copy terminal.)
*/
sc_home = "|\b^";
} else
{
/*
* No "home" string,
* but we can use "move(0,0)".
*/
(void)strcpy(sp, tgoto(sc_move, 0, 0));
sc_home = sp;
sp += strlen(sp) + 1;
}
}
sc_lower_left = tgetstr("ll", &sp);
if (hard || sc_lower_left == NULL || *sc_lower_left == '\0')
{
if (*sc_move == '\0')
{
sc_lower_left = "\r";
} else
{
/*
* No "lower-left" string,
* but we can use "move(0,last-line)".
*/
(void)strcpy(sp, tgoto(sc_move, 0, sc_height-1));
sc_lower_left = sp;
sp += strlen(sp) + 1;
}
}
/*
* To add a line at top of screen and scroll the display down,
* we use "al" (add line) or "sr" (scroll reverse).
*/
if ((sc_addline = tgetstr("al", &sp)) == NULL ||
*sc_addline == '\0')
sc_addline = tgetstr("sr", &sp);
if (hard || sc_addline == NULL || *sc_addline == '\0')
{
sc_addline = "";
/* Force repaint on any backward movement */
back_scroll = 0;
}
if (tgetflag("bs"))
sc_backspace = "\b";
else
{
sc_backspace = tgetstr("bc", &sp);
if (sc_backspace == NULL || *sc_backspace == '\0')
sc_backspace = "\b";
}
}
/*
* Below are the functions which perform all the
* terminal-specific screen manipulation.
*/
int putchr();
/*
* Initialize terminal
*/
init()
{
tputs(sc_init, sc_height, putchr);
}
/*
* Deinitialize terminal
*/
deinit()
{
tputs(sc_deinit, sc_height, putchr);
}
/*
* Home cursor (move to upper left corner of screen).
*/
home()
{
tputs(sc_home, 1, putchr);
}
/*
* Add a blank line (called with cursor at home).
* Should scroll the display down.
*/
add_line()
{
tputs(sc_addline, sc_height, putchr);
}
int short_file; /* if file less than a screen */
lower_left()
{
if (short_file) {
putchr('\r');
flush();
}
else
tputs(sc_lower_left, 1, putchr);
}
/*
* Ring the terminal bell.
*/
bell()
{
putchr('\7');
}
/*
* Clear the screen.
*/
clear()
{
if (mode_flags & M_SO)
so_exit();
if (mode_flags & M_UL)
ul_exit();
if (mode_flags & M_BO)
bo_exit();
tputs(sc_clear, sc_height, putchr);
}
/*
* Clear from the cursor to the end of the cursor's line.
* {{ This must not move the cursor. }}
*/
clear_eol()
{
if (mode_flags & M_SO)
so_exit();
if (mode_flags & M_UL)
ul_exit();
if (mode_flags & M_BO)
bo_exit();
tputs(sc_eol_clear, 1, putchr);
}
/*
* Begin "standout" (bold, underline, or whatever).
*/
so_enter()
{
tputs(sc_s_in, 1, putchr);
mode_flags |= M_SO;
}
/*
* End "standout".
*/
so_exit()
{
tputs(sc_s_out, 1, putchr);
mode_flags &= ~M_SO;
}
/*
* Begin "underline" (hopefully real underlining,
* otherwise whatever the terminal provides).
*/
ul_enter()
{
tputs(sc_u_in, 1, putchr);
mode_flags |= M_UL;
}
/*
* End "underline".
*/
ul_exit()
{
tputs(sc_u_out, 1, putchr);
mode_flags &= ~M_UL;
}
/*
* Begin "bold"
*/
bo_enter()
{
tputs(sc_b_in, 1, putchr);
mode_flags |= M_BO;
}
/*
* End "bold".
*/
bo_exit()
{
tputs(sc_b_out, 1, putchr);
mode_flags &= ~M_BO;
}
/*
* Erase the character to the left of the cursor
* and move the cursor left.
*/
backspace()
{
/*
* Try to erase the previous character by overstriking with a space.
*/
tputs(sc_backspace, 1, putchr);
putchr(' ');
tputs(sc_backspace, 1, putchr);
}
/*
* Output a plain backspace, without erasing the previous char.
*/
putbs()
{
tputs(sc_backspace, 1, putchr);
}