Changeset View
Changeset View
Standalone View
Standalone View
contrib/mg/tty.c
- This file was added.
/* $OpenBSD: tty.c,v 1.39 2021/03/20 09:00:49 lum 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 <sys/ioctl.h> | |||||
#include <sys/queue.h> | |||||
#include <sys/types.h> | |||||
#include <sys/time.h> | |||||
#include <signal.h> | |||||
#include <stdio.h> | |||||
#include <term.h> | |||||
#include <unistd.h> | |||||
#include "def.h" | |||||
static int charcost(const 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); | |||||
volatile sig_atomic_t winch_flag; | |||||
int tceeol; | |||||
int tcinsl; | |||||
int tcdell; | |||||
/* ARGSUSED */ | |||||
static void | |||||
winchhandler(int sig) | |||||
{ | |||||
winch_flag = 1; | |||||
} | |||||
/* | |||||
* Initialize the terminal when the editor | |||||
* gets started up. | |||||
*/ | |||||
void | |||||
ttinit(void) | |||||
{ | |||||
char *tty; | |||||
int errret; | |||||
if (batch == 1) | |||||
tty = "pty"; | |||||
else | |||||
tty = NULL; | |||||
if (setupterm(tty, STDOUT_FILENO, &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 = curbp->b_nlchr; | |||||
} | |||||
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) | |||||
{ | |||||
ttykeymaptidy(); | |||||
/* 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; | |||||
/* One line special cases */ | |||||
if (row == bot) { | |||||
ttmove(row, 0); | |||||
tteeol(); | |||||
return; | |||||
} | |||||
/* Use scroll region and back index */ | |||||
if (change_scroll_region && scroll_reverse) { | |||||
nl = bot - row; | |||||
ttwindow(row, bot); | |||||
ttmove(row, 0); | |||||
while (nchunk--) | |||||
putpad(scroll_reverse, nl); | |||||
ttnowindow(); | |||||
return; | |||||
/* else use insert/delete line */ | |||||
} 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; | |||||
struct winsize winsize; | |||||
if (ioctl(0, TIOCGWINSZ, &winsize) == 0) { | |||||
newrow = winsize.ws_row; | |||||
newcol = winsize.ws_col; | |||||
} | |||||
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(const char *s) | |||||
{ | |||||
cci = 0; | |||||
tputs(s, nrow, fakec); | |||||
return (cci); | |||||
} |