/* * zle_refresh.c - screen update * * This file is part of zsh, the Z shell. * * Copyright (c) 1992-1997 Paul Falstad * All rights reserved. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and to distribute modified versions of this software for any * purpose, provided that the above copyright notice and the following * two paragraphs appear in all copies of this software. * * In no event shall Paul Falstad or the Zsh Development Group be liable * to any party for direct, indirect, special, incidental, or consequential * damages arising out of the use of this software and its documentation, * even if Paul Falstad and the Zsh Development Group have been advised of * the possibility of such damage. * * Paul Falstad and the Zsh Development Group specifically disclaim any * warranties, including, but not limited to, the implied warranties of * merchantability and fitness for a particular purpose. The software * provided hereunder is on an "as is" basis, and Paul Falstad and the * Zsh Development Group have no obligation to provide maintenance, * support, updates, enhancements, or modifications. * */ #include "zle.mdh" #include "zle_refresh.pro" /* Expanded prompts */ /**/ char *lpromptbuf, *rpromptbuf; /* Text attributes after displaying prompts */ /**/ unsigned pmpt_attr, rpmpt_attr; /* number of lines displayed */ /**/ mod_export int nlnct; /* Most lines of the buffer we've shown at once with the current list * * showing. == 0 if there is no list. == -1 if a new list has just * * been put on the screen. == -2 if zrefresh() needs to put up a new * * list. */ /**/ mod_export int showinglist; /* > 0 if a completion list is displayed below the prompt, * < 0 if a list is displayed above the prompt. */ /**/ mod_export int listshown; /* Length of last list displayed (if it is below the prompt). */ /**/ mod_export int lastlistlen; /* Non-zero if ALWAYS_LAST_PROMPT has been used, meaning that the * * screen below the buffer display should not be cleared by * * zrefresh(), but should be by trashzle(). */ /**/ mod_export int clearflag; /* Non-zero if zrefresh() should clear the list below the prompt. */ /**/ mod_export int clearlist; /* Zle in trashed state - updates may be subtly altered */ /**/ int trashedzle; /* * Information used by PREDISPLAY and POSTDISPLAY parameters which * add non-editable text to that being displayed. */ /**/ ZLE_STRING_T predisplay, postdisplay; /**/ int predisplaylen, postdisplaylen; #ifdef HAVE_SELECT /* cost of last update */ /**/ int cost; # define SELECT_ADD_COST(X) cost += X # define zputc(a) zwcputc(a), cost++ # define zwrite(a, b) zwcwrite(a, b), cost += (b * ZLE_CHAR_SIZE) #else # define SELECT_ADD_COST(X) # define zputc(a) zwcputc(a) # define zwrite(a, b) zwcwrite(a, b) #endif /**/ int zwcputc(ZLE_CHAR_T c) { #ifdef ZLE_UNICODE_SUPPORT char mbtmp[MB_CUR_MAX + 1]; mbstate_t mbstate; int i; wcrtomb(NULL, L'\0', &mbstate); if ((i = wcrtomb(mbtmp, c, &mbstate)) > 0) return fwrite(mbtmp, i, 1, shout); /* TODO conversion failed; what should we output? */ return 0; #else return fputc(c, shout); #endif } static int zwcwrite(ZLE_STRING_T s, size_t i) { #ifdef ZLE_UNICODE_SUPPORT size_t j; for (j = 0; j < i; j++) zwcputc(s[j]); return i; /* TODO something better for error indication */ #else return fwrite(s, i, 1, shout); #endif } /* Oct/Nov 94: some code savagely redesigned to fix several bugs - refreshline() & tc_rightcurs() majorly rewritten; zrefresh() fixed - I've put my fingers into just about every routine in here - any queries about updates to mason@primenet.com.au */ static ZLE_STRING_T *nbuf = NULL,/* new video buffer line-by-line char array */ *obuf = NULL; /* old video buffer line-by-line char array */ static int more_start, /* more text before start of screen? */ more_end, /* more stuff after end of screen? */ olnct, /* previous number of lines */ ovln, /* previous video cursor position line */ lpromptw, rpromptw, /* prompt widths on screen */ lpromptwof, /* left prompt width with real end position */ lprompth, /* lines taken up by the prompt */ rprompth, /* right prompt height */ vcs, vln, /* video cursor position column & line */ vmaxln, /* video maximum number of lines */ winw, winh, rwinh, /* window width & height */ winpos; /* singlelinezle: line's position in window */ /**/ void resetvideo(void) { int ln; static int lwinw = -1, lwinh = -1; /* last window width & height */ winw = columns; /* terminal width */ if (termflags & TERM_SHORT) winh = 1; else winh = (lines < 2) ? 24 : lines; rwinh = lines; /* keep the real number of lines */ winpos = vln = vmaxln = 0; if (lwinw != winw || lwinh != winh) { if (nbuf) { for (ln = 0; ln != lwinh; ln++) { zfree(nbuf[ln], (lwinw + 2) * sizeof(**nbuf)); zfree(obuf[ln], (lwinw + 2) * sizeof(**obuf)); } free(nbuf); free(obuf); } nbuf = (ZLE_STRING_T *)zshcalloc((winh + 1) * sizeof(*nbuf)); obuf = (ZLE_STRING_T *)zshcalloc((winh + 1) * sizeof(*obuf)); nbuf[0] = (ZLE_STRING_T)zalloc((winw + 2) * sizeof(**nbuf)); obuf[0] = (ZLE_STRING_T)zalloc((winw + 2) * sizeof(**obuf)); lwinw = winw; lwinh = winh; } for (ln = 0; ln != winh + 1; ln++) { if (nbuf[ln]) { nbuf[ln][0] = ZWC('\n'); nbuf[ln][1] = ZWC('\0'); } if (obuf[ln]) { obuf[ln][0] = ZWC('\n'); obuf[ln][1] = ZWC('\0'); } } /* TODO currently zsh core is not using widechars */ countprompt(lpromptbuf, &lpromptwof, &lprompth, 1); countprompt(rpromptbuf, &rpromptw, &rprompth, 0); if (lpromptwof != winw) lpromptw = lpromptwof; else { lpromptw = 0; lprompth++; } if (lpromptw) { ZS_memset(nbuf[0], ZWC(' '), lpromptw); ZS_memset(obuf[0], ZWC(' '), lpromptw); nbuf[0][lpromptw] = obuf[0][lpromptw] = ZWC('\0'); } vcs = lpromptw; olnct = nlnct = 0; if (showinglist > 0) showinglist = -2; trashedzle = 0; } /* * Nov 96: changed to single line scroll */ /**/ static void scrollwindow(int tline) { int t0; ZLE_STRING_T s; s = nbuf[tline]; for (t0 = tline; t0 < winh - 1; t0++) nbuf[t0] = nbuf[t0 + 1]; nbuf[winh - 1] = s; if (!tline) more_start = 1; return; } /* this is the messy part. */ /* this define belongs where it's used!!! */ #define nextline \ { \ *s = ZWC('\0'); \ if (ln != winh - 1) \ ln++; \ else { \ if (!canscroll) { \ if (nvln != -1 && nvln != winh - 1 \ && (numscrolls != onumscrolls - 1 \ || nvln <= winh / 2)) \ break; \ numscrolls++; \ canscroll = winh / 2; \ } \ canscroll--; \ scrollwindow(0); \ if (nvln != -1) \ nvln--; \ } \ if (!nbuf[ln]) \ nbuf[ln] = (ZLE_STRING_T)zalloc((winw + 2) * sizeof(**nbuf)); \ s = nbuf[ln]; \ sen = s + winw; \ } #define snextline \ { \ *s = ZWC('\0'); \ if (ln != winh - 1) \ ln++; \ else \ if (tosln > ln) { \ tosln--; \ if (nvln > 1) { \ scrollwindow(0); \ nvln--; \ } else \ more_end = 1; \ } else if (tosln > 2 && nvln > 1) { \ tosln--; \ if (tosln <= nvln) { \ scrollwindow(0); \ nvln--; \ } else { \ scrollwindow(tosln); \ more_end = 1; \ } \ } else { \ more_status = 1; \ scrollwindow(tosln + 1); \ } \ if (!nbuf[ln]) \ nbuf[ln] = (ZLE_STRING_T)zalloc((winw + 2) * sizeof(**nbuf)); \ s = nbuf[ln]; \ sen = s + winw; \ } static int cleareol, /* clear to end-of-line (if can't cleareod) */ clearf, /* alwayslastprompt used immediately before */ put_rpmpt, /* whether we should display right-prompt */ oput_rpmpt, /* whether displayed right-prompt last time */ oxtabs, /* oxtabs - tabs expand to spaces if set */ numscrolls, onumscrolls; /* TODO currently it assumes sceenwidth 1 for every character */ /**/ mod_export void zrefresh(void) { static int inlist; /* avoiding recursion */ int canscroll = 0, /* number of lines we are allowed to scroll */ ln = 0, /* current line we're working on */ more_status = 0, /* more stuff in status line */ nvcs = 0, nvln = -1, /* video cursor column and line */ t0 = -1, /* tmp */ tosln = 0; /* tmp in statusline stuff */ ZLE_STRING_T s, /* pointer into the video buffer */ sen, /* pointer to end of the video buffer (eol) */ t, /* pointer into the real buffer */ scs, /* pointer to cursor position in real buffer */ u; /* pointer for status line stuff */ ZLE_STRING_T tmpline, /* line with added pre/post text */ *qbuf; /* tmp */ int tmpcs, tmpll; /* ditto cursor position and line length */ int tmpalloced; /* flag to free tmpline when finished */ if (trashedzle) reexpandprompt(); /* If this is called from listmatches() (indirectly via trashzle()), and * * that was called from the end of zrefresh(), then we don't need to do * * anything. All this `inlist' code is actually unnecessary, but it * * improves speed a little in a common case. */ if (inlist) return; if (predisplaylen || postdisplaylen) { /* There is extra text to display at the start or end of the line */ tmpline = zalloc((zlell + predisplaylen + postdisplaylen)*sizeof(*tmpline)); if (predisplaylen) ZS_memcpy(tmpline, predisplay, predisplaylen); if (zlell) ZS_memcpy(tmpline+predisplaylen, zleline, zlell); if (postdisplaylen) ZS_memcpy(tmpline+predisplaylen+zlell, postdisplay, postdisplaylen); tmpcs = zlecs + predisplaylen; tmpll = predisplaylen + zlell + postdisplaylen; tmpalloced = 1; } else { tmpline = zleline; tmpcs = zlecs; tmpll = zlell; tmpalloced = 0; } if (clearlist && listshown > 0) { if (tccan(TCCLEAREOD)) { int ovln = vln, ovcs = vcs; ZLE_STRING_T nb = nbuf[vln]; nbuf[vln] = obuf[vln]; moveto(nlnct, 0); tcout(TCCLEAREOD); moveto(ovln, ovcs); nbuf[vln] = nb; } else { invalidatelist(); moveto(0, 0); clearflag = 0; resetneeded = 1; } listshown = lastlistlen = 0; if (showinglist != -2) showinglist = 0; } clearlist = 0; #ifdef HAVE_SELECT cost = 0; /* reset */ #endif /* Nov 96: I haven't checked how complete this is. sgtty stuff may or may not work */ #if defined(SGTABTYPE) oxtabs = ((SGTTYFLAG & SGTABTYPE) == SGTABTYPE); #else oxtabs = 0; #endif cleareol = 0; /* unset */ more_start = more_end = 0; /* unset */ if (isset(SINGLELINEZLE) || lines < 3 || (termflags & (TERM_NOUP | TERM_BAD | TERM_UNKNOWN))) termflags |= TERM_SHORT; else termflags &= ~TERM_SHORT; if (resetneeded) { onumscrolls = 0; zsetterm(); #ifdef TIOCGWINSZ if (winchanged) { moveto(0, 0); t0 = olnct; /* this is to clear extra lines even when */ winchanged = 0; /* the terminal cannot TCCLEAREOD */ listshown = 0; } #endif resetvideo(); resetneeded = 0; /* unset */ oput_rpmpt = 0; /* no right-prompt currently on screen */ /* we probably should only have explicitly set attributes */ tsetcap(TCALLATTRSOFF, 0); tsetcap(TCSTANDOUTEND, 0); tsetcap(TCUNDERLINEEND, 0); if (!clearflag) { if (tccan(TCCLEAREOD)) tcout(TCCLEAREOD); else cleareol = 1; /* request: clear to end of line */ if (listshown > 0) listshown = 0; } if (t0 > -1) olnct = (t0 < winh) ? t0 : winh; if (termflags & TERM_SHORT) vcs = 0; else if (!clearflag && lpromptbuf[0]) { zputs(lpromptbuf, shout); /* TODO convert to wide characters */ if (lpromptwof == winw) zputs("\n", shout); /* works with both hasam and !hasam */ } else { txtchange = pmpt_attr; if (txtchangeisset(TXTNOBOLDFACE)) tsetcap(TCALLATTRSOFF, 0); if (txtchangeisset(TXTNOSTANDOUT)) tsetcap(TCSTANDOUTEND, 0); if (txtchangeisset(TXTNOUNDERLINE)) tsetcap(TCUNDERLINEEND, 0); if (txtchangeisset(TXTBOLDFACE)) tsetcap(TCBOLDFACEBEG, 0); if (txtchangeisset(TXTSTANDOUT)) tsetcap(TCSTANDOUTBEG, 0); if (txtchangeisset(TXTUNDERLINE)) tsetcap(TCUNDERLINEBEG, 0); } if (clearflag) { zputc(ZWC('\r')); vcs = 0; moveto(0, lpromptw); } fflush(shout); clearf = clearflag; } else if (winw != columns || rwinh != lines) resetvideo(); /* now winw equals columns and winh equals lines width comparisons can be made with winw, height comparisons with winh */ if (termflags & TERM_SHORT) { singlerefresh(tmpline, tmpll, tmpcs); goto singlelineout; } if (tmpcs < 0) { #ifdef DEBUG fprintf(stderr, "BUG: negative cursor position\n"); fflush(stderr); #endif tmpcs = 0; } scs = tmpline + tmpcs; numscrolls = 0; /* first, we generate the video line buffers so we know what to put on the screen - also determine final cursor position (nvln, nvcs) */ /* Deemed necessary by PWS 1995/05/15 due to kill-line problems */ if (!*nbuf) *nbuf = (ZLE_STRING_T)zalloc((winw + 2) * sizeof(**nbuf)); s = nbuf[ln = 0] + lpromptw; t = tmpline; sen = *nbuf + winw; for (; t < tmpline+tmpll; t++) { if (t == scs) /* if cursor is here, remember it */ nvcs = s - (nbuf[nvln = ln]); if (*t == ZWC('\n')){ /* newline */ nbuf[ln][winw + 1] = ZWC('\0'); /* text not wrapped */ nextline } else if (*t == ZWC('\t')) { /* tab */ t0 = s - nbuf[ln]; if ((t0 | 7) + 1 >= winw) { nbuf[ln][winw + 1] = ZWC('\n'); /* text wrapped */ nextline } else do *s++ = ZWC(' '); while ((++t0) & 7); } else if (ZC_icntrl(*t)) { /* other control character */ *s++ = ZWC('^'); if (s == sen) { nbuf[ln][winw + 1] = ZWC('\n'); /* text wrapped */ nextline } #ifdef ZLE_UNICODE_SUPPORT *s++ = ((*t == 127) || (*t > 255)) ? ZWC('?') : (*t | ZWC('@')); #else *s++ = (*t == 127) ? ZWC('?') : (*t | ZWC('@')); #endif } else { /* normal character */ *s++ = *t; } if (s == sen) { nbuf[ln][winw + 1] = ZWC('\n'); /* text wrapped */ nextline } } /* if we're really on the next line, don't fake it; do everything properly */ if (t == scs && (nvcs = s - (nbuf[nvln = ln])) == winw) { nbuf[ln][winw + 1] = ZWC('\n'); /* text wrapped */ switch ('\0') { /* a sad hack to make the break */ case '\0': /* in nextline work */ nextline } *s = ZWC('\0'); nvcs = 0; nvln++; } if (t != tmpline + tmpll) more_end = 1; if (statusline) { tosln = ln + 1; nbuf[ln][winw + 1] = ZWC('\0'); /* text not wrapped */ snextline u = statusline; for (; u < statusline + statusll; u++) { if (ZC_icntrl(*u)) { /* simplified processing in the status line */ *s++ = ZWC('^'); if (s == sen) { nbuf[ln][winw + 1] = ZWC('\n'); /* text wrapped */ snextline } *s++ = (*u == 127) ? ZWC('?') : (*u | ZWC('@')); } else *s++ = *u; if (s == sen) { nbuf[ln][winw + 1] = ZWC('\n'); /* text wrapped */ snextline } } if (s == sen) snextline } *s = ZWC('\0'); /* insert <.... at end of last line if there is more text past end of screen */ if (more_end) { if (!statusline) tosln = winh; s = nbuf[tosln - 1]; sen = s + winw - 7; for (; s < sen; s++) { if (*s == ZWC('\0')) { for (; s < sen; ) *s++ = ZWC(' '); break; } } ZS_strncpy(sen, ZWC(" <.... "), 7); nbuf[tosln - 1][winw] = nbuf[tosln - 1][winw + 1] = ZWC('\0'); } /* insert <....> at end of first status line if status is too big */ if (more_status) { s = nbuf[tosln]; sen = s + winw - 8; for (; s < sen; s++) { if (*s == ZWC('\0')) { for (; s < sen; ) *s++ = ZWC(' '); break; } } ZS_strncpy(sen, ZWC(" <....> "), 8); nbuf[tosln][winw] = nbuf[tosln][winw + 1] = ZWC('\0'); } nlnct = ln + 1; for (ln = nlnct; ln < winh; ln++) zfree(nbuf[ln], (winw + 2) * sizeof(**nbuf)), nbuf[ln] = NULL; /* determine whether the right-prompt exists and can fit on the screen */ if (!more_start) { if (trashedzle && opts[TRANSIENTRPROMPT]) put_rpmpt = 0; else /* TODO (r)promptbuf will be widechar */ put_rpmpt = rprompth == 1 && rpromptbuf[0] && !strchr(rpromptbuf, '\t') && (int)ZS_strlen(nbuf[0]) + rpromptw < winw - 1; } else { /* insert >.... on first line if there is more text before start of screen */ memset(nbuf[0], ZWC(' '), lpromptw); t0 = winw - lpromptw; t0 = t0 > 5 ? 5 : t0; ZS_strncpy(nbuf[0] + lpromptw, ZWC(">...."), t0); ZS_memset(nbuf[0] + lpromptw + t0, ZWC(' '), winw - t0 - lpromptw); nbuf[0][winw] = nbuf[0][winw + 1] = ZWC('\0'); } for (ln = 0; ln < nlnct; ln++) { /* if we have more lines than last time, clear the newly-used lines */ if (ln >= olnct) cleareol = 1; /* if old line and new line are different, see if we can insert/delete a line to speed up update */ if (!clearf && ln > 0 && ln < olnct - 1 && !(hasam && vcs == winw) && nbuf[ln] && obuf[ln] && ZS_strncmp(nbuf[ln], obuf[ln], 16)) { if (tccan(TCDELLINE) && obuf[ln + 1] && obuf[ln + 1][0] && nbuf[ln] && !ZS_strncmp(nbuf[ln], obuf[ln + 1], 16)) { moveto(ln, 0); tcout(TCDELLINE); zfree(obuf[ln], (winw + 2) * sizeof(**obuf)); for (t0 = ln; t0 != olnct; t0++) obuf[t0] = obuf[t0 + 1]; obuf[--olnct] = NULL; } /* don't try to insert a line if olnct = vmaxln (vmaxln is the number of lines that have been displayed by this routine) so that we don't go off the end of the screen. */ else if (tccan(TCINSLINE) && olnct < vmaxln && nbuf[ln + 1] && obuf[ln] && !ZS_strncmp(nbuf[ln + 1], obuf[ln], 16)) { moveto(ln, 0); tcout(TCINSLINE); for (t0 = olnct; t0 != ln; t0--) obuf[t0] = obuf[t0 - 1]; obuf[ln] = NULL; olnct++; } } /* update the single line */ refreshline(ln); /* output the right-prompt if appropriate */ if (put_rpmpt && !ln && !oput_rpmpt) { moveto(0, winw - 1 - rpromptw); /* TODO it will be wide char at some point */ zputs(rpromptbuf, shout); vcs = winw - 1; /* reset character attributes to that set by the main prompt */ txtchange = pmpt_attr; if (txtchangeisset(TXTNOBOLDFACE) && (rpmpt_attr & TXTBOLDFACE)) tsetcap(TCALLATTRSOFF, 0); if (txtchangeisset(TXTNOSTANDOUT) && (rpmpt_attr & TXTSTANDOUT)) tsetcap(TCSTANDOUTEND, 0); if (txtchangeisset(TXTNOUNDERLINE) && (rpmpt_attr & TXTUNDERLINE)) tsetcap(TCUNDERLINEEND, 0); if (txtchangeisset(TXTBOLDFACE) && (rpmpt_attr & TXTNOBOLDFACE)) tsetcap(TCBOLDFACEBEG, 0); if (txtchangeisset(TXTSTANDOUT) && (rpmpt_attr & TXTNOSTANDOUT)) tsetcap(TCSTANDOUTBEG, 0); if (txtchangeisset(TXTUNDERLINE) && (rpmpt_attr & TXTNOUNDERLINE)) tsetcap(TCUNDERLINEBEG, 0); } } /* if old buffer had extra lines, set them to be cleared and refresh them individually */ if (olnct > nlnct) { cleareol = 1; for (ln = nlnct; ln < olnct; ln++) refreshline(ln); } /* reset character attributes */ if (clearf && postedit) { if ((txtchange = pmpt_attr ? pmpt_attr : rpmpt_attr)) { if (txtchangeisset(TXTNOBOLDFACE)) tsetcap(TCALLATTRSOFF, 0); if (txtchangeisset(TXTNOSTANDOUT)) tsetcap(TCSTANDOUTEND, 0); if (txtchangeisset(TXTNOUNDERLINE)) tsetcap(TCUNDERLINEEND, 0); if (txtchangeisset(TXTBOLDFACE)) tsetcap(TCBOLDFACEBEG, 0); if (txtchangeisset(TXTSTANDOUT)) tsetcap(TCSTANDOUTBEG, 0); if (txtchangeisset(TXTUNDERLINE)) tsetcap(TCUNDERLINEBEG, 0); } } clearf = 0; oput_rpmpt = put_rpmpt; /* move to the new cursor position */ moveto(nvln, nvcs); /* swap old and new buffers - better than freeing/allocating every time */ qbuf = nbuf; nbuf = obuf; obuf = qbuf; /* store current values so we can use them next time */ ovln = nvln; olnct = nlnct; onumscrolls = numscrolls; if (nlnct > vmaxln) vmaxln = nlnct; singlelineout: fflush(shout); /* make sure everything is written out */ if (tmpalloced) zfree(tmpline, tmpll); /* if we have a new list showing, note it; if part of the list has been overwritten, redisplay it. */ if (showinglist == -2 || (showinglist > 0 && showinglist < nlnct)) { inlist = 1; listmatches(); inlist = 0; zrefresh(); } if (showinglist == -1) showinglist = nlnct; } #define tcinscost(X) (tccan(TCMULTINS) ? tclen[TCMULTINS] : (X)*tclen[TCINS]) #define tcdelcost(X) (tccan(TCMULTDEL) ? tclen[TCMULTDEL] : (X)*tclen[TCDEL]) #define tc_delchars(X) (void) tcmultout(TCDEL, TCMULTDEL, (X)) #define tc_inschars(X) (void) tcmultout(TCINS, TCMULTINS, (X)) #define tc_upcurs(X) (void) tcmultout(TCUP, TCMULTUP, (X)) #define tc_leftcurs(X) (void) tcmultout(TCLEFT, TCMULTLEFT, (X)) /* TODO remove it when pfxlen is fixed */ static int wpfxlen(ZLE_STRING_T s, ZLE_STRING_T t) { int i = 0; while (*s && *s == *t) s++, t++, i++; return i; } /* refresh one line, using whatever speed-up tricks are provided by the tty */ /**/ static void refreshline(int ln) { ZLE_STRING_T nl, ol, p1; /* line buffer pointers */ int ccs = 0, /* temporary count for cursor position */ char_ins = 0, /* number of characters inserted/deleted */ col_cleareol, /* clear to end-of-line from this column */ i, j, /* tmp */ ins_last, /* insert pushed last character off line */ nllen, ollen, /* new and old line buffer lengths */ rnllen; /* real new line buffer length */ /* 0: setup */ nl = nbuf[ln]; rnllen = nllen = nl ? ZS_strlen(nl) : 0; ol = obuf[ln] ? obuf[ln] : ZWS(""); ollen = ZS_strlen(ol); /* optimisation: can easily happen for clearing old lines. If the terminal has the capability, then this is the easiest way to skip unnecessary stuff */ if (cleareol && !nllen && !(hasam && ln < nlnct - 1) && tccan(TCCLEAREOL)) { moveto(ln, 0); tcout(TCCLEAREOL); return; } /* 1: pad out the new buffer with spaces to contain _all_ of the characters which need to be written. do this now to allow some pre-processing */ if (cleareol /* request to clear to end of line */ || (!nllen && (ln != 0 || !put_rpmpt)) /* no line buffer given */ || (ln == 0 && (put_rpmpt != oput_rpmpt))) { /* prompt changed */ p1 = zhalloc((winw + 2) * sizeof(*p1)); if (nllen) ZS_strncpy(p1, nl, nllen); ZS_memset(p1 + nllen, ZWC(' '), winw - nllen); p1[winw] = ZWC('\0'); p1[winw + 1] = (nllen < winw) ? ZWC('\0') : nl[winw + 1]; if (ln && nbuf[ln]) ZS_memcpy(nl, p1, winw + 2); /* next time obuf will be up-to-date */ else nl = p1; /* don't keep the padding for prompt line */ nllen = winw; } else if (ollen > nllen) { /* make new line at least as long as old */ p1 = zhalloc((ollen + 1) * sizeof(*p1)); ZS_strncpy(p1, nl, nllen); ZS_memset(p1 + nllen, ZWC(' '), ollen - nllen); p1[ollen] = ZWC('\0'); nl = p1; nllen = ollen; } /* 2: see if we can clear to end-of-line, and if it's faster, work out where to do it from - we can normally only do so if there's no right-prompt. With automatic margins, we shouldn't do it if there is another line, in case it messes up cut and paste. */ if (hasam && ln < nlnct - 1 && rnllen == winw) col_cleareol = -2; /* clearing eol would be evil so don't */ else { col_cleareol = -1; if (tccan(TCCLEAREOL) && (nllen == winw || put_rpmpt != oput_rpmpt)) { for (i = nllen; i && nl[i - 1] == ZWC(' '); i--); for (j = ollen; j && ol[j - 1] == ZWC(' '); j--); if ((j > i + tclen[TCCLEAREOL]) /* new buf has enough spaces */ || (nllen == winw && nl[winw - 1] == ZWC(' '))) col_cleareol = i; } } /* 2b: first a new trick for automargin niceness - good for cut and paste */ if (hasam && vcs == winw) { if (nbuf[vln] && nbuf[vln][vcs + 1] == ZWC('\n')) { vln++, vcs = 1; if (nbuf[vln] && *nbuf[vln]) zputc(*nbuf[vln]); else zputc(ZWC(' ')); /* I don't think this should happen */ if (ln == vln) { /* better safe than sorry */ nl++; if (*ol) ol++; ccs = 1; } /* else hmmm... I wonder what happened */ } else { vln++, vcs = 0; zputc(ZWC('\n')); } } ins_last = 0; /* 2c: if we're on the first line, start checking at the end of the prompt; we shouldn't be doing anything within the prompt */ if (ln == 0 && lpromptw) { i = lpromptw - ccs; j = ZS_strlen(ol); nl += i; ol += (i > j ? j : i); /* if ol is too short, point it to '\0' */ ccs = lpromptw; } /* 3: main display loop - write out the buffer using whatever tricks we can */ for (;;) { if (*nl && *ol && nl[1] == ol[1]) /* skip only if second chars match */ /* skip past all matching characters */ for (; *nl && (*nl == *ol); nl++, ol++, ccs++) ; if (!*nl) { if (ccs == winw && hasam && char_ins > 0 && ins_last && vcs != winw) { nl--; /* we can assume we can go back here */ moveto(ln, winw - 1); zputc(*nl); vcs++; return; /* write last character in line */ } if ((char_ins <= 0) || (ccs >= winw)) /* written everything */ return; if (tccan(TCCLEAREOL) && (char_ins >= tclen[TCCLEAREOL]) && col_cleareol != -2) /* we've got junk on the right yet to clear */ col_cleareol = 0; /* force a clear to end of line */ } moveto(ln, ccs); /* move to where we do all output from */ /* if we can finish quickly, do so */ if ((col_cleareol >= 0) && (ccs >= col_cleareol)) { tcout(TCCLEAREOL); return; } /* we've written out the new but yet to clear rubbish due to inserts */ if (!*nl) { i = (winw - ccs < char_ins) ? (winw - ccs) : char_ins; if (tccan(TCDEL) && (tcdelcost(i) <= i + 1)) tc_delchars(i); else { vcs += i; while (i-- > 0) zputc(ZWC(' ')); } return; } /* if we've reached the end of the old buffer, then there are few tricks we can do, so we just dump out what we must and clear if we can */ if (!*ol) { i = (col_cleareol >= 0) ? col_cleareol : nllen; i -= vcs; zwrite(nl, i); vcs += i; if (col_cleareol >= 0) tcout(TCCLEAREOL); return; } /* inserting & deleting chars: we can if there's no right-prompt */ if ((ln || !put_rpmpt || !oput_rpmpt) && (nl[1] && ol[1] && nl[1] != ol[1])) { /* deleting characters - see if we can find a match series that makes it cheaper to delete intermediate characters eg. oldline: hifoobar \ hopefully cheaper here to delete two newline: foobar / characters, then we have six matches */ /* TODO replace wpfxlen back with pfxlen when the latter is fixed */ if (tccan(TCDEL)) { for (i = 1; *(ol + i); i++) if (tcdelcost(i) < wpfxlen(ol + i, nl)) { tc_delchars(i); ol += i; char_ins -= i; i = 0; break; } if (!i) continue; } /* inserting characters - characters pushed off the right should be annihilated, but we don't do this if we're on the last line lest undesired scrolling occurs due to `illegal' characters on screen */ if (tccan(TCINS) && (vln != lines - 1)) { /* not on last line */ for (i = 1; *(nl + i); i++) if (tcinscost(i) < wpfxlen(nl + i, ol)) { tc_inschars(i); zwrite(nl, i); nl += i; char_ins += i; ccs = (vcs += i); /* if we've pushed off the right, truncate oldline */ for (i = 0; *(ol + i) && i < winw - ccs; i++); if (i == winw - ccs) { *(ol + i) = ZWC('\0'); ins_last = 1; } i = 0; break; } if (!i) continue; } } /* we can't do any fancy tricks, so just dump the single character and keep on trying */ zputc(*nl); nl++, ol++; ccs++, vcs++; } } /* move the cursor to line ln (relative to the prompt line), absolute column cl; update vln, vcs - video line and column */ /**/ void moveto(int ln, int cl) { ZLE_CHAR_T c; if (vcs == winw) { vln++, vcs = 0; if (!hasam) { zputc(ZWC('\r')); zputc(ZWC('\n')); } else { if ((vln < nlnct) && nbuf[vln] && *nbuf[vln]) c = *nbuf[vln]; else c = ZWC(' '); zputc(c); zputc(ZWC('\r')); if ((vln < olnct) && obuf[vln] && *obuf[vln]) *obuf[vln] = c; } } if (ln == vln && cl == vcs) return; /* move up */ if (ln < vln) { tc_upcurs(vln - ln); vln = ln; } /* move down; if we might go off the end of the screen, use newlines instead of TCDOWN */ while (ln > vln) { if (vln < vmaxln - 1) { if (ln > vmaxln - 1) { if (tc_downcurs(vmaxln - 1 - vln)) vcs = 0; vln = vmaxln - 1; } else { if (tc_downcurs(ln - vln)) vcs = 0; vln = ln; continue; } } zputc(ZWC('\r')), vcs = 0; /* safety precaution */ while (ln > vln) { zputc(ZWC('\n')); vln++; } } if (cl != vcs) singmoveto(cl); } /**/ mod_export int tcmultout(int cap, int multcap, int ct) { if (tccan(multcap) && (!tccan(cap) || tclen[multcap] <= tclen[cap] * ct)) { tcoutarg(multcap, ct); return 1; } else if (tccan(cap)) { while (ct--) tcout(cap); return 1; } return 0; } /* ct: number of characters to move across */ /**/ static void tc_rightcurs(int ct) { int cl, /* ``desired'' absolute horizontal position */ i = vcs, /* cursor position after initial movements */ j; ZLE_STRING_T t; cl = ct + vcs; /* do a multright if we can - it's the most reliable */ if (tccan(TCMULTRIGHT)) { tcoutarg(TCMULTRIGHT, ct); return; } /* do an absolute horizontal position if we can */ if (tccan(TCHORIZPOS)) { tcoutarg(TCHORIZPOS, cl); return; } /* XXX: should really check "it" in termcap and use / and % */ /* try tabs if tabs are non destructive and multright is not possible */ if (!oxtabs && tccan(TCNEXTTAB) && ((vcs | 7) < cl)) { i = (vcs | 7) + 1; tcout(TCNEXTTAB); for ( ; i + 8 <= cl; i += 8) tcout(TCNEXTTAB); if ((ct = cl - i) == 0) /* number of chars still to move across */ return; } /* otherwise _carefully_ write the contents of the video buffer. if we're anywhere in the prompt, goto the left column and write the whole prompt out unless ztrlen(lpromptbuf) == lpromptw : we can cheat then */ if (vln == 0 && i < lpromptw && !(termflags & TERM_SHORT)) { if ((int)strlen(lpromptbuf) == lpromptw) fputs(lpromptbuf + i, shout); else if (tccan(TCRIGHT) && (tclen[TCRIGHT] * ct <= ztrlen(lpromptbuf))) /* it is cheaper to send TCRIGHT than reprint the whole prompt */ for (ct = lpromptw - i; ct--; ) tcout(TCRIGHT); else { if (i != 0) zputc('\r'); tc_upcurs(lprompth - 1); zputs(lpromptbuf, shout); /* TODO wide character */ if (lpromptwof == winw) zputs("\n", shout); /* works with both hasam and !hasam */ } i = lpromptw; ct = cl - i; } if (nbuf[vln]) { for (j = 0, t = nbuf[vln]; *t && (j < i); j++, t++); if (j == i) for ( ; *t && ct; ct--, t++) zputc(*t); } while (ct--) zputc(ZWC(' ')); /* not my fault your terminal can't go right */ } /**/ mod_export int tc_downcurs(int ct) { int ret = 0; if (ct && !tcmultout(TCDOWN, TCMULTDOWN, ct)) { while (ct--) zputc(ZWC('\n')); zputc(ZWC('\r')), ret = -1; } return ret; } /**/ mod_export void tcout(int cap) { tputs(tcstr[cap], 1, putshout); SELECT_ADD_COST(tclen[cap]); } /**/ static void tcoutarg(int cap, int arg) { char *result; result = tgoto(tcstr[cap], arg, arg); tputs(result, 1, putshout); SELECT_ADD_COST(strlen(result)); } /**/ mod_export int clearscreen(UNUSED(char **args)) { tcout(TCCLEARSCREEN); resetneeded = 1; clearflag = 0; return 0; } /**/ mod_export int redisplay(UNUSED(char **args)) { moveto(0, 0); zputc(ZWC('\r')); /* extra care */ tc_upcurs(lprompth - 1); resetneeded = 1; clearflag = 0; return 0; } /**/ static void singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs) { ZLE_STRING_T vbuf, vp, /* video buffer and pointer */ *qbuf, /* tmp */ refreshop = *obuf; /* pointer to old video buffer */ int t0, /* tmp */ vsiz, /* size of new video buffer */ nvcs = 0; /* new video cursor column */ nlnct = 1; /* generate the new line buffer completely */ for (vsiz = 1 + lpromptw, t0 = 0; t0 != tmpll; t0++, vsiz++) if (tmpline[t0] == ZWC('\t')) vsiz = (vsiz | 7) + 1; else if (ZC_icntrl(tmpline[t0])) vsiz++; vbuf = (ZLE_STRING_T)zalloc(vsiz * sizeof(*vbuf)); if (tmpcs < 0) { #ifdef DEBUG fprintf(stderr, "BUG: negative cursor position\n"); fflush(stderr); #endif tmpcs = 0; } /* only use last part of prompt */ /* TODO convert prompt to wide char */ #ifdef ZLE_UNICODE_SUPPORT t0 = mbtowc(vbuf, strchr(lpromptbuf, 0) - lpromptw, lpromptw); if (t0 >= 0) { vbuf[t0] = ZWC('\0'); vp = vbuf + t0; } else /* FIXME What to do? */ ; #else memcpy(vbuf, strchr(lpromptbuf, 0) - lpromptw, lpromptw); vbuf[lpromptw] = '\0'; vp = vbuf + lpromptw; #endif for (t0 = 0; t0 < tmpll; t0++) { if (tmpline[t0] == ZWC('\t')) for (*vp++ = ZWC(' '); (vp - vbuf) & 7; ) *vp++ = ZWC(' '); else if (tmpline[t0] == ZWC('\n')) { *vp++ = ZWC('\\'); *vp++ = ZWC('n'); } else if (ZC_icntrl(tmpline[t0])) { ZLE_CHAR_T t = tmpline[++t0]; *vp++ = ZWC('^'); /* FIXME is it portable? */ *vp++ = ((t == 127) || (t > 255)) ? ZWC('?') : (t | ZWC('@')); } else *vp++ = tmpline[t0]; if (t0 == tmpcs) nvcs = vp - vbuf - 1; } if (t0 == tmpcs) nvcs = vp - vbuf; *vp = ZWC('\0'); /* determine which part of the new line buffer we want for the display */ if ((winpos && nvcs < winpos + 1) || (nvcs > winpos + winw - 2)) { if ((winpos = nvcs - ((winw - hasam) / 2)) < 0) winpos = 0; } if (winpos) vbuf[winpos] = ZWC('<'); /* line continues to the left */ if ((int)ZS_strlen(vbuf + winpos) > (winw - hasam)) { vbuf[winpos + winw - hasam - 1] = ZWC('>'); /* line continues to right */ vbuf[winpos + winw - hasam] = ZWC('\0'); } ZS_strcpy(nbuf[0], vbuf + winpos); zfree(vbuf, vsiz * sizeof(*vbuf)); nvcs -= winpos; /* display the `visable' portion of the line buffer */ for (t0 = 0, vp = *nbuf;;) { /* skip past all matching characters */ for (; *vp && *vp == *refreshop; t0++, vp++, refreshop++) ; if (!*vp && !*refreshop) break; singmoveto(t0); /* move to where we do all output from */ if (!*refreshop) { if ((t0 = ZS_strlen(vp))) zwrite(vp, t0); vcs += t0; break; } if (!*vp) { if (tccan(TCCLEAREOL)) tcout(TCCLEAREOL); else for (; *refreshop++; vcs++) zputc(ZWC(' ')); break; } zputc(*vp); vcs++, t0++; vp++, refreshop++; } /* move to the new cursor position */ singmoveto(nvcs); qbuf = nbuf; nbuf = obuf; obuf = qbuf; } /**/ static void singmoveto(int pos) { if (pos == vcs) return; /* choose cheapest movements for ttys without multiple movement capabilities - do this now because it's easier (to code) */ if ((!tccan(TCMULTLEFT) || pos == 0) && (pos <= vcs / 2)) { zputc(ZWC('\r')); vcs = 0; } if (pos < vcs) tc_leftcurs(vcs - pos); else if (pos > vcs) tc_rightcurs(pos - vcs); vcs = pos; }