/*- * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static char sccsid[] = "@(#)svi_refresh.c 8.1 (Berkeley) 6/9/93"; #endif /* not lint */ #include <sys/types.h> #include <ctype.h> #include <curses.h> #include <string.h> #include "vi.h" #include "svi_screen.h" static int svi_modeline __P((SCR *, EXF *)); static int svi_msgflush __P((SCR *)); /* * svi_refresh -- * This is the guts of the vi curses screen code. The idea is that * the SCR structure passed in contains the new coordinates of the * screen. What makes this hard is that we don't know how big * characters are, doing input can put the cursor in illegal places, * and we're frantically trying to avoid repainting unless it's * absolutely necessary. If you change this code, you'd better know * what you're doing. It's subtle and quick to anger. */ int svi_refresh(sp, ep) SCR *sp; EXF *ep; { CHNAME *cname; SMAP *smp, tmp; recno_t lastline, lcnt; size_t cwtotal, cnt, len, x, y; int ch; char *p; #define LNO sp->lno #define OLNO sp->olno #define CNO sp->cno #define OCNO sp->ocno #define SCNO sp->sc_col /* * 1: Resize the window. * * Notice that a resize is requested, and set up everything so that * the file gets reinitialized. Done here, instead of in the vi * loop because there may be other initialization that other screens * need to do. The actual changing of the row/column values was done * by calling the ex options code which put them into the environment, * which is used by curses. Stupid, but ugly. */ if (F_ISSET(sp, S_RESIZE)) { F_SET(sp, S_SSWITCH | S_REFORMAT | S_REDRAW); return (0); } /* * 2: Reformat the lines. * * If the lines themselves have changed (:set list, for example), * fill in the map from scratch. Adjust the screen that's being * displayed if the leftright flag is set. */ if (F_ISSET(sp, S_REFORMAT)) { if (svi_sm_fill(sp, ep, HMAP->lno, P_TOP)) return (1); if (O_ISSET(sp, O_LEFTRIGHT) && (cnt = svi_screens(sp, ep, LNO, &CNO)) != 1) for (smp = HMAP; smp <= TMAP; ++smp) smp->off = cnt; F_CLR(sp, S_REFORMAT); F_SET(sp, S_REDRAW); } /* * 3: Line movement. * * Line changes can cause the top line to change as well. As * before, if the movement is large, the screen is repainted. * * 3a: Tiny screens. * * Tiny screens cannot be permitted into the "scrolling" parts of * the smap code for two reasons. If the screen size is 1 line, * HMAP == TMAP and the code will quickly drop core. If the screen * size is 2, none of the divisions by 2 will work, and scrolling * won't work. In fact, because no line change will be less than * HALFSCREEN(sp), we always ending up "filling" the map, with a * P_MIDDLE flag, which isn't what the user wanted. Tiny screens * can go into the "fill" portions of the smap code, no problem. */ if (sp->t_rows <= 2) { if (LNO < HMAP->lno) { if (svi_sm_fill(sp, ep, LNO, P_TOP)) return (1); } else if (LNO > TMAP->lno) if (svi_sm_fill(sp, ep, LNO, P_BOTTOM)) return (1); if (sp->t_rows == 1) { HMAP->off = svi_screens(sp, ep, LNO, &CNO); goto paint; } F_SET(sp, S_REDRAW); goto adjust; } /* * 3b: Line down. */ if (LNO >= HMAP->lno) { if (LNO <= TMAP->lno) goto adjust; /* * If less than half a screen away, scroll down until the * line is on the screen. */ lcnt = svi_sm_nlines(sp, ep, TMAP, LNO, HALFSCREEN(sp)); if (lcnt < HALFSCREEN(sp)) { while (lcnt--) if (svi_sm_1up(sp, ep)) return (1); goto adjust; } /* * If less than a full screen from the bottom of the file, put * the last line of the file on the bottom of the screen. The * calculation is safe because we know there's at least one * full screen of lines, otherwise couldn't have gotten here. */ if (file_lline(sp, ep, &lastline)) return (1); tmp.lno = LNO; tmp.off = 1; lcnt = svi_sm_nlines(sp, ep, &tmp, lastline, sp->t_rows); if (lcnt < sp->t_rows) { if (svi_sm_fill(sp, ep, lastline, P_BOTTOM)) return (1); F_SET(sp, S_REDRAW); goto adjust; } /* * If more than a full screen from the last line of the file, * put the new line in the middle of the screen. */ goto middle; } /* * 3c: Line up. * * If less than half a screen away, scroll up until the line is * the first line on the screen. */ lcnt = svi_sm_nlines(sp, ep, HMAP, LNO, HALFSCREEN(sp)); if (lcnt < HALFSCREEN(sp)) { while (lcnt--) if (svi_sm_1down(sp, ep)) return (1); goto adjust; } /* * If less than half a screen from the top of the file, put the first * line of the file at the top of the screen. Otherwise, put the line * in the middle of the screen. */ tmp.lno = 1; tmp.off = 1; lcnt = svi_sm_nlines(sp, ep, &tmp, LNO, HALFSCREEN(sp)); if (lcnt < HALFSCREEN(sp)) { if (svi_sm_fill(sp, ep, 1, P_TOP)) return (1); } else middle: if (svi_sm_fill(sp, ep, LNO, P_MIDDLE)) return (1); F_SET(sp, S_REDRAW); /* * At this point we know part of the line is on the screen. Since * scrolling is done using logical lines, not physical, all of the * line may not be on the screen. While that's not necessarily bad, * if the part the cursor is on isn't there, we're going to lose. * This can be tricky; if the line covers the entire screen, lno * may be the same as both ends of the map. This isn't a problem * for left-right scrolling, the cursor movement code handles the * problem. * * XXX * There's a real performance issue here if editing *really* long * lines. This gets to the right spot by scrolling, and, in a * binary, by scrolling hundreds of lines. */ adjust: if (!O_ISSET(sp, O_LEFTRIGHT)) if (LNO == HMAP->lno) { cnt = svi_screens(sp, ep, LNO, &CNO); if (cnt < HMAP->off) while (cnt < HMAP->off) if (svi_sm_1down(sp, ep)) return (1); } else if (LNO == TMAP->lno) { cnt = svi_screens(sp, ep, LNO, &CNO); if (cnt > TMAP->off) while (cnt > TMAP->off) if (svi_sm_1up(sp, ep)) return (1); } /* If the screen needs to be repainted, skip cursor optimization. */ if (F_ISSET(sp, S_REDRAW)) goto paint; /* * 4: Cursor movements. * * Decide cursor position. If the line has changed, the cursor has * moved over a tab, or don't know where the cursor was, reparse the * line. Note, if we think that the cursor "hasn't moved", reparse * the line. This is 'cause if it hasn't moved, we've almost always * lost track of it. * * Otherwise, we've just moved over fixed-width characters, and can * calculate the left/right scrolling and cursor movement without * reparsing the line. Note that we don't know which (if any) of * the characters between the old and new cursor positions changed. * * XXX * With some work, it should be possible to handle tabs quickly, at * least in obvious situations, like moving right and encountering * a tab, without reparsing the whole line. */ /* * If the line we're working with has changed, reparse. */ if (F_ISSET(sp, S_CUR_INVALID) || LNO != OLNO) { F_CLR(sp, S_CUR_INVALID); goto slow; } /* * Otherwise, if nothing's changed, go fast. The one exception is * that a single character or no characters are both column 0, and, * if the single character required multiple screen columns, there * may have still been movement. */ if (CNO == OCNO) { if (CNO == 0) goto slow; goto fast; } /* * Get the current line. If this fails, we either have an empty * file and can just repaint, or there's a real problem. This * isn't a performance issue because there aren't any ways to get * here repeatedly. */ if ((p = file_gline(sp, ep, LNO, &len)) == NULL) { if (file_lline(sp, ep, &lastline)) return (1); if (lastline == 0) goto slow; GETLINE_ERR(sp, LNO); return (1); } /* This is just a test. */ #ifdef DEBUG if (CNO >= len && len != 0) { msgq(sp, M_ERR, "Error: %s/%d: cno (%u) >= len (%u)", tail(__FILE__), __LINE__, CNO, len); return (1); } #endif /* * The basic scheme here is to look at the characters in between * the old and new positions and decide how big they are on the * screen, and therefore, how many screen positions to move. */ cname = sp->cname; if (CNO < OCNO) { /* * 4a: Cursor moved left. * * Point to the old character. The old cursor position can * be past EOL if, for example, we just deleted the rest of * the line. In this case, since we don't know the width of * the characters we traversed, we have to do it slowly. */ p += OCNO; cnt = (OCNO - CNO) + 1; if (OCNO >= len) goto slow; /* * Count up the widths of the characters. If it's a tab * character, go do it the the slow way. */ for (cwtotal = 0; cnt--; cwtotal += cname[ch].len) if ((ch = *(u_char *)p--) == '\t') goto slow; /* * Decrement the screen cursor by the total width of the * characters minus 1. */ cwtotal -= 1; /* * If we're moving left, and there's a wide character in the * current position, go to the end of the character. */ if (cname[ch].len > 1) cwtotal -= cname[ch].len - 1; /* * If the new column moved us out of the current screen, * calculate a new screen. */ if (SCNO < cwtotal) { if (O_ISSET(sp, O_LEFTRIGHT)) { for (smp = HMAP; smp <= TMAP; ++smp) --smp->off; goto paint; } goto slow; } SCNO -= cwtotal; } else { /* * 4b: Cursor moved right. * * Point to the first character to the right. */ p += OCNO + 1; cnt = CNO - OCNO; /* * Count up the widths of the characters. If it's a tab * character, go do it the the slow way. */ for (cwtotal = 0; cnt--; cwtotal += cname[ch].len) if ((ch = *(u_char *)p++) == '\t') goto slow; /* * Increment the screen cursor by the total width of the * characters. */ SCNO += cwtotal; /* * If the new column moved us out of the current screen, * calculate a new screen. */ if (SCNO >= SCREEN_COLS(sp)) { if (O_ISSET(sp, O_LEFTRIGHT)) { SCNO -= SCREEN_COLS(sp); for (smp = HMAP; smp <= TMAP; ++smp) ++smp->off; goto paint; } goto slow; } } fast: getyx(stdscr, y, x); /* Just move the cursor. */ y -= sp->s_off; /* Correct for split screen. */ goto update; slow: /* Find the current line in the map. */ for (smp = HMAP; smp->lno != LNO; ++smp); /* * If doing left-right scrolling, and the cursor movement has * changed the screen being displayed, fix it. */ if (!ISINFOLINE(sp, smp) && O_ISSET(sp, O_LEFTRIGHT)) { cnt = svi_screens(sp, ep, LNO, &CNO) % SCREEN_COLS(sp); if (cnt != HMAP->off) { for (smp = HMAP; smp <= TMAP; ++smp) smp->off = cnt; goto paint; } } /* Update all of the screen lines for this file line. */ for (; smp <= TMAP && smp->lno == LNO; ++smp) if (svi_line(sp, ep, smp, NULL, 0, &y, &SCNO)) return (1); /* Not too bad, move the cursor. */ goto update; /* Lost big, do what you have to do. */ paint: for (smp = HMAP; smp <= TMAP; ++smp) if (svi_line(sp, ep, smp, NULL, 0, &y, &SCNO)) return (1); F_CLR(sp, S_REDRAW); update: /* Ring the bell if scheduled. */ if (F_ISSET(sp, S_BELLSCHED)) svi_bell(sp); /* * If the bottom line isn't in use by vi, display any * messages or paint the mode line. */ if (!F_ISSET(&sp->bhdr, HDR_INUSE)) if (sp->msgp != NULL && !F_ISSET(sp->msgp, M_EMPTY)) svi_msgflush(sp); else if (!F_ISSET(sp, S_UPDATE_MODE)) { svi_modeline(sp, ep); } /* Place the cursor. */ MOVE(sp, y, SCNO); /* Update saved information. */ OCNO = CNO; OLNO = LNO; sp->sc_row = y; /* Refresh the screen. */ if (F_ISSET(sp, S_REFRESH)) { wrefresh(curscr); F_CLR(sp, S_REFRESH); } else refresh(); return (0); } /* * svi_msgflush -- * Flush any accumulated messages. */ static int svi_msgflush(sp) SCR *sp; { CHNAME *cname; MSG *mp; size_t chlen, len; int ch; char *p; #define MCONTMSG " [More ...]" /* Display the messages. */ cname = sp->cname; for (mp = sp->msgp, p = NULL; mp != NULL && !F_ISSET(mp, M_EMPTY); mp = mp->next) { p = mp->mbuf; lcont: /* Move to the message line and clear it. */ MOVE(sp, INFOLINE(sp), 0); clrtoeol(); /* * Turn on standout mode if requested, or, if we've split * the screen and need a divider. */ if (F_ISSET(mp, M_INV_VIDEO) || sp->child != NULL) standout(); /* * Print up to the "more" message. Avoid the last character * in the last line, some hardware doesn't like it. */ if (svi_ncols(sp, p, mp->len, NULL) < sp->cols - 1) len = sp->cols - 1; else len = (sp->cols - sizeof(MCONTMSG)) - 1; for (;; ++p) { if (!mp->len) break; ch = *p; chlen = cname[ch].len; if (chlen >= len) break; len -= chlen; --mp->len; ADDNSTR(cname[ch].name, chlen); } /* If more, print continue message. */ if (mp->len || mp->next != NULL && !F_ISSET(mp->next, M_EMPTY)) { ADDNSTR(MCONTMSG, sizeof(MCONTMSG) - 1); refresh(); while (sp->special[ch = term_key(sp, 0)] != K_CR && !isspace(ch)) svi_bell(sp); } /* Turn off standout mode. */ if (F_ISSET(mp, M_INV_VIDEO) || sp->child != NULL) standend(); if (mp->len) goto lcont; refresh(); F_SET(mp, M_EMPTY); } return (0); } #define RULERSIZE 15 #define MODESIZE (RULERSIZE + 15) /* * svi_modeline -- * Update the mode line. */ static int svi_modeline(sp, ep) SCR *sp; EXF *ep; { char *s, buf[RULERSIZE]; MOVE(sp, INFOLINE(sp), 0); clrtoeol(); /* Display the dividing line. */ if (sp->child != NULL) svi_divider(sp); /* Display the ruler and mode. */ if (O_ISSET(sp, O_RULER) && sp->cols > RULERSIZE + 2) { MOVE(sp, INFOLINE(sp), sp->cols / 2 - RULERSIZE / 2); clrtoeol(); (void)snprintf(buf, sizeof(buf), "%lu,%lu", sp->lno, sp->cno + 1); ADDSTR(buf); } /* * Show the mode. Leave the last character blank, just in case * it's a really dumb terminal with hardware scroll. */ if (O_ISSET(sp, O_SHOWMODE) && sp->cols > MODESIZE) { MOVE(sp, INFOLINE(sp), sp->cols - 8); s = F_ISSET(sp, S_INPUT) ? " Input " : "Command "; ADDSTR(s); } return (0); } int svi_divider(sp) SCR *sp; { #define DIVIDESIZE 10 int dividesize; char buf[DIVIDESIZE + 1]; dividesize = DIVIDESIZE > sp->cols ? sp->cols : DIVIDESIZE; memset(buf, ' ', dividesize); if (standout() == ERR) return (1); ADDNSTR(buf, dividesize); if (standend() == ERR) return (1); return (0); }