|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T v
Length: 38157 (0x950d)
Types: TextFile
Names: »vtrm.c«
└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki
└─⟦this⟧ »EUUGD11/euug-87hel/sec1/vtrm/vtrm.c«
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1986. */
#define lenline len_line /* Avoid name conflict with lenline in tex.c */
/*
* Virtual TeRMinal package.
* (For a description see at the end of this file.)
*
* TO DO:
* - add interrupt handling (trminterrupt)
* - adapt to changed window size when suspended or at SIGWINCH
* (unfortunately, the calling module must be changed first --
* it is not prepared for the changed window size...)
*/
/*
* Includes and data definitions.
*/
#include <stdio.h>
#ifndef TERMIO
#include <sgtty.h>
#else
#include <termio.h>
#endif TERMIO
#include <signal.h>
#include <ctype.h>
#include "trm.h"
char *getenv();
int tgetent();
int tgetnum();
int tgetflag();
char *tgetstr();
#ifdef TRACE
#define Tprintf(args) fprintf args
#else
#define Tprintf(args) /*empty*/
#endif
#ifdef lint
#define VOID (void)
#else
#define VOID
#endif
#define Forward
#define Visible
#define Hidden static
#define Procedure
typedef char *string;
typedef int bool;
#define Yes 1
#define No 0
#define Min(a,b) ((a) <= (b) ? (a) : (b))
/* tty modes */
#ifndef TERMIO
/* v7/BSD tty control */
Hidden struct sgttyb oldtty, newtty;
#ifdef TIOCSETN
/* Redefine stty to uses TIOCSETN, so type-ahead is not flushed */
#define stty(fd, bp) VOID ioctl(fd, (int) TIOCSETN, (char *) bp)
#endif
#ifdef TIOCSLTC /* BSD -- local special chars, must all be turned off */
static struct ltchars oldltchars;
static struct ltchars newltchars= {-1, -1, -1, -1, -1, -1};
#endif TIOCSLTC
#ifdef TIOCSETC /* V7 -- standard special chars, some must be turned off too */
static struct tchars oldtchars;
static struct tchars newtchars;
#endif TIOCSETC
#else TERMIO
/* AT&T tty control */
Hidden struct termio oldtty, newtty;
#define gtty(fd,bp) ioctl(fd, TCGETA, (char *) bp)
#define stty(fd,bp) VOID ioctl(fd, TCSETAW, (char *) bp)
#endif TERMIO
Hidden bool know_ttys = No;
/* visible data for termcap */
char PC;
char *BC;
char *UP;
short ospeed;
Forward int outchar(); /* procedure for termcap's tputs */
#define Putstr(str) tputs((str), 1, outchar)
extern char *tgoto();
/* termcap terminal capabilities */
Hidden int lines;
Hidden int cols;
/*
* String-valued capabilities are saved in one big array.
* Extend this only at the end (even though it disturbs the sorting)
* because otherwise you have to change all macros...
*/
#define par_al_str strcaps[0] /* parametrized al (AL) */
#define cap_cm_str strcaps[1] /* screen-relative cursor motion (CM) */
#define par_dl_str strcaps[2] /* parametrized dl (DL) */
#define al_str strcaps[3] /* add new blank line */
#define cd_str strcaps[4] /* clear to end of display */
#define ce_str strcaps[5] /* clear to end of line */
#define cl_str strcaps[6] /* cursor home and clear screen */
#define cm_str strcaps[7] /* cursor motion */
#define cp_str strcaps[8] /* cursor position sense reply */
#define cr_str strcaps[9] /* carriage return */
#define cs_str strcaps[10] /* change scrolling region */
#define dc_str strcaps[11] /* delete character */
#define dl_str strcaps[12] /* delete line */
#define dm_str strcaps[13] /* enter delete mode */
#define do_str strcaps[14] /* cursor down one line */
#define ed_str strcaps[15] /* end delete mode */
#define ei_str strcaps[16] /* end insert mode */
#define ho_str strcaps[17] /* cursor home */
#define ic_str strcaps[18] /* insert character (if necessary; may pad) */
#define im_str strcaps[19] /* enter insert mode */
#define nd_str strcaps[20] /* cursor right (non-destructive space) */
#define nl_str strcaps[21] /* newline */
#define se_str strcaps[22] /* end standout mode */
#define sf_str strcaps[23] /* scroll text up (from bottom of region) */
#define so_str strcaps[24] /* begin standout mode */
#define sp_str strcaps[25] /* sense cursor position */
#define sr_str strcaps[26] /* scroll text down (from top of region) */
#define te_str strcaps[27] /* end termcap */
#define ti_str strcaps[28] /* start termcap */
#define vb_str strcaps[29] /* visible bell */
#define ve_str strcaps[30] /* make cursor visible again */
#define vi_str strcaps[31] /* make cursor invisible */
#define le_str strcaps[32] /* cursor left */
#define bc_str strcaps[33] /* backspace character */
#define up_str strcaps[34] /* cursor up */
#define pc_str strcaps[35] /* pad character */
#define ks_str strcaps[36] /* keypad mode start */
#define ke_str strcaps[37] /* keypad mode end */
/* Insert new entries here only! Don't forget to change the next line! */
#define NSTRCAPS 38 /* One more than the last entry's index */
Hidden char *strcaps[NSTRCAPS];
Hidden char strcapnames[] =
"ALCMDLalcdceclcmcpcrcsdcdldmdoedeihoicimndnlsesfsospsrtetivbvevilebcuppckske";
/* Same for Boolean-valued capabilities */
#define has_am flagcaps[0] /* has automatic margins */
#define has_da flagcaps[1] /* display may be retained above screen */
#define has_db flagcaps[2] /* display may be retained below screen */
#define has_in flagcaps[3] /* not save to have null chars on the screen */
#define has_mi flagcaps[4] /* move safely in insert (and delete?) mode */
#define has_ms flagcaps[5] /* move safely in standout mode */
#define has_xs flagcaps[6] /* standout not erased by overwriting */
#define has_bs flagcaps[7] /* terminal can backspace */
#define hardcopy flagcaps[8] /* hardcopy terminal */
#define NFLAGS 9
Hidden char flagcaps[NFLAGS];
Hidden char flagnames[]= "amdadbinmimsxsbshc";
Hidden Procedure
getcaps(parea)
register char *parea;
{
register char *capname;
register char **capvar;
register char *flagvar;
for (capname= flagnames, flagvar= flagcaps;
*capname != '\0'; capname += 2, ++flagvar)
*flagvar= tgetflag(capname);
for (capname= strcapnames, capvar= strcaps;
*capname != '\0'; capname += 2, ++capvar)
*capvar= tgetstr(capname, parea);
}
/* terminal status */
/* calling order of Visible Procs */
Hidden bool started = No;
/* to exports the capabilities mentioned in vtrm.h: */
Hidden int flags = 0;
/* cost for impossible operations */
#define Infinity 9999
/* Allow for adding Infinity+Infinity within range */
/* (Range is assumed at least 2**15 - 1) */
/* The following for all sorts of undefined things (except for UNKNOWN char) */
#define Undefined (-1)
/* current mode of putting char's */
#define Normal 0
#define Insert 1
#define Delete 2
Hidden short mode = Normal;
/* current standout mode */
#define Off 0
#define On 0200
Hidden short so_mode = Off;
/* masks for char's and short's */
#define NULCHAR '\000'
#define CHAR 0177
#define SOBIT On
#define SOCHAR 0377
/* if (has_xs) record cookies placed on screen in extra bit */
/* type of cookie is determined by the SO bit */
#define XSBIT 0400
#define SOCOOK 0600
#define COOKBITS SOCOOK
#define UNKNOWN 1
#define NOCOOK UNKNOWN
/* current cursor position */
Hidden short cur_y = Undefined, cur_x = Undefined;
/* "line[y][x]" holds the char on the terminal, with the SOBIT and XSBIT.
* the SOBIT tells whether the character is standing out, the XSBIT whether
* there is a cookie on the screen at this position.
* In particular a standend-cookie may be recorded AFTER the line
* (just in case some trmputdata will write after that position).
* "lenline[y]" holds the length of the line.
* Unknown chars will be 1, so the optimising compare in putline will fail.
* (Partially) empty lines are distinghuished by "lenline[y] < cols".
*/
Hidden short **line = 0, *lenline = 0;
/* Clear the screen initially iff only memory cursor addressing available */
Hidden bool mustclear = No;
/* Make the cursor invisible when trmsync() tries to move outside the screen */
Hidden bool no_cursor = No;
/* Optimise cursor motion */
Hidden int abs_cost; /* cost of absolute cursor motion */
Hidden int cr_cost; /* cost of carriage return */
Hidden int do_cost; /* cost of down */
Hidden int le_cost; /* cost of left */
Hidden int nd_cost; /* cost of right */
Hidden int up_cost; /* cost of up */
/* Optimise trailing match in put_line, iff the terminal can insert and delete
* characters; the cost per n characters will be:
* n * MultiplyFactor + OverHead
*/
Hidden int ins_mf, ins_oh, del_mf, del_oh;
Hidden int ed_cost, ei_cost; /* used in move() */
/* The type of scrolling possible determines which routines get used;
* these may be:
* (1) with addline and deleteline (termcap: al_str & dl_str);
* (2) with a settable scrolling region, like VT100 (cs_str, sr_str, sf_str);
* (3) no scrolling available. (NOT YET IMPLEMENTED)
*/
Hidden Procedure (*scr_up)();
Hidden Procedure (*scr_down)();
Forward Procedure scr1up();
Forward Procedure scr1down();
Forward Procedure scr2up();
Forward Procedure scr2down();
/*Forward Procedure scr3up(); */
/*Forward Procedure scr3down(); */
/*
* Starting, Ending and (fatal) Error.
*/
/*
* Initialization call.
* Determine terminal capabilities from termcap.
* Set up tty modes.
* Start up terminal and internal administration.
* Return 0 if all well, error code if in trouble.
*/
Visible int
trmstart(plines, pcols, pflags)
int *plines;
int *pcols;
int *pflags;
{
register int err;
Tprintf((stderr, "\ttrmstart(&li, &co, &fl);\n"));
if (started)
return TE_TWICE;
err= gettermcaps();
if (err != TE_OK)
return err;
err= setttymode();
if (err != TE_OK)
return err;
err= start_trm();
if (err != TE_OK) {
trmend();
return err;
}
*plines = lines;
*pcols = cols;
*pflags = flags;
started = Yes;
return TE_OK;
}
/*
* Termination call.
* Reset tty modes, etc.
* Beware that it might be called by a caught interrupt even in the middle
* of trmstart()!
*/
Visible Procedure
trmend()
{
Tprintf((stderr, "\ttrmend();\n"));
set_mode(Normal);
if (so_mode != Off)
standend();
Putstr(ke_str);
Putstr(te_str);
VOID fflush(stdout);
resetttymode();
started = No;
}
/*
* Set all internal statuses to undefined, especially the contents of
* the screen, so a hard redraw will not be optimised to heaven.
*/
Visible Procedure
trmundefined()
{
register int y, x;
Tprintf((stderr, "\ttrmundefined();\n"));
cur_y = cur_x = Undefined;
mode = so_mode = Undefined;
for (y = 0; y < lines; y++) {
for (x = 0; x <= cols; x++)
line[y][x] = 1; /* impossible char, no so bits */
lenline[y] = cols;
}
}
#ifndef NDEBUG
Hidden Procedure
check_started(m)
char *m;
{
if (!started) {
trmend();
fprintf(stderr, "bad VTRM call\n");
abort();
}
}
#else
#define check_started(m) /*empty*/
#endif
Hidden int ccc;
/*ARGSUSED*/
Hidden Procedure
countchar(ch)
char ch;
{
ccc++;
}
Hidden int
strcost(str)
char *str;
{
if (str == NULL)
return Infinity;
return str0cost(str);
}
Hidden int
str0cost(str)
char *str;
{
ccc = 0;
tputs(str, 1, countchar);
return ccc;
}
/*
* Get terminal capabilities from termcap and compute related static
* properties. Return TE_OK if all well, error code otherwise.
*
* TO DO:
* - use the curses trick of a reading the capabilities in a loop
* rather than one at a time.
*/
Hidden int
gettermcaps()
{
string trmname;
char tc_buf[1024];
static char strbuf[1024];
char *area = strbuf;
int sg;
static bool tc_initialized = No;
#ifdef TIOCGWINSZ
struct winsize win;
#endif
if (tc_initialized)
return TE_OK;
trmname=getenv("TERM");
if (trmname == NULL || trmname[0] == '\0')
return TE_NOTERM;
if (tgetent(tc_buf, trmname) != 1)
return TE_BADTERM;
getcaps(&area); /* Read all flag and string type capabilities */
if (hardcopy)
return TE_DUMB;
BC = le_str;
if (BC == NULL) {
BC = bc_str;
if (BC == NULL) {
if (has_bs)
BC = "\b";
else
return TE_DUMB;
}
}
UP = up_str;
if (UP == NULL)
return TE_DUMB;
PC = (pc_str != NULL? pc_str[0] : NULCHAR);
if (cm_str == NULL) {
cm_str = cap_cm_str;
if (cm_str == NULL) {
if (ho_str == NULL || do_str == NULL || nd_str == NULL)
return TE_DUMB;
}
else
mustclear = Yes;
}
if (al_str && dl_str) {
scr_up = scr1up;
scr_down = scr1down;
flags |= CAN_SCROLL;
}
else {
if (sf_str == NULL)
sf_str = "\n";
if (cs_str && sr_str) {
scr_up = scr2up;
scr_down = scr2down;
flags |= CAN_SCROLL;
}
else
return TE_DUMB;
}
lines = tgetnum("li");
cols = tgetnum("co");
#ifdef TIOCGWINSZ
if (ioctl(0, TIOCGWINSZ, &win) == 0) {
if (win.ws_col > 0)
cols = win.ws_col;
if (win.ws_row > 0)
lines = win.ws_row;
}
#endif
if (lines < 0) lines = 24;
if (cols < 0) cols = 80;
if ((sg=tgetnum("sg")) == 0)
has_xs = Yes;
else if (sg > 0)
return TE_DUMB;
if (!ce_str)
return TE_DUMB;
if (cr_str == NULL) cr_str = "\r";
if (do_str == NULL) {
do_str = nl_str;
if (do_str == NULL) do_str = "\n";
}
le_str = BC;
up_str = UP;
if (vb_str == NULL) /* then we will do with the audible bell */
vb_str = "\007";
/* cursor sensing (non standard) */
if (cp_str != NULL && sp_str != NULL)
flags |= CAN_SENSE;
if (so_str != NULL && se_str != NULL)
flags |= HAS_STANDOUT;
/* calculate costs of local and absolute cursor motions */
if (cm_str == NULL)
abs_cost = Infinity;
else
abs_cost = strcost(tgoto(cm_str, 0, 0));
cr_cost = strcost(cr_str);
do_cost = strcost(do_str);
le_cost = strcost(le_str);
nd_cost = strcost(nd_str);
up_cost = strcost(up_str);
/* cost of leaving insert or delete mode, used in move() */
ei_cost = str0cost(ei_str);
ed_cost = str0cost(ed_str);
/* calculate insert and delete cost multiply_factor and overhead */
if (((im_str && ei_str) || ic_str) && dc_str) {
flags |= CAN_OPTIMISE;
ins_mf = 1 + str0cost(ic_str);
ins_oh = str0cost(im_str) + ei_cost;
del_mf = str0cost(dc_str);
del_oh = str0cost(dm_str) + ed_cost;
}
tc_initialized = Yes;
return TE_OK;
}
Hidden int
setttymode()
{
if (!know_ttys) {
if (gtty(0, &oldtty) != 0 || gtty(0, &newtty) != 0)
return TE_NOTTY;
#ifndef TERMIO
ospeed = oldtty.sg_ospeed;
#ifdef PWB
newtty.sg_flags = (newtty.sg_flags & ~ECHO & ~CRMOD & ~XTABS)
| RAW;
#else PWB
newtty.sg_flags = (newtty.sg_flags & ~ECHO & ~CRMOD & ~XTABS)
| CBREAK;
#endif PWB
#ifdef TIOCSLTC
VOID ioctl(0, (int) TIOCGLTC, (char *) &oldltchars);
#endif
#ifdef TIOCSETC
VOID ioctl(0, (int) TIOCGETC, (char *) &oldtchars);
#endif
#else TERMIO
ospeed= oldtty.c_lflag & CBAUD;
newtty.c_iflag &= ~ICRNL; /* No CR->NL mapping on input */
newtty.c_oflag &= ~ONLCR; /* NL doesn't output CR */
newtty.c_lflag &= ~(ICANON|ECHO); /* No line editing, no echo */
newtty.c_cc[VMIN]= 3; /* wait for 3 characters */
newtty.c_cc[VTIME]= 1; /* or 0.1 sec. */
#endif TERMIO
know_ttys = Yes;
}
stty(0, &newtty);
#ifndef TERMIO
#ifdef TIOCSLTC
VOID ioctl(0, (int) TIOCSLTC, (char *) &newltchars);
#endif TIOCSLTC
#ifdef TIOCSETC
VOID ioctl(0, (int) TIOCGETC, (char *) &newtchars);
newtchars.t_intrc= -1;
#ifndef DEBUG
newtchars.t_quitc= -1;
#endif
newtchars.t_eofc= -1;
newtchars.t_brkc= -1;
VOID ioctl(0, (int) TIOCSETC, (char *) &newtchars);
#endif TIOCSETC
#endif TERMIO
return TE_OK;
}
Hidden Procedure
resetttymode()
{
if (know_ttys) {
stty(0, &oldtty);
#ifndef TERMIO
#ifdef TIOCSLTC
VOID ioctl(0, (int) TIOCSLTC, (char *) &oldltchars);
#endif TIOCSLTC
#ifdef TIOCSETC
VOID ioctl(0, (int) TIOCSETC, (char *) &oldtchars);
#endif TIOCSETC
#endif TERMIO
know_ttys= No;
}
}
Hidden int
start_trm()
{
register int y;
if (line == NULL) {
if ((line = (short**) malloc(lines * sizeof(short*))) == NULL)
return TE_NOMEM;
for (y = 0; y < lines; y++) {
if ((line[y] = (short*)
malloc((cols+1) * sizeof(short))) == NULL)
return TE_NOMEM;
}
}
if (lenline == NULL) {
if ((lenline = (short*) malloc(lines * sizeof(short))) == NULL)
return TE_NOMEM;
}
trmundefined();
Putstr(ti_str);
Putstr(ks_str);
if (cs_str)
Putstr(tgoto(cs_str, lines-1, 0));
if (mustclear)
clear_lines(0, lines-1);
return TE_OK;
}
/*
* Sensing and moving the cursor.
*/
/*
* Sense the current (y, x) cursor position, after a possible manual
* change by the user with local cursor motions.
* If the terminal cannot be asked for the current cursor position,
* or if the string returned by the terminal is garbled,
* the position is made Undefined.
*/
Visible Procedure
trmsense(py, px)
int *py;
int *px;
{
bool getpos();
Tprintf((stderr, "\ttrmsense(&yy, &xx);\n"));
check_started("trmsense");
*py = *px = Undefined;
set_mode(Normal);
if (so_mode != Off)
standend();
if (flags&CAN_SENSE && getpos(py, px)) {
if (*py < 0 || lines <= *py || *px < 0 || cols <= *px)
*py = *px = Undefined;
}
cur_y = Undefined;
cur_x = Undefined;
}
Hidden bool
getpos(py, px)
int *py, *px;
{
char *format = cp_str;
int fc; /* current format character */
int ic; /* current input character */
int num;
int on_y = 1;
bool incr_orig = No;
int i, ni;
Putstr(sp_str);
VOID fflush(stdout);
while (fc = *format++) {
if (fc != '%') {
if (trminput() != fc)
return No;
}
else {
switch (fc = *format++) {
case '%':
if (trminput() != '%')
return No;
continue;
case 'r':
on_y = 1 - on_y;
continue;
case 'i':
incr_orig = Yes;
continue;
case 'd':
ic = trminput();
if (!isdigit(ic))
return No;
num = ic - '0';
while (isdigit(ic=trminput()))
num = 10*num + ic - '0';
trmpushback(ic);
break;
case '2':
case '3':
ni = fc - '0';
num = 0;
for (i=0; i<ni; i++) {
ic = trminput();
if (isdigit(ic))
num = 10*num + ic - '0';
else
return No;
}
break;
case '+':
num = trminput() - *format++;
break;
case '-':
num = trminput() + *format++;
break;
default:
return No;
}
/* assign num to parameter */
if (incr_orig)
num--;
if (on_y)
*py = num;
else
*px = num;
on_y = 1 - on_y;
}
}
return Yes;
}
/*
* To move over characters by rewriting them, we have to check:
* (1) that the screen has been initialised on these positions;
* (2) we do not screw up characters
* when rewriting line[y] from x_from upto x_to
*/
Hidden bool
rewrite_ok(y, xfrom, xto)
int y, xfrom, xto;
{
register short *plnyx, *plnyto;
if (xto > lenline[y])
return No;
plnyto = &line[y][xto];
for (plnyx = &line[y][xfrom]; plnyx <= plnyto; plnyx++)
if (*plnyx == UNKNOWN
||
(!has_xs && (*plnyx & SOBIT) != so_mode)
)
return No;
return Yes;
}
/*
* Move to position y,x on the screen
*/
/* possible move types for y and x respectively: */
#define None 0
#define Down 1
#define Up 2
#define Right 1
#define ReWrite 2
#define Left 3
#define CrWrite 4
Hidden Procedure
move(y, x)
int y, x;
{
int dy, dx;
int y_cost, x_cost, y_move, x_move;
int mode_cost;
int xi;
if (cur_y == y && cur_x == x)
return;
if (!has_mi || mode == Undefined)
set_mode(Normal);
if (!has_xs && ((!has_ms && so_mode != Off) || so_mode == Undefined))
standend();
if (cur_y == Undefined || cur_x == Undefined)
goto absmove;
dy = y - cur_y;
dx = x - cur_x;
if (dy > 0) {
y_move = Down;
y_cost = dy * do_cost;
}
else if (dy < 0) {
y_move = Up;
y_cost = -dy * up_cost;
}
else {
y_move = None;
y_cost = 0;
}
if (y_cost < abs_cost) {
switch (mode) {
case Normal:
mode_cost = 0;
break;
case Insert:
mode_cost = ei_cost;
break;
case Delete:
mode_cost = ed_cost;
break;
}
if (dx > 0) {
x_cost = dx + mode_cost;
if (dx*nd_cost < x_cost || !rewrite_ok(y, cur_x, x)) {
x_cost = dx * nd_cost;
x_move = Right;
}
else
x_move = ReWrite;
}
else if (dx < 0) {
x_cost = -dx * le_cost;
x_move = Left;
}
else {
x_cost = 0;
x_move = None;
}
if (cr_cost + x + mode_cost < x_cost && rewrite_ok(y, 0, x)) {
x_move = CrWrite;
x_cost = cr_cost + x + mode_cost;
}
}
else
x_cost = abs_cost;
if (y_cost + x_cost < abs_cost) {
switch (y_move) {
case Down:
while (dy-- > 0) Putstr(do_str);
break;
case Up:
while (dy++ < 0) Putstr(up_str);
break;
}
switch (x_move) {
case Right:
while (dx-- > 0) Putstr(nd_str);
break;
case Left:
while (dx++ < 0) Putstr(le_str);
break;
case CrWrite:
Putstr(cr_str);
cur_x = 0;
/* FALL THROUGH */
case ReWrite:
set_mode(Normal);
for (xi = cur_x; xi < x; xi++)
putchar(line[y][xi]);
break;
}
}
else
{
absmove:
if (cm_str == NULL) {
Putstr(ho_str);
for (cur_y = 0; cur_y < y; ++cur_y)
Putstr(do_str);
/* Should try to use tabs here: */
for (cur_x = 0; cur_x < x; ++cur_x)
Putstr(nd_str);
}
else
Putstr(tgoto(cm_str, x, y));
}
cur_y = y;
cur_x = x;
}
/*
* Putting data on the screen.
*/
/*
* Fill screen area with given data.
* Characters with the SO-bit (0200) set are put in standout mode.
*/
Visible Procedure
trmputdata(yfirst, ylast, indent, data)
int yfirst;
int ylast;
register int indent;
register string data;
{
register int y;
int x, len, lendata, space;
Tprintf((stderr, "\ttrmputdata(%d, %d, %d, \"%s\");\n", yfirst, ylast, indent, data));
check_started("trmputdata");
if (yfirst < 0)
yfirst = 0;
if (ylast >= lines)
ylast = lines-1;
space = cols*(ylast-yfirst+1) - indent;
if (space <= 0)
return;
yfirst += indent/cols;
indent %= cols;
y= yfirst;
if (!data)
data= ""; /* Safety net */
x = indent;
lendata = strlen(data);
if (ylast == lines-1 && lendata >= space)
lendata = space - 1;
len = Min(lendata, cols-x);
while (y <= ylast) {
put_line(y, x, data, len);
y++;
lendata -= len;
if (lendata > 0) {
x = 0;
data += len;
len = Min(lendata, cols);
}
else
break;
}
if (y <= ylast)
clear_lines(y, ylast);
}
/*
* We will first try to get the picture:
*
* op>>>>>>>>>>>op oq<<<<<<<<<<<<<<<<<<<<<<<<oq
* ^ ^ ^ ^
* <xskip><-----m1----><----od-----><-----------m2----------->
* OLD: "You're in a maze of twisty little pieces of code, all alike"
* NEW: "in a maze of little twisting pieces of code, all alike"
* <-----m1----><-----nd------><-----------m2----------->
* ^ ^ ^ ^
* np>>>>>>>>>>>np nq<<<<<<<<<<<<<<<<<<<<<<<<nq
* where
* op, oq, np, nq are pointers to start and end of Old and New data,
* and
* xskip = length of indent to be skipped,
* m1 = length of Matching part at start,
* od = length of Differing mid on screen,
* nd = length of Differing mid in data to be put,
* m2 = length of Matching trail.
*
* Then we will try to find a long blank-or-cleared piece in <nd+m2>:
*
* <---m1---><---d1---><---nb---><---d2---><---m2--->
* ^ ^ ^ ^ ^
* np bp bq1 nq nend
* where
* bp, bq are pointers to start and AFTER end of blank piece,
* and
* d1 = length of differing part before blank piece,
* nb = length of blank piece to be skipped,
* d2 = length of differing part after blank piece.
* Remarks:
* d1 + nb + d2 == nd,
* and
* d2 maybe less than 0.
*/
Hidden int
put_line(y, xskip, data, len)
int y, xskip;
string data;
int len;
{
register short *op, *oq;
register char *np, *nq, *nend;
char *bp, *bq1, *p, *q;
int m1, m2, od, nd, delta, dd, d1, nb, d2;
bool skipping;
int cost, o_cost; /* normal and optimising cost */
/* calculate the magic parameters */
op = &line[y][xskip];
oq = &line[y][lenline[y]-1];
np = data;
nq = nend = data + len - 1;
m1 = m2 = 0;
while ((*op&SOCHAR) == (((short)*np)&SOCHAR) && op <= oq && np <= nq)
op++, np++, m1++;
if (flags & CAN_OPTIMISE)
while ((*oq&SOCHAR) == (((short)*nq)&SOCHAR) && op <= oq && np <= nq)
oq--, nq--, m2++;
od = oq - op + 1;
nd = nq - np + 1;
/* now we have the first picture above */
if (od==0 && nd==0)
return;
delta = nd - od;
/* find the blank piece */
p = q = bp = bq1 = np;
oq += m2; /* back to current eol */
if (!has_in) {
while (p <= nend) {
while (q<=nend && *q==' ' && (op>oq || *op==' '))
q++, op++;
if (q - p > bq1 - bp)
bp = p, bq1 = q;
p = ++q;
op++;
}
}
d1 = bp - np;
nb = bq1 - bp;
d2 = nq - bq1 + 1;
/* what is cheapest:
* normal: put nd+m2; (dd = nd+m2)
* skipping: put d1, skip nb, put d2+m2; (dd = d2+m2)
* optimise: put dd, insert or delete delta. (dd = min(od,nd))
*/
cost = nd + m2; /* normal cost */
if (nb > abs_cost || (d1 == 0 && nb > 0)) {
skipping = Yes;
cost -= nb - (d1>0 ? abs_cost : 0); /* skipping cost */
dd = d2;
}
else {
skipping = No;
dd = nd;
}
if (m2 != 0) {
/* try optimising */
o_cost = Min(od, nd);
if (delta > 0)
o_cost += delta * ins_mf + ins_oh;
else if (delta < 0)
o_cost += -delta * del_mf + del_oh;
if (o_cost >= cost) {
/* discard m2, no optimise */
dd += m2;
m2 = 0;
}
else {
dd = Min(od, nd);
skipping = No;
}
}
/* and now for the real work */
if (!skipping || d1 > 0)
move(y, xskip + m1);
if (has_xs)
get_so_mode();
if (skipping) {
if (d1 > 0) {
set_mode(Normal);
put_str(np, d1, No);
}
if (has_xs && so_mode != Off)
standend();
set_blanks(y, xskip+m1+d1, xskip+m1+d1+nb);
if (dd != 0 || delta < 0) {
move(y, xskip+m1+d1+nb);
np = bq1;
}
}
if (dd > 0) {
set_mode(Normal);
put_str(np, dd, No);
}
if (m2 > 0) {
if (delta > 0) {
set_mode(Insert);
ins_str(np+dd, delta);
}
else if (delta < 0) {
if (so_mode != Off)
standend();
/* Some terminals fill with standout spaces! */
set_mode(Delete);
del_str(-delta);
}
}
else {
if (delta < 0) {
clr_to_eol();
return;
}
}
lenline[y] = xskip + len;
if (cur_x == cols) {
if (!has_mi)
set_mode(Normal);
if (!has_ms)
so_mode = Undefined;
if (has_am)
cur_y++;
else
Putstr(cr_str);
cur_x = 0;
}
else if (has_xs) {
if (m2 == 0) {
if (so_mode == On)
standend();
}
else {
if (!(line[cur_y][cur_x] & XSBIT)) {
if (so_mode != (line[cur_y][cur_x] & SOBIT))
(so_mode ? standend() : standout());
}
}
}
}
Hidden Procedure
set_mode(m)
int m;
{
if (m == mode)
return;
switch (mode) {
case Insert:
Putstr(ei_str);
break;
case Delete:
Putstr(ed_str);
break;
case Undefined:
Putstr(ei_str);
Putstr(ed_str);
break;
}
switch (m) {
case Insert:
Putstr(im_str);
break;
case Delete:
Putstr(dm_str);
break;
}
mode = m;
}
Hidden Procedure
get_so_mode()
{
if (cur_x >= lenline[cur_y] || line[cur_y][cur_x] == UNKNOWN)
so_mode = Off;
else
so_mode = line[cur_y][cur_x] & SOBIT;
}
Hidden Procedure
standout()
{
Putstr(so_str);
so_mode = On;
if (has_xs)
line[cur_y][cur_x] |= SOCOOK;
}
Hidden Procedure
standend()
{
Putstr(se_str);
so_mode = Off;
if (has_xs)
line[cur_y][cur_x] = (line[cur_y][cur_x] & ~SOBIT) | XSBIT;
}
Hidden Procedure
put_str(data, n, inserting)
char *data;
int n;
bool inserting;
{
register short c, so;
short *ln_y_x, *ln_y_end;
so = so_mode;
if (has_xs) {
ln_y_x = &line[cur_y][cur_x];
ln_y_end = &line[cur_y][lenline[cur_y]];
}
while (n-- > 0) {
if (has_xs && ln_y_x <= ln_y_end && ((*ln_y_x)&XSBIT))
so = so_mode = (*ln_y_x)&SOBIT;
/* this also checks for the standend cookie AFTER */
/* the line because off the equals sign in <= */
c = ((short)(*data++))&SOCHAR;
if ((c&SOBIT) != so) {
so = c&SOBIT;
so ? standout() : standend();
}
if (inserting)
Putstr(ic_str);
put_c(c);
if (has_xs)
ln_y_x++;
}
}
Hidden Procedure
ins_str(data, n)
char *data;
int n;
{
int x;
/* x will start AFTER the line, because there might be a cookie */
for (x = lenline[cur_y]; x >= cur_x; x--)
line[cur_y][x+n] = line[cur_y][x];
put_str(data, n, Yes);
}
Hidden Procedure
del_str(n)
int n;
{
int x, xto;
xto = lenline[cur_y] - n; /* again one too far because of cookie */
if (has_xs) {
for (x = cur_x + n; x >= cur_x; x--) {
if (line[cur_y][x] & XSBIT)
break;
}
if (x >= cur_x)
line[cur_y][cur_x+n] =
(line[cur_y][cur_x+n] & CHAR)
|
(line[cur_y][x] & COOKBITS);
}
for (x = cur_x; x <= xto; x++)
line[cur_y][x] = line[cur_y][x+n];
while (n-- > 0)
Putstr(dc_str);
}
Hidden Procedure
put_c(c)
int c;
{
char ch;
short xs_flag;
ch = c&CHAR;
if (!isprint(ch) && ch != ' ') /* V7 isprint doesn't include blank */
ch= '?';
putchar(ch);
if (has_xs)
xs_flag = line[cur_y][cur_x]&XSBIT;
else
xs_flag = 0;
line[cur_y][cur_x] = (c&SOCHAR)|xs_flag;
cur_x++;
}
Hidden Procedure
clear_lines(yfirst, ylast)
int yfirst, ylast ;
{
register int y;
if (!has_xs && so_mode != Off)
standend();
if (cl_str && yfirst == 0 && ylast == lines-1) {
Putstr(cl_str);
cur_y = cur_x = 0;
for (y = 0; y < lines; ++y) {
lenline[y] = 0;
if (has_xs) line[y][0] = NOCOOK;
}
return;
}
for (y = yfirst; y <= ylast; y++) {
if (lenline[y] > 0) {
move(y, 0);
if (ylast == lines-1 && cd_str) {
Putstr(cd_str);
while (y <= ylast) {
if (has_xs) line[y][0] = NOCOOK;
lenline[y++] = 0;
}
break;
}
else {
clr_to_eol();
}
}
}
}
Hidden Procedure
clr_to_eol()
{
lenline[cur_y] = cur_x;
if (!has_xs && so_mode != Off)
standend();
Putstr(ce_str);
if (has_xs) {
if (cur_x == 0)
line[cur_y][0] = NOCOOK;
else if (line[cur_y][cur_x-1]&SOBIT)
standend();
}
}
Hidden Procedure
set_blanks
(y, xfrom, xto)
int y, xfrom, xto;
{
register int x;
for (x = xfrom; x < xto; x++) {
line[y][x] = (line[y][x]&XSBIT) | ' ';
}
}
/*
* outchar() is used by termcap's tputs;
* we can't use putchar because that's probably a macro
*/
Hidden int
outchar(ch)
char ch;
{
putchar(ch);
}
/*
* Scrolling (part of) the screen up (or down, dy<0).
*/
Visible Procedure
trmscrollup(yfirst, ylast, by)
register int yfirst;
register int ylast;
register int by;
{
Tprintf((stderr, "\ttrmscrollup(%d, %d, %d);\n", yfirst, ylast, by));
check_started("trmscrollup");
if (yfirst < 0)
yfirst = 0;
if (ylast >= lines)
ylast = lines-1;
if (yfirst > ylast)
return;
if (!has_xs && so_mode != Off)
standend();
if (by > 0 && yfirst + by > ylast
||
by < 0 && yfirst - by > ylast)
{
clear_lines(yfirst, ylast);
return;
}
if (by > 0) {
(*scr_up)(yfirst, ylast, by);
scr_lines(yfirst, ylast, by, 1);
}
else if (by < 0) {
(*scr_down)(yfirst, ylast, -by);
scr_lines(ylast, yfirst, -by, -1);
}
}
Hidden Procedure
scr_lines(yfrom, yto, n, dy)
int yfrom, yto, n, dy;
{
register int y;
short *saveln;
while (n-- > 0) {
saveln = line[yfrom];
for (y = yfrom; y != yto; y += dy) {
line[y] = line[y+dy];
lenline[y] = lenline[y+dy];
}
line[yto] = saveln;
lenline[yto] = 0;
if (has_xs) line[yto][0] = NOCOOK;
}
}
Hidden Procedure
scr1up(yfirst, ylast, n)
int yfirst;
int ylast;
int n;
{
move(yfirst, 0);
dellines(n);
if (ylast < lines-1) {
move(ylast-n+1, 0);
addlines(n);
}
}
Hidden Procedure
scr1down(yfirst, ylast, n)
int yfirst;
int ylast;
int n;
{
if (ylast == lines-1) {
clear_lines(ylast-n+1, ylast);
}
else {
move(ylast-n+1, 0);
dellines(n);
}
move(yfirst, 0);
addlines(n);
}
Hidden Procedure
addlines(n)
register int n;
{
if (par_al_str && n > 1)
Putstr(tgoto(par_al_str, n, n));
else {
while (n-- > 0)
Putstr(al_str);
}
}
Hidden Procedure
dellines(n)
register int n;
{
if (par_dl_str && n > 1)
Putstr(tgoto(par_dl_str, n, n));
else {
while (n-- > 0)
Putstr(dl_str);
}
}
Hidden Procedure
scr2up(yfirst, ylast, n)
int yfirst, ylast, n;
{
Putstr(tgoto(cs_str, ylast, yfirst));
cur_y = cur_x = Undefined;
move(ylast, 0);
while (n-- > 0) {
Putstr(sf_str);
if (has_db && ylast == lines-1)
clr_to_eol();
}
Putstr(tgoto(cs_str, lines-1, 0));
cur_y = cur_x = Undefined;
}
Hidden Procedure
scr2down(yfirst, ylast, n)
int yfirst, ylast, n;
{
Putstr(tgoto(cs_str, ylast, yfirst));
cur_y = cur_x = Undefined;
move(yfirst, 0);
while (n-- > 0) {
Putstr(sr_str);
if (has_da && yfirst == 0)
clr_to_eol();
}
Putstr(tgoto(cs_str, lines-1, 0));
cur_y = cur_x = Undefined;
}
/*
* Synchronization, move cursor to given position (or previous if < 0).
*/
Visible Procedure
trmsync(y, x)
int y;
int x;
{
Tprintf((stderr, "\ttrmsync(%d, %d);\n", y, x));
check_started("trmsync");
if (0 <= y && y < lines && 0 <= x && x < cols) {
move(y, x);
if (no_cursor) {
Putstr(ve_str);
no_cursor = No;
}
}
else if (no_cursor == No) {
Putstr(vi_str);
no_cursor = Yes;
}
VOID fflush(stdout);
}
/*
* Send a bell, visible if possible.
*/
Visible Procedure
trmbell()
{
Tprintf((stderr, "\ttrmbell();\n"));
check_started("trmbell");
Putstr(vb_str);
VOID fflush(stdout);
}
#ifdef SHOW
/*
* Show the current internal statuses of the screen on stderr.
* For debugging only.
*/
Visible Procedure
trmshow(s)
char *s;
{
int y, x;
fprintf(stderr, "<<< %s >>>\n", s);
for (y = 0; y < lines; y++) {
for (x = 0; x <= lenline[y] /*** && x < cols-1 ***/ ; x++) {
fputc(line[y][x]&CHAR, stderr);
}
fputc('\n', stderr);
for (x = 0; x <= lenline[y] && x < cols-1; x++) {
if (line[y][x]&SOBIT)
fputc('-', stderr);
else
fputc(' ', stderr);
}
fputc('\n', stderr);
for (x = 0; x <= lenline[y] && x < cols-1; x++) {
if (line[y][x]&XSBIT)
fputc('+', stderr);
else
fputc(' ', stderr);
}
fputc('\n', stderr);
}
fprintf(stderr, "CUR_Y = %d, CUR_X = %d.\n", cur_y, cur_x);
VOID fflush(stderr);
}
#endif
/*
* Return the next input character, or -1 if read fails.
* Only the low 7 bits are returned, so reading in RAW mode is permissible
* (although CBREAK is preferred if implemented).
* To avoid having to peek in the input buffer for trmavail, we use the
* 'read' system call rather than getchar().
* (The interface allows 8-bit characters to be returned, to accomodate
* larger character sets!)
*/
Hidden int pushback= -1;
int
trminput()
{
char c;
if (pushback >= 0) {
c= pushback;
pushback= -1;
return c;
}
if (read(0, &c, 1) <= 0)
return -1;
return c & 0177;
}
trmpushback(c)
int c;
{
pushback= c;
}
/*
* See if there's input available from the keyboard.
* The code to do this is dependent on the type of Unix you have
* (BSD, System V, ...).
* Return value: 0 -- no input; 1 -- input; -1 -- unimplementable.
* Note that each implementation form should first check pushback.
*
* TO DO:
* - Implement it for other than 4.x BSD! (notably System 5)
*/
#ifdef SELECT
#include <sys/time.h>
int
trmavail()
{
int nfound, nfds, readfds;
static struct timeval timeout= {0, 0};
if (pushback >= 0)
return 1;
readfds= 1 << 0;
nfds= 0+1;
nfound= select(nfds, &readfds, (int*) NIL, (int*) NIL, &timeout);
return nfound > 0;
}
#define TRMAVAIL_DEFINED
#endif SELECT
#if !defined(TRMAVAIL_DEFINED) && defined(FIONREAD)
int
trmavail()
{
long n;
if (pushback >= 0)
return 1;
ioctl(0, (int) FIONREAD, (char *) &n);
return n > 0;
}
#define TRMAVAIL_DEFINED
#endif FIONREAD
#ifndef TRMAVAIL_DEFINED
int
trmavail()
{
if (pushback >= 0)
return 1;
return -1;
}
#endif
/*
* Suspend the editor.
* Should be called only after trmend and before trmstart!
*/
trmsuspend()
{
int (*oldsig)();
oldsig= signal(SIGTSTP, SIG_IGN);
if (oldsig == SIG_IGN)
return; /* Could spawn a subshell here... */
trmend(); /* Safety net */
signal(SIGTSTP, oldsig);
kill(0, SIGSTOP);
}
/*
* DESCRIPTION.
*
* This package uses termcap to determine the terminal capabilities.
*
* The lines and columns of our virtual terminal are numbered
* y = {0...lines-1} from top to bottom, and
* x = {0...cols-1} from left to right,
* respectively.
*
* The Visible Procedures in this package are:
*
* trmstart(&lines, &cols, &flags)
* Obligatory initialization call (sets tty modes etc.),
* Returns the height and width of the screen to the integers
* whose addresses are passed as parameters, and a flag that
* describes some capabilities.
* Function return value: 0 if all went well, an error code if there
* is any trouble. No messages are printed for errors.
*
* trmundefined()
* Sets internal representation of screen and attributes to undefined.
* This is necessary for a hard redraw, which would get optimised to
* oblivion,
*
* trmsense(&y, &x)
* Returns the cursor position through its parameters
* after a possible manual change by the user.
*
* trmputdata(yfirst, ylast, indent, data)
* Fill lines {yfirst..ylast} with data, after skipping the initial
* 'indent' positions. It is assumed that these positions do not contain
* anything dangerous (like standout cookies or null characters).
*
* trmscrollup(yfirst, ylast, by)
* Shift lines {yfirst..ylast} up by lines (down |by| if by < 0).
*
* trmsync(y, x)
* Call to output data to the terminal and set cursor position.
*
* trmbell()
* Send a (possibly visible) bell, immediately (flushing stdout).
*
* trmend()
* Obligatory termination call (resets tty modes etc.).
*
* You may call these as one or more cycles of:
* + trmstart
* + zero or more times any of the other routines
* + trmend
* Trmend may be called even in the middle of trmstart; this is necessary
* to make it possible to write an interrupt handler that resets the tty
* state before exiting the program.
*
* ADDITIONAL SPECIFICATIONS (ROUTINES FOR CHARACTER INPUT)
*
* trminput()
* Return the next input character (with its parity bit cleared
* if any). This value is a nonnegative int. Returns -1 if the
* input can't be read any more.
*
* trmavail()
* Return 1 if there is an input character immediately available,
* 0 if not. Return -1 if not implementable.
*
* trminterrupt()
* Return 1 if an interrupt has occurred since the last call to
* trminput or trmavail, 0 else. [Currently not implemented.]
*
* trmsuspend()
* When called in the proper environment (4BSD with job control
* enabled), suspends the editor, temporarily popping back to
* the calling shell. The caller should have called trmend()
* first, and must call trmstart again afterwards.
* BUG: there is a timing window where keyboard-generated
* signals (such as interrupt) can reach the program.
*/