|
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. */