/*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #ifndef lint static const char sccsid[] = "@(#)vs_smap.c 10.25 (Berkeley) 7/12/96"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int vs_deleteln __P((SCR *, int)); static int vs_insertln __P((SCR *, int)); static int vs_sm_delete __P((SCR *, recno_t)); static int vs_sm_down __P((SCR *, MARK *, recno_t, scroll_t, SMAP *)); static int vs_sm_erase __P((SCR *)); static int vs_sm_insert __P((SCR *, recno_t)); static int vs_sm_reset __P((SCR *, recno_t)); static int vs_sm_up __P((SCR *, MARK *, recno_t, scroll_t, SMAP *)); /* * vs_change -- * Make a change to the screen. * * PUBLIC: int vs_change __P((SCR *, recno_t, lnop_t)); */ int vs_change(sp, lno, op) SCR *sp; recno_t lno; lnop_t op; { VI_PRIVATE *vip; SMAP *p; size_t cnt, oldy, oldx; vip = VIP(sp); /* * XXX * Very nasty special case. The historic vi code displays a single * space (or a '$' if the list option is set) for the first line in * an "empty" file. If we "insert" a line, that line gets scrolled * down, not repainted, so it's incorrect when we refresh the screen. * The vi text input functions detect it explicitly and don't insert * a new line. * * Check for line #2 before going to the end of the file. */ if ((op == LINE_APPEND && lno == 0 || op == LINE_INSERT && lno == 1) && !db_exist(sp, 2)) { lno = 1; op = LINE_RESET; } /* Appending is the same as inserting, if the line is incremented. */ if (op == LINE_APPEND) { ++lno; op = LINE_INSERT; } /* Ignore the change if the line is after the map. */ if (lno > TMAP->lno) return (0); /* * If the line is before the map, and it's a decrement, decrement * the map. If it's an increment, increment the map. Otherwise, * ignore it. */ if (lno < HMAP->lno) { switch (op) { case LINE_APPEND: abort(); /* NOTREACHED */ case LINE_DELETE: for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) --p->lno; if (sp->lno >= lno) --sp->lno; F_SET(vip, VIP_N_RENUMBER); break; case LINE_INSERT: for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) ++p->lno; if (sp->lno >= lno) ++sp->lno; F_SET(vip, VIP_N_RENUMBER); break; case LINE_RESET: break; } return (0); } F_SET(vip, VIP_N_REFRESH); /* * Invalidate the line size cache, and invalidate the cursor if it's * on this line, */ VI_SCR_CFLUSH(vip); if (sp->lno == lno) F_SET(vip, VIP_CUR_INVALID); /* * If ex modifies the screen after ex output is already on the screen * or if we've switched into ex canonical mode, don't touch it -- we'll * get scrolling wrong, at best. */ if (!F_ISSET(sp, SC_TINPUT_INFO) && (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) { F_SET(vip, VIP_N_EX_REDRAW); return (0); } /* Save and restore the cursor for these routines. */ (void)sp->gp->scr_cursor(sp, &oldy, &oldx); switch (op) { case LINE_DELETE: if (vs_sm_delete(sp, lno)) return (1); F_SET(vip, VIP_N_RENUMBER); break; case LINE_INSERT: if (vs_sm_insert(sp, lno)) return (1); F_SET(vip, VIP_N_RENUMBER); break; case LINE_RESET: if (vs_sm_reset(sp, lno)) return (1); break; default: abort(); } (void)sp->gp->scr_move(sp, oldy, oldx); return (0); } /* * vs_sm_fill -- * Fill in the screen map, placing the specified line at the * right position. There isn't any way to tell if an SMAP * entry has been filled in, so this routine had better be * called with P_FILL set before anything else is done. * * !!! * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP * slot is already filled in, P_BOTTOM means that the TMAP slot is * already filled in, and we just finish up the job. * * PUBLIC: int vs_sm_fill __P((SCR *, recno_t, pos_t)); */ int vs_sm_fill(sp, lno, pos) SCR *sp; recno_t lno; pos_t pos; { SMAP *p, tmp; size_t cnt; /* Flush all cached information from the SMAP. */ for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) SMAP_FLUSH(p); /* * If the map is filled, the screen must be redrawn. * * XXX * This is a bug. We should try and figure out if the desired line * is already in the map or close by -- scrolling the screen would * be a lot better than redrawing. */ F_SET(sp, SC_SCR_REDRAW); switch (pos) { case P_FILL: tmp.lno = 1; tmp.coff = 0; tmp.soff = 1; /* See if less than half a screen from the top. */ if (vs_sm_nlines(sp, &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { lno = 1; goto top; } /* See if less than half a screen from the bottom. */ if (db_last(sp, &tmp.lno)) return (1); tmp.coff = 0; tmp.soff = vs_screens(sp, tmp.lno, NULL); if (vs_sm_nlines(sp, &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { TMAP->lno = tmp.lno; TMAP->coff = tmp.coff; TMAP->soff = tmp.soff; goto bottom; } goto middle; case P_TOP: if (lno != OOBLNO) { top: HMAP->lno = lno; HMAP->coff = 0; HMAP->soff = 1; } /* If we fail, just punt. */ for (p = HMAP, cnt = sp->t_rows; --cnt; ++p) if (vs_sm_next(sp, p, p + 1)) goto err; break; case P_MIDDLE: /* If we fail, guess that the file is too small. */ middle: p = HMAP + sp->t_rows / 2; p->lno = lno; p->coff = 0; p->soff = 1; for (; p > HMAP; --p) if (vs_sm_prev(sp, p, p - 1)) { lno = 1; goto top; } /* If we fail, just punt. */ p = HMAP + sp->t_rows / 2; for (; p < TMAP; ++p) if (vs_sm_next(sp, p, p + 1)) goto err; break; case P_BOTTOM: if (lno != OOBLNO) { TMAP->lno = lno; TMAP->coff = 0; TMAP->soff = vs_screens(sp, lno, NULL); } /* If we fail, guess that the file is too small. */ bottom: for (p = TMAP; p > HMAP; --p) if (vs_sm_prev(sp, p, p - 1)) { lno = 1; goto top; } break; default: abort(); } return (0); /* * Try and put *something* on the screen. If this fails, we have a * serious hard error. */ err: HMAP->lno = 1; HMAP->coff = 0; HMAP->soff = 1; for (p = HMAP; p < TMAP; ++p) if (vs_sm_next(sp, p, p + 1)) return (1); return (0); } /* * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the * screen contains only a single line (whether because the screen is small * or the line large), it gets fairly exciting. Skip the fun, set a flag * so the screen map is refilled and the screen redrawn, and return. This * is amazingly slow, but it's not clear that anyone will care. */ #define HANDLE_WEIRDNESS(cnt) { \ if (cnt >= sp->t_rows) { \ F_SET(sp, SC_SCR_REFORMAT); \ return (0); \ } \ } /* * vs_sm_delete -- * Delete a line out of the SMAP. */ static int vs_sm_delete(sp, lno) SCR *sp; recno_t lno; { SMAP *p, *t; size_t cnt_orig; /* * Find the line in the map, and count the number of screen lines * which display any part of the deleted line. */ for (p = HMAP; p->lno != lno; ++p); if (O_ISSET(sp, O_LEFTRIGHT)) cnt_orig = 1; else for (cnt_orig = 1, t = p + 1; t <= TMAP && t->lno == lno; ++cnt_orig, ++t); HANDLE_WEIRDNESS(cnt_orig); /* Delete that many lines from the screen. */ (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_deleteln(sp, cnt_orig)) return (1); /* Shift the screen map up. */ memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP)); /* Decrement the line numbers for the rest of the map. */ for (t = TMAP - cnt_orig; p <= t; ++p) --p->lno; /* Display the new lines. */ for (p = TMAP - cnt_orig;;) { if (p < TMAP && vs_sm_next(sp, p, p + 1)) return (1); /* vs_sm_next() flushed the cache. */ if (vs_line(sp, ++p, NULL, NULL)) return (1); if (p == TMAP) break; } return (0); } /* * vs_sm_insert -- * Insert a line into the SMAP. */ static int vs_sm_insert(sp, lno) SCR *sp; recno_t lno; { SMAP *p, *t; size_t cnt_orig, cnt, coff; /* Save the offset. */ coff = HMAP->coff; /* * Find the line in the map, find out how many screen lines * needed to display the line. */ for (p = HMAP; p->lno != lno; ++p); cnt_orig = vs_screens(sp, lno, NULL); HANDLE_WEIRDNESS(cnt_orig); /* * The lines left in the screen override the number of screen * lines in the inserted line. */ cnt = (TMAP - p) + 1; if (cnt_orig > cnt) cnt_orig = cnt; /* Push down that many lines. */ (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_insertln(sp, cnt_orig)) return (1); /* Shift the screen map down. */ memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP)); /* Increment the line numbers for the rest of the map. */ for (t = p + cnt_orig; t <= TMAP; ++t) ++t->lno; /* Fill in the SMAP for the new lines, and display. */ for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) { t->lno = lno; t->coff = coff; t->soff = cnt; SMAP_FLUSH(t); if (vs_line(sp, t, NULL, NULL)) return (1); } return (0); } /* * vs_sm_reset -- * Reset a line in the SMAP. */ static int vs_sm_reset(sp, lno) SCR *sp; recno_t lno; { SMAP *p, *t; size_t cnt_orig, cnt_new, cnt, diff; /* * See if the number of on-screen rows taken up by the old display * for the line is the same as the number needed for the new one. * If so, repaint, otherwise do it the hard way. */ for (p = HMAP; p->lno != lno; ++p); if (O_ISSET(sp, O_LEFTRIGHT)) { t = p; cnt_orig = cnt_new = 1; } else { for (cnt_orig = 0, t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t); cnt_new = vs_screens(sp, lno, NULL); } HANDLE_WEIRDNESS(cnt_orig); if (cnt_orig == cnt_new) { do { SMAP_FLUSH(p); if (vs_line(sp, p, NULL, NULL)) return (1); } while (++p < t); return (0); } if (cnt_orig < cnt_new) { /* Get the difference. */ diff = cnt_new - cnt_orig; /* * The lines left in the screen override the number of screen * lines in the inserted line. */ cnt = (TMAP - p) + 1; if (diff > cnt) diff = cnt; /* If there are any following lines, push them down. */ if (cnt > 1) { (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_insertln(sp, diff)) return (1); /* Shift the screen map down. */ memmove(p + diff, p, (((TMAP - p) - diff) + 1) * sizeof(SMAP)); } /* Fill in the SMAP for the replaced line, and display. */ for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) { t->lno = lno; t->soff = cnt; SMAP_FLUSH(t); if (vs_line(sp, t, NULL, NULL)) return (1); } } else { /* Get the difference. */ diff = cnt_orig - cnt_new; /* Delete that many lines from the screen. */ (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_deleteln(sp, diff)) return (1); /* Shift the screen map up. */ memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP)); /* Fill in the SMAP for the replaced line, and display. */ for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) { t->lno = lno; t->soff = cnt; SMAP_FLUSH(t); if (vs_line(sp, t, NULL, NULL)) return (1); } /* Display the new lines at the bottom of the screen. */ for (t = TMAP - diff;;) { if (t < TMAP && vs_sm_next(sp, t, t + 1)) return (1); /* vs_sm_next() flushed the cache. */ if (vs_line(sp, ++t, NULL, NULL)) return (1); if (t == TMAP) break; } } return (0); } /* * vs_sm_scroll * Scroll the SMAP up/down count logical lines. Different * semantics based on the vi command, *sigh*. * * PUBLIC: int vs_sm_scroll __P((SCR *, MARK *, recno_t, scroll_t)); */ int vs_sm_scroll(sp, rp, count, scmd) SCR *sp; MARK *rp; recno_t count; scroll_t scmd; { SMAP *smp; /* * Invalidate the cursor. The line is probably going to change, * (although for ^E and ^Y it may not). In any case, the scroll * routines move the cursor to draw things. */ F_SET(VIP(sp), VIP_CUR_INVALID); /* Find the cursor in the screen. */ if (vs_sm_cursor(sp, &smp)) return (1); switch (scmd) { case CNTRL_B: case CNTRL_U: case CNTRL_Y: case Z_CARAT: if (vs_sm_down(sp, rp, count, scmd, smp)) return (1); break; case CNTRL_D: case CNTRL_E: case CNTRL_F: case Z_PLUS: if (vs_sm_up(sp, rp, count, scmd, smp)) return (1); break; default: abort(); } /* * !!! * If we're at the start of a line, go for the first non-blank. * This makes it look like the old vi, even though we're moving * around by logical lines, not physical ones. * * XXX * In the presence of a long line, which has more than a screen * width of leading spaces, this code can cause a cursor warp. * Live with it. */ if (scmd != CNTRL_E && scmd != CNTRL_Y && rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno)) return (1); return (0); } /* * vs_sm_up -- * Scroll the SMAP up count logical lines. */ static int vs_sm_up(sp, rp, count, scmd, smp) SCR *sp; MARK *rp; scroll_t scmd; recno_t count; SMAP *smp; { int cursor_set, echanged, zset; SMAP *ssmp, s1, s2; /* * Check to see if movement is possible. * * Get the line after the map. If that line is a new one (and if * O_LEFTRIGHT option is set, this has to be true), and the next * line doesn't exist, and the cursor doesn't move, or the cursor * isn't even on the screen, or the cursor is already at the last * line in the map, it's an error. If that test succeeded because * the cursor wasn't at the end of the map, test to see if the map * is mostly empty. */ if (vs_sm_next(sp, TMAP, &s1)) return (1); if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) { if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) { v_eof(sp, NULL); return (1); } if (vs_sm_next(sp, smp, &s1)) return (1); if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) { v_eof(sp, NULL); return (1); } } /* * Small screens: see vs_refresh.c section 6a. * * If it's a small screen, and the movement isn't larger than a * screen, i.e some context will remain, open up the screen and * display by scrolling. In this case, the cursor moves down one * line for each line displayed. Otherwise, erase/compress and * repaint, and move the cursor to the first line in the screen. * Note, the ^F command is always in the latter case, for historical * reasons. */ cursor_set = 0; if (IS_SMALL(sp)) { if (count >= sp->t_maxrows || scmd == CNTRL_F) { s1 = TMAP[0]; if (vs_sm_erase(sp)) return (1); for (; count--; s1 = s2) { if (vs_sm_next(sp, &s1, &s2)) return (1); if (s2.lno != s1.lno && !db_exist(sp, s2.lno)) break; } TMAP[0] = s2; if (vs_sm_fill(sp, OOBLNO, P_BOTTOM)) return (1); return (vs_sm_position(sp, rp, 0, P_TOP)); } cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp); for (; count && sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { if (vs_sm_next(sp, TMAP, &s1)) return (1); if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno)) break; *++TMAP = s1; /* vs_sm_next() flushed the cache. */ if (vs_line(sp, TMAP, NULL, NULL)) return (1); if (!cursor_set) ++ssmp; } if (!cursor_set) { rp->lno = ssmp->lno; rp->cno = ssmp->c_sboff; } if (count == 0) return (0); } for (echanged = zset = 0; count; --count) { /* Decide what would show up on the screen. */ if (vs_sm_next(sp, TMAP, &s1)) return (1); /* If the line doesn't exist, we're done. */ if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno)) break; /* Scroll the screen cursor up one logical line. */ if (vs_sm_1up(sp)) return (1); switch (scmd) { case CNTRL_E: if (smp > HMAP) --smp; else echanged = 1; break; case Z_PLUS: if (zset) { if (smp > HMAP) --smp; } else { smp = TMAP; zset = 1; } /* FALLTHROUGH */ default: break; } } if (cursor_set) return(0); switch (scmd) { case CNTRL_E: /* * On a ^E that was forced to change lines, try and keep the * cursor as close as possible to the last position, but also * set it up so that the next "real" movement will return the * cursor to the closest position to the last real movement. */ if (echanged) { rp->lno = smp->lno; rp->cno = vs_colpos(sp, smp->lno, (O_ISSET(sp, O_LEFTRIGHT) ? smp->coff : (smp->soff - 1) * sp->cols) + sp->rcm % sp->cols); } return (0); case CNTRL_F: /* * If there are more lines, the ^F command is positioned at * the first line of the screen. */ if (!count) { smp = HMAP; break; } /* FALLTHROUGH */ case CNTRL_D: /* * The ^D and ^F commands move the cursor towards EOF * if there are more lines to move. Check to be sure * the lines actually exist. (They may not if the * file is smaller than the screen.) */ for (; count; --count, ++smp) if (smp == TMAP || !db_exist(sp, smp[1].lno)) break; break; case Z_PLUS: /* The z+ command moves the cursor to the first new line. */ break; default: abort(); } if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) return (1); rp->lno = smp->lno; rp->cno = smp->c_sboff; return (0); } /* * vs_sm_1up -- * Scroll the SMAP up one. * * PUBLIC: int vs_sm_1up __P((SCR *)); */ int vs_sm_1up(sp) SCR *sp; { /* * Delete the top line of the screen. Shift the screen map * up and display a new line at the bottom of the screen. */ (void)sp->gp->scr_move(sp, 0, 0); if (vs_deleteln(sp, 1)) return (1); /* One-line screens can fail. */ if (IS_ONELINE(sp)) { if (vs_sm_next(sp, TMAP, TMAP)) return (1); } else { memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP)); if (vs_sm_next(sp, TMAP - 1, TMAP)) return (1); } /* vs_sm_next() flushed the cache. */ return (vs_line(sp, TMAP, NULL, NULL)); } /* * vs_deleteln -- * Delete a line a la curses, make sure to put the information * line and other screens back. */ static int vs_deleteln(sp, cnt) SCR *sp; int cnt; { GS *gp; size_t oldy, oldx; gp = sp->gp; if (IS_ONELINE(sp)) (void)gp->scr_clrtoeol(sp); else { (void)gp->scr_cursor(sp, &oldy, &oldx); while (cnt--) { (void)gp->scr_deleteln(sp); (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_insertln(sp); (void)gp->scr_move(sp, oldy, oldx); } } return (0); } /* * vs_sm_down -- * Scroll the SMAP down count logical lines. */ static int vs_sm_down(sp, rp, count, scmd, smp) SCR *sp; MARK *rp; recno_t count; SMAP *smp; scroll_t scmd; { SMAP *ssmp, s1, s2; int cursor_set, ychanged, zset; /* Check to see if movement is possible. */ if (HMAP->lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) && (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) { v_sof(sp, NULL); return (1); } /* * Small screens: see vs_refresh.c section 6a. * * If it's a small screen, and the movement isn't larger than a * screen, i.e some context will remain, open up the screen and * display by scrolling. In this case, the cursor moves up one * line for each line displayed. Otherwise, erase/compress and * repaint, and move the cursor to the first line in the screen. * Note, the ^B command is always in the latter case, for historical * reasons. */ cursor_set = scmd == CNTRL_Y; if (IS_SMALL(sp)) { if (count >= sp->t_maxrows || scmd == CNTRL_B) { s1 = HMAP[0]; if (vs_sm_erase(sp)) return (1); for (; count--; s1 = s2) { if (vs_sm_prev(sp, &s1, &s2)) return (1); if (s2.lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1)) break; } HMAP[0] = s2; if (vs_sm_fill(sp, OOBLNO, P_TOP)) return (1); return (vs_sm_position(sp, rp, 0, P_BOTTOM)); } cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp); for (; count && sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { if (HMAP->lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1)) break; ++TMAP; if (vs_sm_1down(sp)) return (1); } if (!cursor_set) { rp->lno = ssmp->lno; rp->cno = ssmp->c_sboff; } if (count == 0) return (0); } for (ychanged = zset = 0; count; --count) { /* If the line doesn't exist, we're done. */ if (HMAP->lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1)) break; /* Scroll the screen and cursor down one logical line. */ if (vs_sm_1down(sp)) return (1); switch (scmd) { case CNTRL_Y: if (smp < TMAP) ++smp; else ychanged = 1; break; case Z_CARAT: if (zset) { if (smp < TMAP) ++smp; } else { smp = HMAP; zset = 1; } /* FALLTHROUGH */ default: break; } } if (scmd != CNTRL_Y && cursor_set) return(0); switch (scmd) { case CNTRL_B: /* * If there are more lines, the ^B command is positioned at * the last line of the screen. However, the line may not * exist. */ if (!count) { for (smp = TMAP; smp > HMAP; --smp) if (db_exist(sp, smp->lno)) break; break; } /* FALLTHROUGH */ case CNTRL_U: /* * The ^B and ^U commands move the cursor towards SOF * if there are more lines to move. */ if (count < smp - HMAP) smp -= count; else smp = HMAP; break; case CNTRL_Y: /* * On a ^Y that was forced to change lines, try and keep the * cursor as close as possible to the last position, but also * set it up so that the next "real" movement will return the * cursor to the closest position to the last real movement. */ if (ychanged) { rp->lno = smp->lno; rp->cno = vs_colpos(sp, smp->lno, (O_ISSET(sp, O_LEFTRIGHT) ? smp->coff : (smp->soff - 1) * sp->cols) + sp->rcm % sp->cols); } return (0); case Z_CARAT: /* The z^ command moves the cursor to the first new line. */ break; default: abort(); } if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) return (1); rp->lno = smp->lno; rp->cno = smp->c_sboff; return (0); } /* * vs_sm_erase -- * Erase the small screen area for the scrolling functions. */ static int vs_sm_erase(sp) SCR *sp; { GS *gp; gp = sp->gp; (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) { (void)gp->scr_move(sp, TMAP - HMAP, 0); (void)gp->scr_clrtoeol(sp); } return (0); } /* * vs_sm_1down -- * Scroll the SMAP down one. * * PUBLIC: int vs_sm_1down __P((SCR *)); */ int vs_sm_1down(sp) SCR *sp; { /* * Insert a line at the top of the screen. Shift the screen map * down and display a new line at the top of the screen. */ (void)sp->gp->scr_move(sp, 0, 0); if (vs_insertln(sp, 1)) return (1); /* One-line screens can fail. */ if (IS_ONELINE(sp)) { if (vs_sm_prev(sp, HMAP, HMAP)) return (1); } else { memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP)); if (vs_sm_prev(sp, HMAP + 1, HMAP)) return (1); } /* vs_sm_prev() flushed the cache. */ return (vs_line(sp, HMAP, NULL, NULL)); } /* * vs_insertln -- * Insert a line a la curses, make sure to put the information * line and other screens back. */ static int vs_insertln(sp, cnt) SCR *sp; int cnt; { GS *gp; size_t oldy, oldx; gp = sp->gp; if (IS_ONELINE(sp)) { (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); } else { (void)gp->scr_cursor(sp, &oldy, &oldx); while (cnt--) { (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0); (void)gp->scr_deleteln(sp); (void)gp->scr_move(sp, oldy, oldx); (void)gp->scr_insertln(sp); } } return (0); } /* * vs_sm_next -- * Fill in the next entry in the SMAP. * * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *)); */ int vs_sm_next(sp, p, t) SCR *sp; SMAP *p, *t; { size_t lcnt; SMAP_FLUSH(t); if (O_ISSET(sp, O_LEFTRIGHT)) { t->lno = p->lno + 1; t->coff = p->coff; } else { lcnt = vs_screens(sp, p->lno, NULL); if (lcnt == p->soff) { t->lno = p->lno + 1; t->soff = 1; } else { t->lno = p->lno; t->soff = p->soff + 1; } } return (0); } /* * vs_sm_prev -- * Fill in the previous entry in the SMAP. * * PUBLIC: int vs_sm_prev __P((SCR *, SMAP *, SMAP *)); */ int vs_sm_prev(sp, p, t) SCR *sp; SMAP *p, *t; { SMAP_FLUSH(t); if (O_ISSET(sp, O_LEFTRIGHT)) { t->lno = p->lno - 1; t->coff = p->coff; } else { if (p->soff != 1) { t->lno = p->lno; t->soff = p->soff - 1; } else { t->lno = p->lno - 1; t->soff = vs_screens(sp, t->lno, NULL); } } return (t->lno == 0); } /* * vs_sm_cursor -- * Return the SMAP entry referenced by the cursor. * * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **)); */ int vs_sm_cursor(sp, smpp) SCR *sp; SMAP **smpp; { SMAP *p; /* See if the cursor is not in the map. */ if (sp->lno < HMAP->lno || sp->lno > TMAP->lno) return (1); /* Find the first occurence of the line. */ for (p = HMAP; p->lno != sp->lno; ++p); /* Fill in the map information until we find the right line. */ for (; p <= TMAP; ++p) { /* Short lines are common and easy to detect. */ if (p != TMAP && (p + 1)->lno != p->lno) { *smpp = p; return (0); } if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL)) return (1); if (p->c_eboff >= sp->cno) { *smpp = p; return (0); } } /* It was past the end of the map after all. */ return (1); } /* * vs_sm_position -- * Return the line/column of the top, middle or last line on the screen. * (The vi H, M and L commands.) Here because only the screen routines * know what's really out there. * * PUBLIC: int vs_sm_position __P((SCR *, MARK *, u_long, pos_t)); */ int vs_sm_position(sp, rp, cnt, pos) SCR *sp; MARK *rp; u_long cnt; pos_t pos; { SMAP *smp; recno_t last; switch (pos) { case P_TOP: /* * !!! * Historically, an invalid count to the H command failed. * We do nothing special here, just making sure that H in * an empty screen works. */ if (cnt > TMAP - HMAP) goto sof; smp = HMAP + cnt; if (cnt && !db_exist(sp, smp->lno)) { sof: msgq(sp, M_BERR, "220|Movement past the end-of-screen"); return (1); } break; case P_MIDDLE: /* * !!! * Historically, a count to the M command was ignored. * If the screen isn't filled, find the middle of what's * real and move there. */ if (!db_exist(sp, TMAP->lno)) { if (db_last(sp, &last)) return (1); for (smp = TMAP; smp->lno > last && smp > HMAP; --smp); if (smp > HMAP) smp -= (smp - HMAP) / 2; } else smp = (HMAP + (TMAP - HMAP) / 2) + cnt; break; case P_BOTTOM: /* * !!! * Historically, an invalid count to the L command failed. * If the screen isn't filled, find the bottom of what's * real and try to offset from there. */ if (cnt > TMAP - HMAP) goto eof; smp = TMAP - cnt; if (!db_exist(sp, smp->lno)) { if (db_last(sp, &last)) return (1); for (; smp->lno > last && smp > HMAP; --smp); if (cnt > smp - HMAP) { eof: msgq(sp, M_BERR, "221|Movement past the beginning-of-screen"); return (1); } smp -= cnt; } break; default: abort(); } /* Make sure that the cached information is valid. */ if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) return (1); rp->lno = smp->lno; rp->cno = smp->c_sboff; return (0); } /* * vs_sm_nlines -- * Return the number of screen lines from an SMAP entry to the * start of some file line, less than a maximum value. * * PUBLIC: recno_t vs_sm_nlines __P((SCR *, SMAP *, recno_t, size_t)); */ recno_t vs_sm_nlines(sp, from_sp, to_lno, max) SCR *sp; SMAP *from_sp; recno_t to_lno; size_t max; { recno_t lno, lcnt; if (O_ISSET(sp, O_LEFTRIGHT)) return (from_sp->lno > to_lno ? from_sp->lno - to_lno : to_lno - from_sp->lno); if (from_sp->lno == to_lno) return (from_sp->soff - 1); if (from_sp->lno > to_lno) { lcnt = from_sp->soff - 1; /* Correct for off-by-one. */ for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;) lcnt += vs_screens(sp, lno, NULL); } else { lno = from_sp->lno; lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1; for (; ++lno < to_lno && lcnt <= max;) lcnt += vs_screens(sp, lno, NULL); } return (lcnt); }