BSD 4_4 release
[unix-history] / usr / src / contrib / nvi / nvi / svi / svi_smap.c
/*-
* Copyright (c) 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_smap.c 8.1 (Berkeley) 6/9/93";
#endif /* not lint */
#include <sys/types.h>
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include "vi.h"
#include "vcmd.h"
#include "svi_screen.h"
/*
* svi_change --
* Make a change to the screen.
*/
int
svi_change(sp, ep, lno, op)
SCR *sp;
EXF *ep;
recno_t lno;
enum operation op;
{
SMAP *p;
size_t oldy, oldx;
/* 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) {
if (op == LINE_DELETE)
for (p = HMAP; p <= TMAP; ++p)
--p->lno;
else if (op == LINE_INSERT)
for (p = HMAP; p <= TMAP; ++p)
++p->lno;
return (0);
}
/* Invalidate the cursor, if it's on this line. */
if (sp->lno == lno)
F_SET(sp, S_CUR_INVALID);
getyx(stdscr, oldy, oldx);
switch (op) {
case LINE_DELETE:
if (svi_sm_delete(sp, ep, lno))
return (1);
break;
case LINE_INSERT:
if (svi_sm_insert(sp, ep, lno))
return (1);
break;
case LINE_RESET:
if (svi_sm_reset(sp, ep, lno))
return (1);
break;
default:
abort();
}
MOVEA(sp, oldy, oldx);
return (0);
}
/*
* svi_sm_fill --
* Fill in the screen map, placing the specified line at the
* right position.
*/
int
svi_sm_fill(sp, ep, lno, pos)
SCR *sp;
EXF *ep;
recno_t lno;
enum position pos;
{
SMAP *p, tmp;
switch (pos) {
case P_FILL:
tmp.lno = 1;
tmp.off = 1;
/* See if less than half a screen from the top. */
if (svi_sm_nlines(sp, ep,
&tmp, lno, HALFSCREEN(sp)) <= HALFSCREEN(sp)) {
lno = 1;
goto top;
}
/* See if less than half a screen from the bottom. */
if (file_lline(sp, ep, &tmp.lno))
return (1);
tmp.off = svi_screens(sp, ep, tmp.lno, NULL);
if (svi_sm_nlines(sp, ep,
&tmp, lno, HALFSCREEN(sp)) <= HALFSCREEN(sp)) {
TMAP->lno = tmp.lno;
TMAP->off = tmp.off;
goto bottom;
}
goto middle;
case P_TOP:
/* If we fail, just punt. */
top: for (p = HMAP, p->lno = lno, p->off = 1; p < TMAP; ++p)
if (svi_sm_next(sp, ep, p, p + 1))
goto err;
break;
case P_MIDDLE:
/* If we fail, guess that the file is too small. */
middle: p = HMAP + (TMAP - HMAP) / 2;
for (p->lno = lno, p->off = 1; p > HMAP; --p)
if (svi_sm_prev(sp, ep, p, p - 1)) {
lno = 1;
goto top;
}
/* If we fail, just punt. */
p = HMAP + (TMAP - HMAP) / 2;
for (; p < TMAP; ++p)
if (svi_sm_next(sp, ep, p, p + 1))
goto err;
break;
case P_BOTTOM:
/* If we fail, guess that the file is too small. */
TMAP->lno = lno;
TMAP->off = svi_screens(sp, ep, lno, NULL);
bottom: for (p = TMAP; p > HMAP; --p)
if (svi_sm_prev(sp, ep, p, p - 1)) {
lno = 1;
goto top;
}
break;
}
return (0);
/*
* Try and put *something* on the screen. If this fails,
* we have a serious hard error.
*/
err: HMAP->lno = 1;
HMAP->off = 1;
for (p = HMAP; p < TMAP; ++p)
if (svi_sm_next(sp, ep, p, p + 1))
return (1);
return (0);
}
/*
* For the routines svi_sm_reset, svi_sm_delete and svi_sm_insert: if the
* screen only contains one line, or, if the line is the entire screen, this
* gets fairly exciting. Skip the fun and simply return if there's only one
* line in the screen, or just call fill. Fill may not be entirely accurate,
* i.e. we may be painting the screen with something not even close to the
* cursor, but it's not like we're into serious performance issues here, and
* the refresh routine will fix it for us.
*/
#define TOO_WEIRD { \
if (cnt_orig >= sp->t_rows) { \
if (cnt_orig == 1) \
return (0); \
if (file_gline(sp, ep, lno, NULL) == NULL) \
if (file_lline(sp, ep, &lno)) \
return (1); \
F_SET(sp, S_REDRAW); \
return (svi_sm_fill(sp, ep, lno, P_TOP)); \
} \
}
/*
* svi_sm_delete --
* Delete a line out of the SMAP.
*/
int
svi_sm_delete(sp, ep, lno)
SCR *sp;
EXF *ep;
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);
for (cnt_orig = 1, t = p + 1;
t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
TOO_WEIRD;
/* Delete that many lines from the screen. */
MOVE(sp, p - HMAP, 0);
if (svi_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 && svi_sm_next(sp, ep, p, p + 1))
return (1);
if (svi_line(sp, ep, ++p, NULL, 0, NULL, NULL))
return (1);
if (p == TMAP)
break;
}
return (0);
}
/*
* svi_sm_insert --
* Insert a line into the SMAP.
*/
int
svi_sm_insert(sp, ep, lno)
SCR *sp;
EXF *ep;
recno_t lno;
{
SMAP *p, *t;
size_t cnt_orig, cnt;
/*
* 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 = svi_screens(sp, ep, lno, NULL);
TOO_WEIRD;
/*
* 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. */
MOVE(sp, p - HMAP, 0);
if (svi_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->off = cnt;
if (svi_line(sp, ep, t, NULL, 0, NULL, NULL))
return (1);
}
return (0);
}
/*
* svi_sm_reset --
* Reset a line in the SMAP.
*/
int
svi_sm_reset(sp, ep, lno)
SCR *sp;
EXF *ep;
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);
for (cnt_orig = 0, t = p;
t->lno == lno && t <= TMAP; ++cnt_orig, ++t);
cnt_new = svi_screens(sp, ep, lno, NULL);
TOO_WEIRD;
if (cnt_orig == cnt_new) {
do {
if (svi_line(sp, ep, p, NULL, 0, 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.
*/
if (diff > cnt_orig)
diff = cnt_orig;
/* Push down the extra lines. */
MOVE(sp, p - HMAP, 0);
if (svi_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->off = cnt;
if (svi_line(sp, ep, t, NULL, 0, NULL, NULL))
return (1);
}
} else {
/* Get the difference. */
diff = cnt_orig - cnt_new;
/* Delete that many lines from the screen. */
MOVE(sp, p - HMAP, 0);
if (svi_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->off = cnt;
if (svi_line(sp, ep, t, NULL, 0, NULL, NULL))
return (1);
}
/* Display the new lines at the bottom of the screen. */
for (t = TMAP - diff;;) {
if (t < TMAP && svi_sm_next(sp, ep, t, t + 1))
return (1);
if (svi_line(sp, ep, ++t, NULL, 0, NULL, NULL))
return (1);
if (t == TMAP)
break;
}
}
return (0);
}
/*
* svi_sm_up --
* Scroll the SMAP up count logical lines.
*/
int
svi_sm_up(sp, ep, rp, count, cursor_move)
SCR *sp;
EXF *ep;
MARK *rp;
recno_t count;
int cursor_move;
{
SMAP *p, svmap, tmp;
recno_t last;
int scrolled;
/* Set the default return position. */
rp->lno = sp->lno;
rp->cno = sp->cno;
/*
* There are two forms of this command, one where the cursor follows
* the line, and one where it doesn't. In the latter, we try and keep
* the cursor at the same position on the screen, but, if the screen
* is small enough and the line length large enough, the cursor can
* end up in very strange places. Probably not worth fixing.
*
* Find the line in the SMAP.
*/
for (p = HMAP;; ++p) {
if (p > TMAP) {
msgq(sp, M_ERR,
"Line %lu not on the screen.", sp->lno);
return (1);
}
if (p->lno == sp->lno)
break;
}
if (file_lline(sp, ep, &last))
return (1);
if (last == 0) {
v_eof(sp, ep, NULL);
return (1);
}
for (svmap = *p, scrolled = 0;; scrolled = 1) {
if (count == 0)
break;
--count;
/* Decide what would show up on the screen. */
if (svi_sm_next(sp, ep, TMAP, &tmp))
return (1);
/* If the line doesn't exist, we're done. */
if (tmp.lno > last)
break;
/* Scroll up one logical line. */
if (svi_sm_1up(sp, ep))
return (1);
if (!cursor_move && p > HMAP)
--p;
}
if (cursor_move) {
/*
* If didn't move enough lines, it's an error if we're at the
* EOF, else move there. Otherwise, try and place the cursor
* roughly where it was before.
*/
if (!scrolled || count) {
if (sp->lno == last) {
v_eof(sp, ep, NULL);
return (1);
}
if (last < TMAP->lno) {
for (p = HMAP;; ++p)
if (p->lno == last)
break;
} else
p = TMAP;
}
} else {
/*
* If the line itself moved, invalidate the cursor, because
* the comparison with the old line/new line won't be right
*/
F_SET(sp, S_CUR_INVALID);
/* It's an error if we didn't scroll enough. */
if (!scrolled || count) {
v_eof(sp, ep, NULL);
return (1);
}
/* If the cursor moved off the screen, move it to the top. */
if (sp->lno < HMAP->lno)
p = HMAP;
}
/*
* On a logical movement, we 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 (p->lno != svmap.lno || p->off != svmap.off) {
rp->lno = p->lno;
rp->cno = svi_lrelative(sp, ep, p->lno, p->off);
}
return (0);
}
/*
* svi_sm_1up --
* Scroll the SMAP up one.
*/
int
svi_sm_1up(sp, ep)
SCR *sp;
EXF *ep;
{
/*
* Delete the top line of the screen. Shift the screen map up.
* Display a new line at the bottom of the screen.
*/
MOVE(sp, 0, 0);
if (svi_deleteln(sp, 1))
return (1);
/* One-line screens can fail. */
if (HMAP == TMAP) {
if (svi_sm_next(sp, ep, TMAP, TMAP))
return (1);
} else {
memmove(HMAP, HMAP + 1, sp->rows * sizeof(SMAP));
if (svi_sm_next(sp, ep, TMAP - 1, TMAP))
return (1);
}
if (svi_line(sp, ep, TMAP, NULL, 0, NULL, NULL))
return (1);
return (0);
}
/*
* svi_deleteln --
* Delete a line a la curses, make sure to put the information
* line and other screens back.
*/
int
svi_deleteln(sp, cnt)
SCR *sp;
int cnt;
{
size_t oldy, oldx;
getyx(stdscr, oldy, oldx);
while (cnt--) {
deleteln();
MOVE(sp, INFOLINE(sp) - 1, 0);
insertln();
MOVEA(sp, oldy, oldx);
}
return (0);
}
/*
* svi_sm_down --
* Scroll the SMAP down count logical lines.
*/
int
svi_sm_down(sp, ep, rp, count, cursor_move)
SCR *sp;
EXF *ep;
MARK *rp;
recno_t count;
int cursor_move;
{
SMAP *p, svmap;
int scrolled;
/* Set the default return position. */
rp->lno = sp->lno;
rp->cno = sp->cno;
/*
* There are two forms of this command, one where the cursor follows
* the line, and one where it doesn't. In the latter, we try and keep
* the cursor at the same position on the screen, but, if the screen
* is small enough and the line length large enough, the cursor can
* end up in very strange places. Probably not worth fixing.
*
* Find the line in the SMAP.
*/
for (p = HMAP;; ++p) {
if (p > TMAP) {
msgq(sp, M_ERR,
"Line %lu not on the screen", sp->lno);
return (1);
}
if (p->lno == sp->lno)
break;
}
for (svmap = *p, scrolled = 0;; scrolled = 1) {
if (count == 0)
break;
--count;
/* If the line doesn't exist, we're done. */
if (HMAP->lno == 1 && HMAP->off == 1)
break;
/* Scroll down one logical line. */
if (svi_sm_1down(sp, ep))
return (1);
if (!cursor_move && p < TMAP)
++p;
}
if (cursor_move) {
/*
* If didn't move enough lines, it's an error if we're at the
* SOF, else move there. Otherwise, try and place the cursor
* roughly where it was before.
*/
if (!scrolled || count) {
if (sp->lno == HMAP->lno) {
v_sof(sp, NULL);
return (1);
}
p = HMAP;
}
} else {
/*
* If the line itself moved, invalidate the cursor, because
* the comparison with the old line/new line won't be right
*/
F_SET(sp, S_CUR_INVALID);
/* It's an error if we didn't scroll enough. */
if (!scrolled || count) {
v_sof(sp, NULL);
return (1);
}
/* If the cursor moved off the screen, move it to the bottom. */
if (sp->lno > TMAP->lno)
p = TMAP;
}
/*
* On a logical movement, we 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 (p->lno != svmap.lno || p->off != svmap.off) {
rp->lno = p->lno;
rp->cno = svi_lrelative(sp, ep, p->lno, p->off);
}
return (0);
}
/*
* svi_sm_1down --
* Scroll the SMAP down one.
*/
int
svi_sm_1down(sp, ep)
SCR *sp;
EXF *ep;
{
/*
* Clear the bottom line of the screen, insert a line at the top
* of the screen. Shift the screen map down, display a new line
* at the top of the screen.
*/
MOVE(sp, sp->t_rows, 0);
clrtoeol();
MOVE(sp, 0, 0);
if (svi_insertln(sp, 1))
return (1);
memmove(HMAP + 1, HMAP, sp->rows * sizeof(SMAP));
if (svi_sm_prev(sp, ep, HMAP + 1, HMAP))
return (1);
if (svi_line(sp, ep, HMAP, NULL, 0, NULL, NULL))
return (1);
return (0);
}
/*
* svi_insertln --
* Insert a line a la curses, make sure to put the information
* line and other screens back.
*/
int
svi_insertln(sp, cnt)
SCR *sp;
int cnt;
{
size_t oldy, oldx;
getyx(stdscr, oldy, oldx);
while (cnt--) {
MOVE(sp, INFOLINE(sp) - 1, 0);
deleteln();
MOVEA(sp, oldy, oldx);
insertln();
}
return (0);
}
/*
* svi_sm_next --
* Fill in the next entry in the SMAP.
*/
int
svi_sm_next(sp, ep, p, t)
SCR *sp;
EXF *ep;
SMAP *p, *t;
{
size_t lcnt;
if (O_ISSET(sp, O_LEFTRIGHT)) {
t->lno = p->lno + 1;
t->off = p->off;
} else {
lcnt = svi_screens(sp, ep, p->lno, NULL);
if (lcnt == p->off) {
t->lno = p->lno + 1;
t->off = 1;
} else {
t->lno = p->lno;
t->off = p->off + 1;
}
}
return (0);
}
/*
* svi_sm_prev --
* Fill in the previous entry in the SMAP.
*/
int
svi_sm_prev(sp, ep, p, t)
SCR *sp;
EXF *ep;
SMAP *p, *t;
{
if (O_ISSET(sp, O_LEFTRIGHT)) {
t->lno = p->lno - 1;
t->off = p->off;
} else if (p->off != 1) {
t->lno = p->lno;
t->off = p->off - 1;
} else {
t->lno = p->lno - 1;
t->off = svi_screens(sp, ep, t->lno, NULL);
}
return (t->lno == 0);
}
/*
* svi_sm_position --
* Return the line number 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.
*/
int
svi_sm_position(sp, ep, lnop, cnt, pos)
SCR *sp;
EXF *ep;
recno_t *lnop;
u_long cnt;
enum position pos;
{
SMAP *p, *t;
recno_t down, last;
switch (pos) {
case P_TOP:
/*
* Note, the top line number may not be at the top of the
* screen, because we search for a line that starts on the
* screen. It works that way because that's how the historic
* vi behaved.
*
* Set t to point at the map entry one past the last legal
* entry in the map.
*/
if (file_lline(sp, ep, &last))
return (1);
if (TMAP->lno <= last)
t = TMAP + 1;
else
for (t = HMAP; t->lno <= last; ++t);
/* Step past cnt start-of-lines, stopping at t. */
for (p = HMAP - 1; cnt; --cnt)
for (;;) {
if (++p == t) {
msgq(sp, M_ERR,
"No such line on the screen.");
return (1);
}
if (p->off == 1)
break;
}
*lnop = p->lno;
break;
case P_MIDDLE:
/*
* Note, the middle line number may not be anywhere near the
* middle of the screen, because that's how the historic vi
* behaved.
*
* Check for less than a full screen of lines.
*/
if (file_lline(sp, ep, &last))
return (1);
if (TMAP->lno < last)
last = TMAP->lno;
down = (last - HMAP->lno + 1) / 2;
if (down == 0 && HMAP->off != 1) {
msgq(sp, M_ERR, "No such line on the screen.");
return (1);
}
*lnop = HMAP->lno + down;
break;
case P_BOTTOM:
/* Set p to point at the last legal entry in the map. */
if (file_lline(sp, ep, &last))
return (1);
if (TMAP->lno <= last)
p = TMAP + 1;
else
for (p = HMAP; p->lno <= last; ++p);
/* Step past cnt start-of-lines, stopping at HMAP. */
for (; cnt; --cnt)
for (;;) {
if (--p < HMAP) {
msgq(sp, M_ERR,
"No such line on the screen.");
return (1);
}
if (p->off == 1)
break;
}
*lnop = p->lno;
break;
default:
abort();
}
return (0);
}
/*
* svi_sm_nlines --
* Return the number of screen lines from an SMAP entry to the
* start of some file line, less than a maximum value.
*/
recno_t
svi_sm_nlines(sp, ep, from_sp, to_lno, max)
SCR *sp;
EXF *ep;
SMAP *from_sp;
recno_t to_lno;
size_t max;
{
recno_t lno, lcnt;
if (O_ISSET(sp, O_LEFTRIGHT))
if (from_sp->lno > to_lno)
return (from_sp->lno - to_lno);
else
return (to_lno - from_sp->lno);
if (from_sp->lno == to_lno)
return (from_sp->off - 1);
if (from_sp->lno > to_lno) {
lcnt = from_sp->off - 1; /* Correct for off-by-one. */
for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;)
lcnt += svi_screens(sp, ep, lno, NULL);
} else {
lno = from_sp->lno;
lcnt = (svi_screens(sp, ep, lno, NULL) - from_sp->off) + 1;
for (; ++lno < to_lno && lcnt <= max;)
lcnt += svi_screens(sp, ep, lno, NULL);
}
return (lcnt);
}