summaryrefslogtreecommitdiffstats
path: root/contrib/nvi/vi/vs_smap.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/nvi/vi/vs_smap.c')
-rw-r--r--contrib/nvi/vi/vs_smap.c1260
1 files changed, 1260 insertions, 0 deletions
diff --git a/contrib/nvi/vi/vs_smap.c b/contrib/nvi/vi/vs_smap.c
new file mode 100644
index 0000000..af38057
--- /dev/null
+++ b/contrib/nvi/vi/vs_smap.c
@@ -0,0 +1,1260 @@
+/*-
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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);
+}
OpenPOWER on IntegriCloud