diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 1995-10-18 08:53:40 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 1995-10-18 08:53:40 +0000 |
commit | d6583bb2a13f329cf0332ef2570eb8bb8fc0e39c (patch) | |
tree | ece253b876159b39c620e62b6c9b1174642e070e /usr.bin/vi/svi |
initial import of NetBSD tree
Diffstat (limited to 'usr.bin/vi/svi')
-rw-r--r-- | usr.bin/vi/svi/svi_confirm.c | 95 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_curses.c | 252 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_ex.c | 650 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_get.c | 161 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_line.c | 441 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_refresh.c | 818 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_relative.c | 334 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_screen.c | 336 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_screen.h | 262 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_smap.c | 1216 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_split.c | 635 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_term.c | 310 | ||||
-rw-r--r-- | usr.bin/vi/svi/svi_util.c | 347 |
13 files changed, 5857 insertions, 0 deletions
diff --git a/usr.bin/vi/svi/svi_confirm.c b/usr.bin/vi/svi/svi_confirm.c new file mode 100644 index 00000000000..e3f703ed1a7 --- /dev/null +++ b/usr.bin/vi/svi/svi_confirm.c @@ -0,0 +1,95 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * 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_confirm.c 8.10 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <termios.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "svi_screen.h" + +enum confirm +svi_confirm(sp, ep, fp, tp) + SCR *sp; + EXF *ep; + MARK *fp, *tp; +{ + CH ikey; + size_t oldy, oldx; + + /* + * Refresh the cursor first -- this means that we won't have to + * set S_UPDATE_MODE to keep refresh from erasing the mode line + * or SVI_CUR_INVALID because we sneaked the cursor off somewhere + * else. + */ + sp->lno = fp->lno; + sp->cno = fp->cno; + if (svi_paint(sp, ep)) + return (CONF_QUIT); + + getyx(stdscr, oldy, oldx); + MOVE(sp, INFOLINE(sp), 0); + clrtoeol(); + ADDNSTR(STR_CONFIRM, sizeof(STR_CONFIRM) - 1); + MOVEA(sp, oldy, oldx); + refresh(); + + if (term_key(sp, &ikey, 0) != INP_OK) + return (CONF_QUIT); + switch (ikey.ch) { + case CH_YES: + return (CONF_YES); + case CH_QUIT: + return (CONF_QUIT); + default: + case CH_NO: + return (CONF_NO); + } + /* NOTREACHED */ +} diff --git a/usr.bin/vi/svi/svi_curses.c b/usr.bin/vi/svi/svi_curses.c new file mode 100644 index 00000000000..6fec89550fb --- /dev/null +++ b/usr.bin/vi/svi/svi_curses.c @@ -0,0 +1,252 @@ +/*- + * Copyright (c) 1993, 1994 + * 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_curses.c 8.5 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "svi_screen.h" + +/* + * svi_curses_init -- + * Initialize curses. + */ +int +svi_curses_init(sp) + SCR *sp; +{ + struct termios t; + char *p; + +#ifdef SYSV_CURSES + /* + * The SunOS/System V initscr() isn't reentrant. Don't even think + * about trying to use it. It fails in subtle ways (e.g. select(2) + * on fileno(stdin) stops working). We don't care about the SCREEN + * reference returned by newterm, we never have more than one SCREEN + * at a time. + */ + errno = 0; + if (newterm(O_STR(sp, O_TERM), stdout, stdin) == NULL) { + msgq(sp, errno ? M_SYSERR : M_ERR, "newterm failed"); + return (1); + } +#else + /* + * Initscr() doesn't provide useful error values or messages. The + * reasonable guess is that either malloc failed or the terminal was + * unknown or lacking some essential feature. Try and guess so the + * user isn't even more pissed off because of the error message. + */ + errno = 0; + if (initscr() == NULL) { + char kbuf[2048]; + msgq(sp, errno ? M_SYSERR : M_ERR, "initscr failed"); + if ((p = getenv("TERM")) == NULL || !strcmp(p, "unknown")) + msgq(sp, M_ERR, + "No TERM environment variable set, or TERM set to \"unknown\""); + else if (tgetent(kbuf, p) != 1) + msgq(sp, M_ERR, +"%s: unknown terminal type, or terminal lacks necessary features", p); + else + msgq(sp, M_ERR, + "%s: terminal type lacks necessary features", p); + return (1); + } +#endif + /* + * We use raw mode. What we want is 8-bit clean, however, signals + * and flow control should continue to work. Admittedly, it sounds + * like cbreak, but it isn't. Using cbreak() can get you additional + * things like IEXTEN, which turns on things like DISCARD and LNEXT. + * + * !!! + * If raw isn't turning off echo and newlines, something's wrong. + * However, it doesn't hurt. + */ + noecho(); /* No character echo. */ + nonl(); /* No CR/NL translation. */ + raw(); /* 8-bit clean. */ + idlok(stdscr, 1); /* Use hardware insert/delete line. */ + + /* + * XXX + * Historic implementations of curses handled SIGTSTP signals + * in one of three ways. They either: + * + * 1: Set their own handler, regardless. + * 2: Did not set a handler if a handler was already installed. + * 3: Set their own handler, but then called any previously set + * handler after completing their own cleanup. + * + * We don't try and figure out which behavior is in place, we + * just set it to SIG_DFL after initializing the curses interface. + */ + (void)signal(SIGTSTP, SIG_DFL); + + /* + * If flow control was on, turn it back on. Turn signals on. ISIG + * turns on VINTR, VQUIT, VDSUSP and VSUSP. See signal.c:sig_init() + * for a discussion of what's going on here. To sum up, sig_init() + * already installed a handler for VINTR. We're going to disable the + * other three. + * + * XXX + * We want to use ^Y as a vi scrolling command. If the user has the + * DSUSP character set to ^Y (common practice) clean it up. As it's + * equally possible that the user has VDSUSP set to 'a', we disable + * it regardless. It doesn't make much sense to suspend vi at read, + * so I don't think anyone will care. Alternatively, we could look + * it up in the table of legal command characters and turn it off if + * it matches one. VDSUSP wasn't in POSIX 1003.1-1990, so we test for + * it. + * + * XXX + * We don't check to see if the user had signals enabled to start with. + * If they didn't, it's unclear what we're supposed to do here, but it + * is also pretty unlikely. + */ + if (!tcgetattr(STDIN_FILENO, &t)) { + if (sp->gp->original_termios.c_iflag & IXON) + t.c_iflag |= IXON; + if (sp->gp->original_termios.c_iflag & IXOFF) + t.c_iflag |= IXOFF; + + t.c_lflag |= ISIG; +#ifdef VDSUSP + t.c_cc[VDSUSP] = _POSIX_VDISABLE; +#endif + t.c_cc[VQUIT] = _POSIX_VDISABLE; + t.c_cc[VSUSP] = _POSIX_VDISABLE; + + (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &t); + } + + /* Put the cursor keys into application mode. */ + svi_keypad(sp, 1); + + /* + * The first screen in the list gets it all. All other screens + * are hidden and lose their maps. + */ + svi_dtoh(sp, "Window resize"); + + /* Initialize terminal values. */ + SVP(sp)->srows = O_VAL(sp, O_LINES); + + /* + * Initialize screen values. + * + * Small windows: see svi/svi_refresh.c:svi_refresh, section 3b. + * + * Setup: + * t_minrows is the minimum rows to display + * t_maxrows is the maximum rows to display (rows - 1) + * t_rows is the rows currently being displayed + */ + sp->rows = SVP(sp)->srows; + sp->cols = O_VAL(sp, O_COLUMNS); + sp->woff = 0; + sp->t_rows = sp->t_minrows = O_VAL(sp, O_WINDOW); + if (sp->t_rows > sp->rows - 1) { + sp->t_minrows = sp->t_rows = sp->rows - 1; + msgq(sp, M_INFO, + "Windows option value is too large, max is %u", sp->t_rows); + } + sp->t_maxrows = sp->rows - 1; + + /* Create the screen map. */ + CALLOC(sp, HMAP, SMAP *, SIZE_HMAP(sp), sizeof(SMAP)); + if (HMAP == NULL) { + if (endwin() == ERR) + msgq(sp, M_SYSERR, "endwin"); + return (1); + } + TMAP = HMAP + (sp->t_rows - 1); + + F_SET(SVP(sp), SVI_CUR_INVALID); /* Cursor is invalid. */ + F_SET(SVP(sp), SVI_CURSES_INIT); /* It's initialized. */ + + return (0); +} + +/* + * svi_curses_end -- + * Move to the bottom of the screen, end curses. + */ +int +svi_curses_end(sp) + SCR *sp; +{ + /* + * XXX + * By the time we get here, the screen private area (SVI_PRIVATE) + * is probably gone. Don't use it, and don't call any routines + * that do. + * + * Restore the cursor keys to normal mode. + */ + svi_keypad(sp, 0); + + /* Move to the bottom of the screen. */ + if (move(INFOLINE(sp), 0) == OK) { + clrtoeol(); + refresh(); + } + + /* End curses window. */ + if (endwin() == ERR) + msgq(sp, M_SYSERR, "endwin"); + + return (0); +} diff --git a/usr.bin/vi/svi/svi_ex.c b/usr.bin/vi/svi/svi_ex.c new file mode 100644 index 00000000000..0d20b013c4c --- /dev/null +++ b/usr.bin/vi/svi/svi_ex.c @@ -0,0 +1,650 @@ +/*- + * Copyright (c) 1993, 1994 + * 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_ex.c 8.56 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "../vi/vcmd.h" +#include "excmd.h" +#include "svi_screen.h" +#include "../sex/sex_screen.h" + +static int svi_ex_divider __P((SCR *)); +static int svi_ex_done __P((SCR *, EXF *, MARK *)); +static int svi_ex_inv __P((SCR *)); +static int svi_ex_scroll __P((SCR *, int, CH *)); + +#define MSGS_WAITING(sp) \ + ((sp)->msgq.lh_first != NULL && \ + !F_ISSET((sp)->msgq.lh_first, M_EMPTY)) + +/* + * svi_ex_cmd -- + * Execute an ex command. + */ +int +svi_ex_cmd(sp, ep, exp, rp) + SCR *sp; + EXF *ep; + EXCMDARG *exp; + MARK *rp; +{ + SVI_PRIVATE *svp; + int rval; + + svp = SVP(sp); + svp->exlcontinue = svp->exlinecount = svp->extotalcount = 0; + + (void)svi_busy(sp, NULL); + rval = exp->cmd->fn(sp, ep, exp); + + (void)msg_rpt(sp, 0); + (void)ex_fflush(EXCOOKIE); + + /* + * If displayed anything, figure out if we have to wait. If the + * screen wasn't trashed, only one line output and there are no + * waiting messages, don't wait, but don't overwrite it with mode + * information either. + */ + if (svp->extotalcount > 0) + if (!F_ISSET(sp, S_REFRESH) && + svp->extotalcount == 1 && !MSGS_WAITING(sp)) { + F_SET(sp, S_UPDATE_MODE); + if (sp->q.cqe_next != (void *)&sp->gp->dq) + (void)svi_ex_inv(sp); + } else { + /* This message isn't interruptible. */ + F_CLR(sp, S_INTERRUPTIBLE); + (void)svi_ex_scroll(sp, 1, NULL); + } + return (svi_ex_done(sp, ep, rp) || rval); +} + +/* + * svi_ex_run -- + * Execute strings of ex commands. + */ +int +svi_ex_run(sp, ep, rp) + SCR *sp; + EXF *ep; + MARK *rp; +{ + enum input (*get) __P((SCR *, EXF *, TEXTH *, ARG_CHAR_T, u_int)); + struct termios t; + CH ikey; + SVI_PRIVATE *svp; + TEXT *tp; + int flags, in_exmode, rval; + + svp = SVP(sp); + svp->exlcontinue = svp->exlinecount = svp->extotalcount = 0; + + /* + * There's some tricky stuff going on here to handle when a user has + * mapped a key to multiple ex commands. Historic practice was that + * vi ran without any special actions, as if the user were entering + * the characters, until ex trashed the screen, e.g. something like a + * '!' command. At that point, we no longer know what the screen + * looks like, so we can't afford to overwrite anything. The solution + * is to go into real ex mode until we get to the end of the command + * strings. + */ + get = svi_get; + flags = TXT_BS | TXT_PROMPT; + for (in_exmode = rval = 0;;) { + /* + * Get the next command. Interrupt flag manipulation is safe + * because ex_icmd clears them all. + */ + F_SET(sp, S_INTERRUPTIBLE); + if (get(sp, ep, sp->tiqp, ':', flags) != INP_OK) { + rval = 1; + break; + } + if (INTERRUPTED(sp)) + break; + + /* + * Len is 0 if the user backspaced over the prompt, + * 1 if only a CR was entered. + */ + tp = sp->tiqp->cqh_first; + if (tp->len == 0) + break; + + if (!in_exmode) + (void)svi_busy(sp, NULL); + + /* Ignore return, presumably an error message was displayed. */ + (void)ex_icmd(sp, ep, tp->lb, tp->len, 0); + (void)ex_fflush(EXCOOKIE); + + /* + * The file or screen may have changed, in which case, the + * main editor loop takes care of it. + */ + if (F_ISSET(sp, S_MAJOR_CHANGE)) + break; + + /* + * If continue not required, and one or no lines, and there + * are no waiting messages, don't wait, but don't overwrite + * it with mode information either. + */ + if (!F_ISSET(sp, S_CONTINUE) && (svp->extotalcount == 0 || + svp->extotalcount == 1 && !MSGS_WAITING(sp))) { + if (svp->extotalcount == 1) { + F_SET(sp, S_UPDATE_MODE); + if (sp->q.cqe_next != (void *)&sp->gp->dq) + svi_ex_inv(sp); + } + break; + } + + if (INTERRUPTED(sp)) + break; + + /* + * If the screen is trashed, or there are messages waiting, + * go into ex mode. + */ + if (!in_exmode && + (F_ISSET(sp, S_REFRESH) || MSGS_WAITING(sp))) { + /* Initialize the terminal state. */ + if (F_ISSET(sp->gp, G_STDIN_TTY)) + SEX_RAW(t); + get = sex_get; + flags = TXT_CR | TXT_NLECHO | TXT_PROMPT; + in_exmode = 1; + } + + /* Display any waiting messages. */ + if (MSGS_WAITING(sp)) + (void)sex_refresh(sp, ep); + + /* + * Get a continue character; users may continue in ex mode by + * entering a ':'. + * + * !!! + * Historic practice is that any key can be used to continue. + * Nvi used to require that the user enter a <carriage-return> + * or <newline>, but this broke historic users. + */ + if (in_exmode) { + (void)write(STDOUT_FILENO, + STR_CMSG, sizeof(STR_CMSG) - 1); + if (term_key(sp, &ikey, 0) != INP_OK) { + rval = 1; + goto ret; + } + } else { + /* This message isn't interruptible. */ + F_CLR(sp, S_INTERRUPTIBLE); + (void)svi_ex_scroll(sp, 1, &ikey); + } + if (ikey.ch != ':') + break; + + if (in_exmode) + (void)write(STDOUT_FILENO, "\n", 1); + else { + ++svp->extotalcount; + ++svp->exlinecount; + } + } + +ret: if (in_exmode) { + /* Reset the terminal state. */ + if (F_ISSET(sp->gp, G_STDIN_TTY) && SEX_NORAW(t)) + rval = 1; + F_SET(sp, S_REFRESH); + } else + if (svi_ex_done(sp, ep, rp)) + rval = 1; + + F_CLR(sp, S_CONTINUE); + return (rval); +} + +/* + * svi_msgflush -- + * Flush any accumulated messages. + */ +int +svi_msgflush(sp) + SCR *sp; +{ + enum {INVERSE, NORMAL} inverse; + SVI_PRIVATE *svp; + MSG *mp; + int rval; + + svp = SVP(sp); + svp->exlcontinue = svp->exlinecount = svp->extotalcount = 0; + + /* + * XXX + * S_IVIDEO is a bit of a kluge. We can only pass a single magic + * cookie into the svi_ex_write routine, and it has to be the SCR + * structure. So, the inverse video bit has to be there. + */ + inverse = NORMAL; + for (mp = sp->msgq.lh_first; + mp != NULL && !F_ISSET(mp, M_EMPTY); mp = mp->q.le_next) { + /* + * If the second and subsequent messages fit on the current + * line, write a separator. Otherwise, put out a newline + * and break the line. + */ + if (mp != sp->msgq.lh_first) + if (mp->len + svp->exlcontinue + 3 >= sp->cols) { + if (inverse == INVERSE) + F_SET(sp, S_IVIDEO); + (void)svi_ex_write(sp, ".\n", 2); + F_CLR(sp, S_IVIDEO); + } else { + if (inverse == INVERSE) + F_SET(sp, S_IVIDEO); + (void)svi_ex_write(sp, ";", 1); + F_CLR(sp, S_IVIDEO); + (void)svi_ex_write(sp, " ", 2); + } + + inverse = F_ISSET(mp, M_INV_VIDEO) ? INVERSE : NORMAL; + if (inverse == INVERSE) + F_SET(sp, S_IVIDEO); + (void)svi_ex_write(sp, mp->mbuf, mp->len); + F_CLR(sp, S_IVIDEO); + + F_SET(mp, M_EMPTY); + } + + /* + * None of the messages end with periods, we do it in the message + * flush routine, which makes it possible to join messages. + */ + if (inverse == INVERSE) + F_SET(sp, S_IVIDEO); + (void)svi_ex_write(sp, ".", 1); + F_CLR(sp, S_IVIDEO); + + /* + * Figure out if we have to wait. Don't wait for only one line, + * but don't overwrite it with mode information either. + */ + if (svp->extotalcount == 1) { + F_SET(sp, S_UPDATE_MODE); + if (sp->q.cqe_next != (void *)&sp->gp->dq) + svi_ex_inv(sp); + return (0); + } + + rval = svi_ex_scroll(sp, 1, NULL); + if (svi_ex_done(sp, sp->ep, NULL)) + rval = 1; + MOVE(sp, INFOLINE(sp), 0); + clrtoeol(); + return (rval); +} + +/* + * svi_ex_done -- + * Cleanup from dipping into ex. + */ +static int +svi_ex_done(sp, ep, rp) + SCR *sp; + EXF *ep; + MARK *rp; +{ + SMAP *smp; + SVI_PRIVATE *svp; + recno_t lno; + size_t cnt, len; + + /* + * The file or screen may have changed, in which case, + * the main editor loop takes care of it. + */ + if (F_ISSET(sp, S_MAJOR_CHANGE)) + return (0); + + /* + * Otherwise, the only cursor modifications will be real, however, the + * underlying line may have changed; don't trust anything. This code + * has been a remarkably fertile place for bugs. + * + * Repaint the entire screen if at least half the screen is trashed. + * Else, repaint only over the overwritten lines. The "-2" comes + * from one for the mode line and one for the fact that it's an offset. + * Note the check for small screens. + * + * Don't trust ANYTHING. + */ + svp = SVP(sp); + if (svp->extotalcount >= HALFTEXT(sp)) + F_SET(sp, S_REDRAW); + else + for (cnt = sp->rows - 2; svp->extotalcount--; --cnt) + if (cnt > sp->t_rows) { + MOVE(sp, cnt, 0); + clrtoeol(); + } else { + smp = HMAP + cnt; + SMAP_FLUSH(smp); + if (svi_line(sp, ep, smp, NULL, NULL)) + return (1); + } + + /* Ignore the cursor if the caller doesn't care. */ + if (rp == NULL) + return (0); + + /* + * Do a reality check on a cursor value, and make sure it's okay. + * If necessary, change it. Ex keeps track of the line number, + * but it doesn't care about the column and it may have disappeared. + */ + if (file_gline(sp, ep, sp->lno, &len) == NULL) { + if (file_lline(sp, ep, &lno)) + return (1); + if (lno != 0) + GETLINE_ERR(sp, sp->lno); + sp->lno = 1; + sp->cno = 0; + } else if (sp->cno >= len) + sp->cno = len ? len - 1 : 0; + + rp->lno = sp->lno; + rp->cno = sp->cno; + return (0); +} + +/* + * svi_ex_write -- + * Write out the ex messages. + */ +int +svi_ex_write(cookie, line, llen) + void *cookie; + const char *line; + int llen; +{ + SCR *sp; + SVI_PRIVATE *svp; + size_t oldy, oldx; + int len, rlen, tlen; + const char *p, *t; + + /* + * XXX + * If it's a 4.4BSD system, we could just use fpurge(3). + * This shouldn't be too expensive, though. + */ + sp = cookie; + svp = SVP(sp); + if (INTERRUPTED(sp)) + return (llen); + + p = line; /* In case of a write of 0. */ + for (rlen = llen; llen;) { + /* Get the next line. */ + if ((p = memchr(line, '\n', llen)) == NULL) + len = llen; + else + len = p - line; + + /* + * The max is sp->cols characters, and we may + * have already written part of the line. + */ + if (len + svp->exlcontinue > sp->cols) + len = sp->cols - svp->exlcontinue; + + /* + * If the first line output, do nothing. + * If the second line output, draw the divider line. + * If drew a full screen, remove the divider line. + * If it's a continuation line, move to the continuation + * point, else, move the screen up. + */ + if (svp->exlcontinue == 0) { + if (svp->extotalcount == 1) { + MOVE(sp, INFOLINE(sp) - 1, 0); + clrtoeol(); + if (svi_ex_divider(sp)) + return (-1); + F_SET(svp, SVI_DIVIDER); + ++svp->extotalcount; + ++svp->exlinecount; + } + if (svp->extotalcount == sp->t_maxrows && + F_ISSET(svp, SVI_DIVIDER)) { + --svp->extotalcount; + --svp->exlinecount; + F_CLR(svp, SVI_DIVIDER); + } + if (svp->extotalcount != 0 && + svi_ex_scroll(sp, 0, NULL)) + return (-1); + MOVE(sp, INFOLINE(sp), 0); + ++svp->extotalcount; + ++svp->exlinecount; + if (F_ISSET(sp, S_INTERRUPTIBLE) && INTERRUPTED(sp)) + break; + } else + MOVE(sp, INFOLINE(sp), svp->exlcontinue); + + /* Display the line, doing character translation. */ + if (F_ISSET(sp, S_IVIDEO)) + standout(); + for (t = line, tlen = len; tlen--; ++t) + ADDCH(*t); + if (F_ISSET(sp, S_IVIDEO)) + standend(); + + /* Clear to EOL. */ + getyx(stdscr, oldy, oldx); + if (oldx < sp->cols) + clrtoeol(); + + /* If we loop, it's a new line. */ + svp->exlcontinue = 0; + + /* Reset for the next line. */ + line += len; + llen -= len; + if (p != NULL) { + ++line; + --llen; + } + } + /* Refresh the screen, even if it's a partial. */ + refresh(); + + /* Set up next continuation line. */ + if (p == NULL) + getyx(stdscr, oldy, svp->exlcontinue); + return (rlen); +} + +/* + * svi_ex_scroll -- + * Scroll the screen for ex output. + */ +static int +svi_ex_scroll(sp, mustwait, chp) + SCR *sp; + int mustwait; + CH *chp; +{ + CH ikey; + SVI_PRIVATE *svp; + + /* + * Scroll the screen. Instead of scrolling the entire screen, delete + * the line above the first line output so preserve the maximum amount + * of the screen. + */ + svp = SVP(sp); + if (svp->extotalcount >= sp->rows) { + MOVE(sp, 0, 0); + } else + MOVE(sp, INFOLINE(sp) - svp->extotalcount, 0); + + deleteln(); + + /* If there are screens below us, push them back into place. */ + if (sp->q.cqe_next != (void *)&sp->gp->dq) { + MOVE(sp, INFOLINE(sp), 0); + insertln(); + } + + /* If just displayed a full screen, wait. */ + if (mustwait || svp->exlinecount == sp->t_maxrows) { + MOVE(sp, INFOLINE(sp), 0); + if (F_ISSET(sp, S_INTERRUPTIBLE)) { + ADDNSTR(STR_QMSG, (int)sizeof(STR_QMSG) - 1); + } else { + ADDNSTR(STR_CMSG, (int)sizeof(STR_CMSG) - 1); + } + clrtoeol(); + refresh(); + /* + * !!! + * Historic practice is that any key can be used to continue. + * Nvi used to require that the user enter a <carriage-return> + * or <newline>, but this broke historic users. + */ + if (term_key(sp, &ikey, 0) != INP_OK) + return (-1); + if (ikey.ch == CH_QUIT && F_ISSET(sp, S_INTERRUPTIBLE)) + F_SET(sp, S_INTERRUPTED); + if (chp != NULL) + *chp = ikey; + svp->exlinecount = 0; + } + return (0); +} + +/* + * svi_ex_inv -- + * Change whatever is on the info line to inverse video so we have + * a divider line between split screens. + */ +static int +svi_ex_inv(sp) + SCR *sp; +{ + CHAR_T ch; + size_t spcnt, col, row; + + row = INFOLINE(sp); + + /* + * Walk through the line, retrieving each character and writing + * it back out in inverse video. Since curses doesn't have an + * EOL marker, only put out trailing spaces if we find another + * character. + * + * XXX + * This is a major kluge -- curses should have an interface + * that allows us to change attributes on a per line basis. + */ + MOVE(sp, row, 0); + standout(); + for (spcnt = col = 0;;) { + ch = winch(stdscr); + if (isspace(ch)) { + ++spcnt; + if (++col >= sp->cols) + break; + MOVE(sp, row, col); + } else { + if (spcnt) { + MOVE(sp, row, col - spcnt); + for (; spcnt > 0; --spcnt) + ADDCH(' '); + } + ADDCH(ch); + if (++col >= sp->cols) + break; + } + } + standend(); + return (0); +} + +/* + * svi_ex_divider -- + * Draw a dividing line between the screens. + */ +static int +svi_ex_divider(sp) + SCR *sp; +{ + size_t len; + +#define DIVIDESTR "+=+=+=+=+=+=+=+" + len = sizeof(DIVIDESTR) - 1 > sp->cols ? + sp->cols : sizeof(DIVIDESTR) - 1; + standout(); + ADDNSTR(DIVIDESTR, len); + standend(); + return (0); +} diff --git a/usr.bin/vi/svi/svi_get.c b/usr.bin/vi/svi/svi_get.c new file mode 100644 index 00000000000..b58100b21e0 --- /dev/null +++ b/usr.bin/vi/svi/svi_get.c @@ -0,0 +1,161 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * 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_get.c 8.27 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <termios.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "../vi/vcmd.h" +#include "svi_screen.h" + +/* + * svi_get -- + * Fill a buffer from the terminal for vi. + */ +enum input +svi_get(sp, ep, tiqh, prompt, flags) + SCR *sp; + EXF *ep; + TEXTH *tiqh; + ARG_CHAR_T prompt; + u_int flags; +{ + MARK save; + SMAP *esmp; + recno_t bot_lno; + size_t bot_off, cnt; + int eval; + + /* + * The approach used is to fake like the user is doing input on + * the last line of the screen. This makes all of the scrolling + * work correctly, and allows us the use of the vi text editing + * routines, not to mention practically infinite length ex commands. + * + * Save the current location. + */ + bot_lno = TMAP->lno; + bot_off = TMAP->off; + save.lno = sp->lno; + save.cno = sp->cno; + + /* + * If it's a small screen, TMAP may be small for the screen. + * Fix it, filling in fake lines as we go. + */ + if (ISSMALLSCREEN(sp)) + for (esmp = HMAP + (sp->t_maxrows - 1); TMAP < esmp; ++TMAP) { + TMAP[1].lno = TMAP[0].lno + 1; + TMAP[1].off = 1; + } + + /* Build the fake entry. */ + TMAP[1].lno = TMAP[0].lno + 1; + TMAP[1].off = 1; + SMAP_FLUSH(&TMAP[1]); + ++TMAP; + + /* Move to it. */ + sp->lno = TMAP[0].lno; + sp->cno = 0; + + if (O_ISSET(sp, O_ALTWERASE)) + LF_SET(TXT_ALTWERASE); + if (O_ISSET(sp, O_TTYWERASE)) + LF_SET(TXT_TTYWERASE); + LF_SET(TXT_APPENDEOL | + TXT_CR | TXT_ESCAPE | TXT_INFOLINE | TXT_MAPINPUT); + + /* Don't update the modeline for now. */ + F_SET(SVP(sp), SVI_INFOLINE); + + eval = v_ntext(sp, ep, tiqh, NULL, NULL, 0, NULL, prompt, 0, flags); + + F_CLR(SVP(sp), SVI_INFOLINE); + + /* Put it all back. */ + --TMAP; + sp->lno = save.lno; + sp->cno = save.cno; + + /* + * If it's a small screen, TMAP may be wrong. Clear any + * lines that might have been overwritten. + */ + if (ISSMALLSCREEN(sp)) { + for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) { + MOVE(sp, cnt, 0); + clrtoeol(); + } + TMAP = HMAP + (sp->t_rows - 1); + } + + /* + * The map may be wrong if the user entered more than one + * (logical) line. Fix it. If the user entered a whole + * screen, this will be slow, but it's not worth caring. + */ + while (bot_lno != TMAP->lno || bot_off != TMAP->off) + if (svi_sm_1down(sp, ep)) + return (INP_ERR); + + /* + * Invalidate the cursor and the line size cache, the line never + * really existed. This fixes bugs where the user searches for + * the last line on the screen + 1 and the refresh routine thinks + * that's where we just were. + */ + F_SET(SVP(sp), SVI_CUR_INVALID); + SVI_SCR_CFLUSH(SVP(sp)); + + return (eval ? INP_ERR : INP_OK); +} diff --git a/usr.bin/vi/svi/svi_line.c b/usr.bin/vi/svi/svi_line.c new file mode 100644 index 00000000000..e1d50da122d --- /dev/null +++ b/usr.bin/vi/svi/svi_line.c @@ -0,0 +1,441 @@ +/*- + * Copyright (c) 1993, 1994 + * 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_line.c 8.27 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <termios.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "svi_screen.h" + +#if defined(DEBUG) && 0 +#define TABCH '-' +#define TABSTR "--------------------" +#else +#define TABSTR " " +#define TABCH ' ' +#endif + +/* + * svi_line -- + * Update one line on the screen. + */ +int +svi_line(sp, ep, smp, yp, xp) + SCR *sp; + EXF *ep; + SMAP *smp; + size_t *xp, *yp; +{ + SMAP *tsmp; + size_t chlen, cols_per_screen, cno_cnt, len, scno, skip_screens; + size_t offset_in_char, offset_in_line; + size_t oldy, oldx; + int ch, is_cached, is_infoline, is_partial, is_tab; + int list_tab, list_dollar; + char *p, nbuf[10]; + +#if defined(DEBUG) && 0 + TRACE(sp, "svi_line: row %u: line: %u off: %u\n", + smp - HMAP, smp->lno, smp->off); +#endif + + /* + * Assume that, if the cache entry for the line is filled in, the + * line is already on the screen, and all we need to do is return + * the cursor position. If the calling routine doesn't need the + * cursor position, we can just return. + */ + is_cached = SMAP_CACHE(smp); + if (yp == NULL && is_cached) + return (0); + + /* + * A nasty side effect of this routine is that it returns the screen + * position for the "current" character. Not pretty, but this is the + * only routine that really knows what's out there. + * + * Move to the line. This routine can be called by svi_sm_position(), + * which uses it to fill in the cache entry so it can figure out what + * the real contents of the screen are. Because of this, we have to + * return to whereever we started from. + */ + getyx(stdscr, oldy, oldx); + MOVE(sp, smp - HMAP, 0); + + /* Get a copy of the line. */ + p = file_gline(sp, ep, smp->lno, &len); + + /* + * Special case if we're printing the info/mode line. Skip printing + * the leading number, as well as other minor setup. If painting the + * line between two screens, it's always in reverse video. The only + * time this code paints the mode line is when the user is entering + * text for a ":" command, so we can put the code here instead of + * dealing with the empty line logic below. This is a kludge, but it's + * pretty much confined to this module. + * + * Set the number of screens to skip until a character is displayed. + * Left-right screens are special, because we don't bother building + * a buffer to be skipped over. + * + * Set the number of columns for this screen. + */ + cols_per_screen = sp->cols; + list_tab = O_ISSET(sp, O_LIST); + if (is_infoline = ISINFOLINE(sp, smp)) { + list_dollar = 0; + if (O_ISSET(sp, O_LEFTRIGHT)) + skip_screens = 0; + else + skip_screens = smp->off - 1; + } else { + list_dollar = list_tab; + skip_screens = smp->off - 1; + + /* + * If O_NUMBER is set and it's line number 1 or the line exists + * and this is the first screen of a folding line or any left- + * right line, display the line number. + */ + if (O_ISSET(sp, O_NUMBER)) { + cols_per_screen -= O_NUMBER_LENGTH; + if ((smp->lno == 1 || p != NULL) && skip_screens == 0) { + (void)snprintf(nbuf, + sizeof(nbuf), O_NUMBER_FMT, smp->lno); + ADDSTR(nbuf); + } + } + } + + /* + * Special case non-existent lines and the first line of an empty + * file. In both cases, the cursor position is 0, but corrected + * for the O_NUMBER field if it was displayed. + */ + if (p == NULL || len == 0) { + /* Fill in the cursor. */ + if (yp != NULL && smp->lno == sp->lno) { + *yp = smp - HMAP; + *xp = sp->cols - cols_per_screen; + } + + /* If the line is on the screen, quit. */ + if (is_cached) + goto ret; + + /* Set line cacheing information. */ + smp->c_sboff = smp->c_eboff = 0; + smp->c_scoff = smp->c_eclen = 0; + + /* Lots of special cases for empty lines. */ + if (skip_screens == 0) + if (p == NULL) { + if (smp->lno == 1) { + if (list_dollar) { + ch = '$'; + goto empty; + } + } else { + ch = '~'; + goto empty; + } + } else + if (list_dollar) { + ch = '$'; +empty: ADDCH(ch); + } + + clrtoeol(); + MOVEA(sp, oldy, oldx); + return (0); + } + + /* + * If we wrote a line that's this or a previous one, we can do this + * much more quickly -- we cached the starting and ending positions + * of that line. The way it works is we keep information about the + * lines displayed in the SMAP. If we're painting the screen in + * the forward, this saves us from reformatting the physical line for + * every line on the screen. This wins big on binary files with 10K + * lines. + * + * Test for the first screen of the line, then the current screen line, + * then the line behind us, then do the hard work. Note, it doesn't + * do us any good to have a line in front of us -- it would be really + * hard to try and figure out tabs in the reverse direction, i.e. how + * many spaces a tab takes up in the reverse direction depends on + * what characters preceded it. + */ + if (smp->off == 1) { + smp->c_sboff = offset_in_line = 0; + smp->c_scoff = offset_in_char = 0; + p = &p[offset_in_line]; + } else if (is_cached) { + offset_in_line = smp->c_sboff; + offset_in_char = smp->c_scoff; + p = &p[offset_in_line]; + if (skip_screens != 0) + cols_per_screen = sp->cols; + } else if (smp != HMAP && + SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) { + if (tsmp->c_eclen != tsmp->c_ecsize) { + offset_in_line = tsmp->c_eboff; + offset_in_char = tsmp->c_eclen; + } else { + offset_in_line = tsmp->c_eboff + 1; + offset_in_char = 0; + } + + /* Put starting info for this line in the cache. */ + smp->c_sboff = offset_in_line; + smp->c_scoff = offset_in_char; + p = &p[offset_in_line]; + if (skip_screens != 0) + cols_per_screen = sp->cols; + } else { + offset_in_line = 0; + offset_in_char = 0; + + /* This is the loop that skips through screens. */ + if (skip_screens == 0) { + smp->c_sboff = offset_in_line; + smp->c_scoff = offset_in_char; + } else for (scno = 0; offset_in_line < len; ++offset_in_line) { + scno += chlen = + (ch = *(u_char *)p++) == '\t' && !list_tab ? + TAB_OFF(sp, scno) : KEY_LEN(sp, ch); + if (scno < cols_per_screen) + continue; + /* + * Reset cols_per_screen to second and subsequent line + * length. + */ + scno -= cols_per_screen; + cols_per_screen = sp->cols; + + /* + * If crossed the last skipped screen boundary, start + * displaying the characters. + */ + if (--skip_screens) + continue; + + /* Put starting info for this line in the cache. */ + if (scno) { + smp->c_sboff = offset_in_line; + smp->c_scoff = offset_in_char = chlen - scno; + --p; + } else { + smp->c_sboff = ++offset_in_line; + smp->c_scoff = 0; + } + break; + } + } + + /* + * Set the number of characters to skip before reaching the cursor + * character. Offset by 1 and use 0 as a flag value. Svi_line is + * called repeatedly with a valid pointer to a cursor position. + * Don't fill anything in unless it's the right line and the right + * character, and the right part of the character... + */ + if (yp == NULL || + smp->lno != sp->lno || sp->cno < offset_in_line || + offset_in_line + cols_per_screen < sp->cno) { + cno_cnt = 0; + /* If the line is on the screen, quit. */ + if (is_cached) + goto ret; + } else + cno_cnt = (sp->cno - offset_in_line) + 1; + + /* This is the loop that actually displays characters. */ + for (is_partial = 0, scno = 0; + offset_in_line < len; ++offset_in_line, offset_in_char = 0) { + if ((ch = *(u_char *)p++) == '\t' && !list_tab) { + scno += chlen = TAB_OFF(sp, scno) - offset_in_char; + is_tab = 1; + } else { + scno += chlen = KEY_LEN(sp, ch) - offset_in_char; + is_tab = 0; + } + + /* + * Only display up to the right-hand column. Set a flag if + * the entire character wasn't displayed for use in setting + * the cursor. If reached the end of the line, set the cache + * info for the screen. Don't worry about there not being + * characters to display on the next screen, its lno/off won't + * match up in that case. + */ + if (scno >= cols_per_screen) { + smp->c_ecsize = chlen; + chlen -= scno - cols_per_screen; + smp->c_eclen = chlen; + smp->c_eboff = offset_in_line; + if (scno > cols_per_screen) + is_partial = 1; + + /* Terminate the loop. */ + offset_in_line = len; + } + + /* + * If the caller wants the cursor value, and this was the + * cursor character, set the value. There are two ways to + * put the cursor on a character -- if it's normal display + * mode, it goes on the last column of the character. If + * it's input mode, it goes on the first. In normal mode, + * set the cursor only if the entire character was displayed. + */ + if (cno_cnt && + --cno_cnt == 0 && (F_ISSET(sp, S_INPUT) || !is_partial)) { + *yp = smp - HMAP; + if (F_ISSET(sp, S_INPUT)) + *xp = scno - chlen; + else + *xp = scno - 1; + if (O_ISSET(sp, O_NUMBER) && + !is_infoline && smp->off == 1) + *xp += O_NUMBER_LENGTH; + + /* If the line is on the screen, quit. */ + if (is_cached) + goto ret; + } + + /* If the line is on the screen, don't display anything. */ + if (is_cached) + continue; + + /* + * Display the character. If it's a tab and tabs aren't some + * ridiculous length, do it fast. (We do tab expansion here + * because curses doesn't have a way to set the tab length.) + */ + if (is_tab) { + if (chlen <= sizeof(TABSTR) - 1) { + ADDNSTR(TABSTR, chlen); + } else + while (chlen--) + ADDCH(TABCH); + } else + ADDNSTR(KEY_NAME(sp, ch) + offset_in_char, chlen); + } + + if (scno < cols_per_screen) { + /* If didn't paint the whole line, update the cache. */ + smp->c_ecsize = smp->c_eclen = KEY_LEN(sp, ch); + smp->c_eboff = len - 1; + + /* + * If not the info/mode line, and O_LIST set, and at the + * end of the line, and the line ended on this screen, + * add a trailing $. + */ + if (list_dollar) { + ++scno; + ADDCH('$'); + } + + /* If still didn't paint the whole line, clear the rest. */ + if (scno < cols_per_screen) + clrtoeol(); + } + +ret: MOVEA(sp, oldy, oldx); + return (0); +} + +/* + * svi_number -- + * Repaint the numbers on all the lines. + */ +int +svi_number(sp, ep) + SCR *sp; + EXF *ep; +{ + SMAP *smp; + size_t oldy, oldx; + char *lp, nbuf[10]; + + /* + * Try and avoid getting the last line in the file, by getting the + * line after the last line in the screen -- if it exists, we know + * we have to to number all the lines in the screen. Get the one + * after the last instead of the last, so that the info line doesn't + * fool us. + * + * If that test fails, we have to check each line for existence. + * + * XXX + * The problem is that file_lline will lie, and tell us that the + * info line is the last line in the file. + */ + lp = file_gline(sp, ep, TMAP->lno + 1, NULL); + + getyx(stdscr, oldy, oldx); + for (smp = HMAP; smp <= TMAP; ++smp) { + if (smp->off != 1) + continue; + if (ISINFOLINE(sp, smp)) + break; + if (smp->lno != 1 && lp == NULL && + file_gline(sp, ep, smp->lno, NULL) == NULL) + break; + MOVE(sp, smp - HMAP, 0); + (void)snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, smp->lno); + ADDSTR(nbuf); + } + MOVEA(sp, oldy, oldx); + return (0); +} diff --git a/usr.bin/vi/svi/svi_refresh.c b/usr.bin/vi/svi/svi_refresh.c new file mode 100644 index 00000000000..7b3b89fccc0 --- /dev/null +++ b/usr.bin/vi/svi/svi_refresh.c @@ -0,0 +1,818 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * 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.62 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "svi_screen.h" +#include "../sex/sex_screen.h" + +static int svi_modeline __P((SCR *, EXF *)); + +int +svi_refresh(sp, ep) + SCR *sp; + EXF *ep; +{ + SCR *tsp; + u_int paintbits; + + /* + * 1: Resize the screen. + * + * 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)) { + /* Reinitialize curses. */ + if (svi_curses_end(sp) || svi_curses_init(sp)) + return (1); + + /* Invalidate the line size cache. */ + SVI_SCR_CFLUSH(SVP(sp)); + + /* + * Fill the map, incidentally losing any svi_line() + * cached information. + */ + if (svi_sm_fill(sp, ep, sp->lno, P_FILL)) + return (1); + F_CLR(sp, S_RESIZE | S_REFORMAT); + F_SET(sp, S_REDRAW); + } + + /* + * 2: S_REFRESH + * + * If S_REFRESH is set in the current screen, repaint everything + * that we can find. + */ + if (F_ISSET(sp, S_REFRESH)) + for (tsp = sp->gp->dq.cqh_first; + tsp != (void *)&sp->gp->dq; tsp = tsp->q.cqe_next) + if (tsp != sp) + F_SET(tsp, S_REDRAW); + /* + * 3: Related or dirtied screens, or screens with messages. + * + * If related screens share a view into a file, they may have been + * modified as well. Refresh any screens with paint or dirty bits + * set, or where messages are waiting. Finally, if we refresh any + * screens other than the current one, the cursor will be trashed. + */ + paintbits = S_REDRAW | S_REFORMAT | S_REFRESH; + if (O_ISSET(sp, O_NUMBER)) + paintbits |= S_RENUMBER; + for (tsp = sp->gp->dq.cqh_first; + tsp != (void *)&sp->gp->dq; tsp = tsp->q.cqe_next) + if (tsp != sp && + (F_ISSET(tsp, paintbits) || + F_ISSET(SVP(tsp), SVI_SCREENDIRTY) || + tsp->msgq.lh_first != NULL && + !F_ISSET(tsp->msgq.lh_first, M_EMPTY))) { + (void)svi_paint(tsp, tsp->ep); + F_CLR(SVP(tsp), SVI_SCREENDIRTY); + F_SET(SVP(sp), SVI_CUR_INVALID); + } + + /* + * 4: Refresh the current screen. + * + * Always refresh the current screen, it may be a cursor movement. + * Also, always do it last -- that way, S_REFRESH can be set in + * the current screen only, and the screen won't flash. + */ + F_CLR(sp, SVI_SCREENDIRTY); + return (svi_paint(sp, ep)); +} + +/* + * svi_paint -- + * 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_paint(sp, ep) + SCR *sp; + EXF *ep; +{ + SMAP *smp, tmp; + SVI_PRIVATE *svp; + recno_t lastline, lcnt; + size_t cwtotal, cnt, len, x, y; + int ch, didpaint, leftright_warp; + char *p; + +#define LNO sp->lno +#define OLNO svp->olno +#define CNO sp->cno +#define OCNO svp->ocno +#define SCNO svp->sc_col + + didpaint = leftright_warp = 0; + svp = SVP(sp); + + /* + * 1: 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)) { + /* Invalidate the line size cache. */ + SVI_SCR_CFLUSH(SVP(sp)); + + /* Toss svi_line() cached information. */ + if (svi_sm_fill(sp, ep, HMAP->lno, P_TOP)) + return (1); + if (O_ISSET(sp, O_LEFTRIGHT) && + (cnt = svi_opt_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); + } + + /* + * 2: Line movement. + * + * Line changes can cause the top line to change as well. As + * before, if the movement is large, the screen is repainted. + * + * 2a: 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 + * HALFTEXT(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, however. + */ + 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_opt_screens(sp, ep, LNO, &CNO); + goto paint; + } + F_SET(sp, S_REDRAW); + goto adjust; + } + + /* + * 2b: Small screens. + * + * Users can use the window, w300, w1200 and w9600 options to make + * the screen artificially small. The behavior of these options + * in the historic vi wasn't all that consistent, and, in fact, it + * was never documented how various screen movements affected the + * screen size. Generally, one of three things would happen: + * 1: The screen would expand in size, showing the line + * 2: The screen would scroll, showing the line + * 3: The screen would compress to its smallest size and + * repaint. + * In general, scrolling didn't cause compression (200^D was handled + * the same as ^D), movement to a specific line would (:N where N + * was 1 line below the screen caused a screen compress), and cursor + * movement would scroll if it was 11 lines or less, and compress if + * it was more than 11 lines. (And, no, I have no idea where the 11 + * comes from.) + * + * What we do is try and figure out if the line is less than half of + * a full screen away. If it is, we expand the screen if there's + * room, and then scroll as necessary. The alternative is to compress + * and repaint. + * + * !!! + * This code is a special case from beginning to end. Unfortunately, + * home modems are still slow enough that it's worth having. + * + * XXX + * If the line a really long one, i.e. part of the line is on the + * screen but the column offset is not, we'll end up in the adjust + * code, when we should probably have compressed the screen. + */ + if (ISSMALLSCREEN(sp)) + if (LNO < HMAP->lno) { + lcnt = svi_sm_nlines(sp, ep, HMAP, LNO, sp->t_maxrows); + if (lcnt <= HALFSCREEN(sp)) + for (; lcnt && sp->t_rows != sp->t_maxrows; + --lcnt, ++sp->t_rows) { + ++TMAP; + if (svi_sm_1down(sp, ep)) + return (1); + } + else + goto small_fill; + } else if (LNO > TMAP->lno) { + lcnt = svi_sm_nlines(sp, ep, TMAP, LNO, sp->t_maxrows); + if (lcnt <= HALFSCREEN(sp)) + for (; lcnt && sp->t_rows != sp->t_maxrows; + --lcnt, ++sp->t_rows) { + if (svi_sm_next(sp, ep, TMAP, TMAP + 1)) + return (1); + ++TMAP; + if (svi_line(sp, ep, TMAP, NULL, NULL)) + return (1); + } + else { +small_fill: MOVE(sp, INFOLINE(sp), 0); + clrtoeol(); + for (; sp->t_rows > sp->t_minrows; + --sp->t_rows, --TMAP) { + MOVE(sp, TMAP - HMAP, 0); + clrtoeol(); + } + if (svi_sm_fill(sp, ep, LNO, P_FILL)) + return (1); + F_SET(sp, S_REDRAW); + goto adjust; + } + } + + /* + * 3a: Line down, or current screen. + */ + if (LNO >= HMAP->lno) { + /* Current screen. */ + if (LNO <= TMAP->lno) + goto adjust; + + /* + * If less than half a screen above the line, scroll down + * until the line is on the screen. + */ + lcnt = svi_sm_nlines(sp, ep, TMAP, LNO, HALFTEXT(sp)); + if (lcnt < HALFTEXT(sp)) { + while (lcnt--) + if (svi_sm_1up(sp, ep)) + return (1); + goto adjust; + } + goto bottom; + } + + /* + * 3b: Line up. + */ + lcnt = svi_sm_nlines(sp, ep, HMAP, LNO, HALFTEXT(sp)); + if (lcnt < HALFTEXT(sp)) { + /* + * If less than half a screen below the line, scroll up until + * the line is the first line on the screen. Special check so + * that if the screen has been emptied, we refill it. + */ + if (file_gline(sp, ep, HMAP->lno, &len) != NULL) { + while (lcnt--) + if (svi_sm_1down(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. + */ +bottom: 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; + } + /* It's not close, just put the line in the middle. */ + goto middle; + } + + /* + * 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, HALFTEXT(sp)); + if (lcnt < HALFTEXT(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, that's why we test BOTH + * the top and the bottom of the map. This isn't a problem for + * left-right scrolling, the cursor movement code handles the problem. + * + * There's a 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. If the adjustment looks like it's + * going to be a serious problem, refill the screen and repaint. + */ +adjust: if (!O_ISSET(sp, O_LEFTRIGHT) && + (LNO == HMAP->lno || LNO == TMAP->lno)) { + cnt = svi_opt_screens(sp, ep, LNO, &CNO); + if (LNO == HMAP->lno && cnt < HMAP->off) + if ((HMAP->off - cnt) > HALFTEXT(sp)) { + HMAP->off = cnt; + svi_sm_fill(sp, ep, OOBLNO, P_TOP); + F_SET(sp, S_REDRAW); + } else + while (cnt < HMAP->off) + if (svi_sm_1down(sp, ep)) + return (1); + if (LNO == TMAP->lno && cnt > TMAP->off) + if ((cnt - TMAP->off) > HALFTEXT(sp)) { + TMAP->off = cnt; + svi_sm_fill(sp, ep, OOBLNO, P_BOTTOM); + F_SET(sp, S_REDRAW); + } else + while (cnt > TMAP->off) + if (svi_sm_1up(sp, ep)) + return (1); + } + + /* + * If the screen needs to be repainted, skip cursor optimization. + * However, in the code above we skipped leftright scrolling on + * the grounds that the cursor code would handle it. Make sure + * the right screen is up. + */ + if (F_ISSET(sp, S_REDRAW)) { + if (O_ISSET(sp, O_LEFTRIGHT)) { + cnt = svi_opt_screens(sp, ep, LNO, &CNO); + if (HMAP->off != cnt) + for (smp = HMAP; smp <= TMAP; ++smp) + smp->off = cnt; + } + 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. 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(SVP(sp), SVI_CUR_INVALID) || LNO != OLNO) { + F_CLR(SVP(sp), SVI_CUR_INVALID); + goto slow; + } + + /* Otherwise, if nothing's changed, go fast. */ + if (CNO == OCNO) + 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); + } + +#ifdef DEBUG + /* This is just a test. */ + 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. + */ + 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; + + /* + * Quick sanity check -- it's hard to figure out exactly when + * we cross a screen boundary as we do in the cursor right + * movement. If cnt is so large that we're going to cross the + * boundary no matter what, stop now. + */ + if (SCNO + 1 + MAX_CHARACTER_COLUMNS < cnt) + goto lscreen; + + /* + * 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 += KEY_LEN(sp, ch)) + 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 (KEY_LEN(sp, ch) > 1) + cwtotal -= KEY_LEN(sp, ch) - 1; + + /* + * If the new column moved us off of the current logical line, + * calculate a new one. If doing leftright scrolling, we've + * moved off of the current screen, as well. Since most files + * don't have more than two screens, we optimize moving from + * screen 2 to screen 1. + */ + if (SCNO < cwtotal) { +lscreen: if (O_ISSET(sp, O_LEFTRIGHT)) { + cnt = HMAP->off == 2 ? 1 : + svi_opt_screens(sp, ep, LNO, &CNO); + for (smp = HMAP; smp <= TMAP; ++smp) + smp->off = cnt; + leftright_warp = 1; + 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. If we cross a + * screen boundary, we can quit. + */ + for (cwtotal = SCNO; cnt--;) { + if ((ch = *(u_char *)p++) == '\t') + goto slow; + if ((cwtotal += KEY_LEN(sp, ch)) >= SCREEN_COLS(sp)) + break; + } + + /* + * Increment the screen cursor by the total width of the + * characters. + */ + SCNO = cwtotal; + + /* See screen change comment in section 4a. */ + if (SCNO >= SCREEN_COLS(sp)) { + if (O_ISSET(sp, O_LEFTRIGHT)) { + cnt = svi_opt_screens(sp, ep, LNO, &CNO); + for (smp = HMAP; smp <= TMAP; ++smp) + smp->off = cnt; + leftright_warp = 1; + goto paint; + } + goto slow; + } + } + + /* + * 4c: Fast cursor update. + * + * Retrieve the current cursor position, and correct it + * for split screens. + */ +fast: getyx(stdscr, y, x); + y -= sp->woff; + goto number; + + /* + * 4d: Slow cursor update. + * + * Walk through the map and find the current line. If doing left-right + * scrolling and the cursor movement has changed the screen displayed, + * scroll the screen left or right, unless we're updating the info line + * in which case we just scroll that one line. Then update the screen + * lines for this file line until we have a new screen cursor position. + */ +slow: for (smp = HMAP; smp->lno != LNO; ++smp); + if (O_ISSET(sp, O_LEFTRIGHT)) { + cnt = svi_opt_screens(sp, ep, LNO, &CNO) % SCREEN_COLS(sp); + if (cnt != HMAP->off) { + if (ISINFOLINE(sp, smp)) + smp->off = cnt; + else { + for (smp = HMAP; smp <= TMAP; ++smp) + smp->off = cnt; + leftright_warp = 1; + } + goto paint; + } + } + for (y = -1; smp <= TMAP && smp->lno == LNO; ++smp) { + if (svi_line(sp, ep, smp, &y, &SCNO)) + return (1); + if (y != -1) + break; + } + goto number; + + /* + * 5: Repaint the entire screen. + * + * Lost big, do what you have to do. We flush the cache as S_REDRAW + * gets set when the screen isn't worth fixing, and it's simpler to + * repaint. So, don't trust anything that we think we know about it. + */ +paint: for (smp = HMAP; smp <= TMAP; ++smp) + SMAP_FLUSH(smp); + for (smp = HMAP; smp <= TMAP; ++smp) + if (svi_line(sp, ep, smp, &y, &SCNO)) + return (1); + /* + * If it's a small screen and we're redrawing, clear the unused lines, + * ex may have overwritten them. + */ + if (F_ISSET(sp, S_REDRAW)) { + if (ISSMALLSCREEN(sp)) + for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) { + MOVE(sp, cnt, 0); + clrtoeol(); + } + F_CLR(sp, S_REDRAW); + } + + didpaint = 1; + + /* + * 6: Repaint the line numbers. + * + * If O_NUMBER is set and the S_RENUMBER bit is set, and we didn't + * repaint the screen, repaint all of the line numbers, they've + * changed. + */ +number: if (O_ISSET(sp, O_NUMBER) && F_ISSET(sp, S_RENUMBER) && !didpaint) { + if (svi_number(sp, ep)) + return (1); + F_CLR(sp, S_RENUMBER); + } + + /* + * 7: Refresh the screen. + * + * If the screen was corrupted, refresh it. + */ + if (F_ISSET(sp, S_REFRESH)) { + wrefresh(curscr); + F_CLR(sp, S_REFRESH); + } + + if (F_ISSET(sp, S_BELLSCHED)) + svi_bell(sp); + /* + * If the bottom line isn't in use by the colon command, and + * we're not in the middle of a map: + * + * Display any messages. Don't test S_UPDATE_MODE. The + * message printing routine set it to avoid anyone else + * destroying the message we're about to display. + * + * If the bottom line isn't in use by anyone, put out the + * standard status line. + */ + if (!F_ISSET(SVP(sp), SVI_INFOLINE) && !KEYS_WAITING(sp)) + if (sp->msgq.lh_first != NULL && + !F_ISSET(sp->msgq.lh_first, M_EMPTY)) + svi_msgflush(sp); + else if (!F_ISSET(sp, S_UPDATE_MODE)) + svi_modeline(sp, ep); + + /* Update saved information. */ + OCNO = CNO; + OLNO = LNO; + + /* Place the cursor. */ + MOVE(sp, y, SCNO); + + /* Flush it all out. */ + refresh(); + + /* + * XXX + * Recalculate the "most favorite" cursor position. Vi doesn't know + * that we've warped the screen and it's going to have a completely + * wrong idea about where the cursor should be. This is vi's problem, + * and fixing it here is a gross violation of layering. + */ + if (leftright_warp) + (void)svi_column(sp, ep, &sp->rcm); + + return (0); +} + +/* + * svi_modeline -- + * Update the mode line. + */ +static int +svi_modeline(sp, ep) + SCR *sp; + EXF *ep; +{ + size_t cols, curlen, endpoint, len, midpoint; + char *p, buf[20]; + + /* Clear the mode line. */ + MOVE(sp, INFOLINE(sp), 0); + clrtoeol(); + + /* + * We put down the file name, the ruler, the mode and the dirty flag. + * If there's not enough room, there's not enough room, we don't play + * any special games. We try to put the ruler in the middle and the + * mode and dirty flag at the end. + * + * !!! + * Leave the last character blank, in case it's a really dumb terminal + * with hardware scroll. Second, don't paint the last character in the + * screen, SunOS 4.1.1 and Ultrix 4.2 curses won't let you. + */ + cols = sp->cols - 1; + + curlen = 0; + if (sp->q.cqe_next != (void *)&sp->gp->dq) { + for (p = sp->frp->name; *p != '\0'; ++p); + while (--p > sp->frp->name) { + if (*p == '/') { + ++p; + break; + } + if ((curlen += KEY_LEN(sp, *(u_char *)p)) > cols) { + curlen -= KEY_LEN(sp, *(u_char *)p); + ++p; + break; + } + } + + MOVE(sp, INFOLINE(sp), 0); + standout(); + for (; *p != '\0'; ++p) + ADDCH(*p); + standend(); + } + + /* + * Display the ruler. If we're not at the midpoint yet, move there. + * Otherwise, just add in two extra spaces. + * + * XXX + * Assume that numbers, commas, and spaces only take up a single + * column on the screen. + */ + if (O_ISSET(sp, O_RULER)) { + len = snprintf(buf, + sizeof(buf), "%lu,%lu", sp->lno, sp->cno + 1); + midpoint = (cols - ((len + 1) / 2)) / 2; + if (curlen < midpoint) { + MOVE(sp, INFOLINE(sp), midpoint); + ADDSTR(buf); + curlen += len; + } else if (curlen + 2 + len < cols) { + ADDSTR(" "); + ADDSTR(buf); + curlen += 2 + len; + } + } + + /* + * Display the mode and the modified flag, as close to the end of the + * line as possible, but guaranteeing at least two spaces between the + * ruler and the modified flag. + * + * XXX + * Assume that mode name characters, asterisks, and spaces only take + * up a single column on the screen. + */ + endpoint = cols; + if (O_ISSET(sp, O_SHOWDIRTY) && F_ISSET(ep, F_MODIFIED)) + --endpoint; + +#define MODESIZE 9 + if (O_ISSET(sp, O_SHOWMODE)) + endpoint -= MAX_MODE_NAME; + + if (endpoint < curlen + 2) + return (0); + + MOVE(sp, INFOLINE(sp), endpoint); + if (O_ISSET(sp, O_SHOWDIRTY) && F_ISSET(ep, F_MODIFIED)) + ADDSTR("*"); + if (O_ISSET(sp, O_SHOWMODE)) + ADDSTR(sp->showmode); + return (0); +} diff --git a/usr.bin/vi/svi/svi_relative.c b/usr.bin/vi/svi/svi_relative.c new file mode 100644 index 00000000000..31575b975f9 --- /dev/null +++ b/usr.bin/vi/svi/svi_relative.c @@ -0,0 +1,334 @@ +/*- + * Copyright (c) 1993, 1994 + * 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_relative.c 8.18 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <termios.h> + +#include "compat.h" +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "svi_screen.h" + +static size_t svi_screens + __P((SCR *, EXF *, char *, size_t, recno_t, size_t *)); + +/* + * svi_column -- + * Return the logical column of the cursor. + */ +int +svi_column(sp, ep, cp) + SCR *sp; + EXF *ep; + size_t *cp; +{ + size_t col; + + col = SVP(sp)->sc_col; + if (O_ISSET(sp, O_NUMBER)) + col -= O_NUMBER_LENGTH; + *cp = col; + return (0); +} + +/* + * svi_opt_screens -- + * Return the screen columns necessary to display the line, or + * if specified, the physical character column within the line, + * including space required for the O_NUMBER and O_LIST options. + */ +size_t +svi_opt_screens(sp, ep, lno, cnop) + SCR *sp; + EXF *ep; + recno_t lno; + size_t *cnop; +{ + size_t cols, screens; + + /* + * Check for a cached value. We maintain a cache because, if the + * line is large, this routine gets called repeatedly. One other + * hack, lots of time the cursor is on column one, which is an easy + * one. + */ + if (cnop == NULL) { + if (SVP(sp)->ss_lno == lno) + return (SVP(sp)->ss_screens); + } else if (*cnop == 0) + return (1); + + /* Figure out how many columns the line/column needs. */ + cols = svi_screens(sp, ep, NULL, 0, lno, cnop); + + /* Leading number if O_NUMBER option set. */ + if (O_ISSET(sp, O_NUMBER)) + cols += O_NUMBER_LENGTH; + + /* Trailing '$' if O_LIST option set. */ + if (O_ISSET(sp, O_LIST) && cnop == NULL) + cols += KEY_LEN(sp, '$'); + + screens = (cols / sp->cols + (cols % sp->cols ? 1 : 0)); + if (screens == 0) + screens = 1; + + /* Cache the value. */ + if (cnop == NULL) { + SVP(sp)->ss_lno = lno; + SVP(sp)->ss_screens = screens; + } + return (screens); +} + +/* + * svi_screens -- + * Return the screen columns necessary to display the line, or, + * if specified, the physical character column within the line. + */ +static size_t +svi_screens(sp, ep, lp, llen, lno, cnop) + SCR *sp; + EXF *ep; + char *lp; + size_t llen; + recno_t lno; + size_t *cnop; +{ + size_t chlen, cno, len, scno, tab_off; + int ch, listset; + char *p; + + /* Need the line to go any further. */ + if (lp == NULL) + lp = file_gline(sp, ep, lno, &llen); + + /* Missing or empty lines are easy. */ + if (lp == NULL || llen == 0) + return (0); + + listset = O_ISSET(sp, O_LIST); + +#define SET_CHLEN { \ + chlen = (ch = *(u_char *)p++) == '\t' && \ + !listset ? TAB_OFF(sp, tab_off) : KEY_LEN(sp, ch); \ +} +#define TAB_RESET { \ + /* \ + * If past the end of the screen, and the character was a tab, \ + * reset the screen column to 0. Otherwise, display the rest \ + * of the character on the next line. \ + */ \ + if ((tab_off += chlen) >= sp->cols) \ + if (ch == '\t') { \ + tab_off = 0; \ + scno -= scno % sp->cols; \ + } else \ + tab_off -= sp->cols; \ +} + p = lp; + len = llen; + scno = tab_off = 0; + if (cnop == NULL) + while (len--) { + SET_CHLEN; + scno += chlen; + TAB_RESET; + } + else + for (cno = *cnop; len--; --cno) { + SET_CHLEN; + scno += chlen; + TAB_RESET; + if (cno == 0) + break; + } + return (scno); +} + +/* + * svi_rcm -- + * Return the physical column from the line that will display a + * character closest to the currently most attractive character + * position (which is stored as a screen column). + */ +size_t +svi_rcm(sp, ep, lno) + SCR *sp; + EXF *ep; + recno_t lno; +{ + size_t len; + + /* Last character is easy, and common. */ + if (sp->rcm_last) + return (file_gline(sp, + ep, lno, &len) == NULL || len == 0 ? 0 : len - 1); + + /* First character is easy, and common. */ + if (HMAP->off == 1 && sp->rcm == 0) + return (0); + + /* + * Get svi_cm_private() to do the hard work. If doing leftright + * scrolling, we use the current screen offset, otherwise, use + * the first screen, i.e. an offset of 1. + * + * XXX + * I'm not sure that an offset of 1 is right. What happens is that + * the vi main loop calls us for the VM_RCM case. By using an offset + * of 1, we're assuming that every VM_RCM command changes lines, and + * that we want to position on the first screen for that line. This + * is currently the way it works, but it's not clean. I'd prefer it if + * we could find the SMAP entry the cursor references, and use that + * screen offset. Unfortunately, that's not going to be easy, as we + * don't keep that information around and it may be expensive to get. + */ + return (svi_cm_private(sp, ep, lno, + O_ISSET(sp, O_LEFTRIGHT) ? HMAP->off : 1, sp->rcm)); +} + +/* + * svi_cm_public -- + * Return the physical column from the line that will display a + * character closest to the specified screen column. + * + * The extra interface is because it's called by vi, which doesn't + * have a handle on the SMAP structure. + */ +size_t +svi_cm_public(sp, ep, lno, cno) + SCR *sp; + EXF *ep; + recno_t lno; + size_t cno; +{ + return (svi_cm_private(sp, ep, lno, HMAP->off, cno)); +} + +/* + * svi_cm_private -- + * Return the physical column from the line that will display a + * character closest to the specified screen column, taking into + * account the screen offset. + * + * The offset is for the commands that move logical distances, i.e. + * if it's a logical scroll the closest physical distance is based + * on the logical line, not the physical line. + */ +size_t +svi_cm_private(sp, ep, lno, off, cno) + SCR *sp; + EXF *ep; + recno_t lno; + size_t off, cno; +{ + size_t chlen, len, llen, scno, tab_off; + int ch, listset; + char *lp, *p; + + /* Need the line to go any further. */ + lp = file_gline(sp, ep, lno, &llen); + + /* Missing or empty lines are easy. */ + if (lp == NULL || llen == 0) + return (0); + + listset = O_ISSET(sp, O_LIST); + + /* Discard screen (logical) lines. */ + for (scno = 0, p = lp, len = llen; --off;) { + for (; len && scno < sp->cols; --len) + scno += (ch = *(u_char *)p++) == '\t' && + !listset ? TAB_OFF(sp, scno) : KEY_LEN(sp, ch); + + /* + * If reached the end of the physical line, return + * the last physical character in the line. + */ + if (len == 0) + return (llen - 1); + + /* + * If the character was a tab, reset the screen column to 0. + * Otherwise, the rest of the character is displayed on the + * next line. + */ + if (ch == '\t') + scno = 0; + else + scno -= sp->cols; + } + + /* Step through the line until reach the right character or EOL. */ + for (tab_off = scno; len--;) { + SET_CHLEN; + + /* + * If we've reached the specific character, there are three + * cases. + * + * 1: scno == cno, i.e. the current character ends at the + * screen character we care about. + * a: off < llen - 1, i.e. not the last character in + * the line, return the offset of the next character. + * b: else return the offset of the last character. + * 2: scno != cno, i.e. this character overruns the character + * we care about, return the offset of this character. + */ + if ((scno += chlen) >= cno) { + off = p - lp; + return (scno == cno ? + (off < llen - 1 ? off : llen - 1) : off - 1); + } + + TAB_RESET; + } + + /* No such character; return the start of the last character. */ + return (llen - 1); +} diff --git a/usr.bin/vi/svi/svi_screen.c b/usr.bin/vi/svi/svi_screen.c new file mode 100644 index 00000000000..f7d3f88703f --- /dev/null +++ b/usr.bin/vi/svi/svi_screen.c @@ -0,0 +1,336 @@ +/*- + * Copyright (c) 1993, 1994 + * 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_screen.c 8.94 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "../vi/vcmd.h" +#include "svi_screen.h" +#include "../sex/sex_screen.h" + +/* + * svi_screen_init -- + * Initialize a screen. + */ +int +svi_screen_init(sp) + SCR *sp; +{ + /* Initialize support routines. */ + sp->s_bell = svi_bell; + sp->s_bg = svi_bg; + sp->s_busy = svi_busy; + sp->s_change = svi_change; + sp->s_clear = svi_clear; + sp->s_colpos = svi_cm_public; + sp->s_column = svi_column; + sp->s_confirm = svi_confirm; + sp->s_crel = svi_crel; + sp->s_edit = svi_screen_edit; + sp->s_end = svi_screen_end; + sp->s_ex_cmd = svi_ex_cmd; + sp->s_ex_run = svi_ex_run; + sp->s_ex_write = svi_ex_write; + sp->s_fg = svi_fg; + sp->s_fill = svi_sm_fill; + sp->s_get = svi_get; + sp->s_key_read = sex_key_read; + sp->s_optchange = svi_optchange; + sp->s_fmap = svi_fmap; + sp->s_position = svi_sm_position; + sp->s_rabs = svi_rabs; + sp->s_rcm = svi_rcm; + sp->s_refresh = svi_refresh; + sp->s_scroll = svi_sm_scroll; + sp->s_split = svi_split; + sp->s_suspend = svi_suspend; + sp->s_window = sex_window; + + return (0); +} + +/* + * svi_screen_copy -- + * Copy to a new screen. + */ +int +svi_screen_copy(orig, sp) + SCR *orig, *sp; +{ + SVI_PRIVATE *osvi, *nsvi; + + /* Create the private screen structure. */ + CALLOC_RET(orig, nsvi, SVI_PRIVATE *, 1, sizeof(SVI_PRIVATE)); + sp->svi_private = nsvi; + +/* INITIALIZED AT SCREEN CREATE. */ + /* Invalidate the line size cache. */ + SVI_SCR_CFLUSH(nsvi); + +/* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */ + if (orig == NULL) { + } else { + osvi = SVP(orig); + nsvi->srows = osvi->srows; + if (osvi->VB != NULL && (nsvi->VB = strdup(osvi->VB)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return (1); + } + + F_SET(nsvi, F_ISSET(osvi, SVI_CURSES_INIT)); + } + return (0); +} + +/* + * svi_screen_end -- + * End a screen. + */ +int +svi_screen_end(sp) + SCR *sp; +{ + SVI_PRIVATE *svp; + + svp = SVP(sp); + + /* Free the screen map. */ + if (HMAP != NULL) + FREE(HMAP, SIZE_HMAP(sp) * sizeof(SMAP)); + + /* Free the visual bell string. */ + if (svp->VB != NULL) + free(svp->VB); + + /* Free private memory. */ + FREE(svp, sizeof(SVI_PRIVATE)); + sp->svi_private = NULL; + + return (0); +} + +/* + * We use a single curses "window" for each vi screen. The model would be + * simpler with two windows (one for the text, and one for the modeline) + * because scrolling the text window down would work correctly then, not + * affecting the mode line. As it is we have to play games to make it look + * right. The reason for this choice is that it would be difficult for + * curses to optimize the movement, i.e. detect that the downward scroll + * isn't going to change the modeline, set the scrolling region on the + * terminal and only scroll the first part of the text window. (Even if + * curses did detect it, the set-scrolling-region terminal commands can't + * be used by curses because it's indeterminate where the cursor ends up + * after they are sent.) + */ +/* + * svi_screen_edit -- + * Main vi curses screen loop. + */ +int +svi_screen_edit(sp, ep) + SCR *sp; + EXF *ep; +{ + SCR *tsp; + int ecurses, escreen, force, rval; + + escreen = ecurses = rval = 0; + + /* Initialize curses. */ + if (svi_curses_init(sp)) { + escreen = 1; + goto err; + } + ecurses = 1; + + /* + * The resize bit is probably set, as a result of the terminal being + * set. We clear it as we just finished initializing the screen. + * However, we will want to fill in the map from scratch, so provide + * a line number just in case, and set the reformat flag. + */ + HMAP->lno = 1; + F_CLR(sp, S_RESIZE); + F_SET(sp, S_REFORMAT); + + /* + * The historic 4BSD curses had an uneasy relationship with termcap. + * Termcap used a static buffer to hold the terminal information, + * which was was then used by the curses functions. We want to use + * it too, for lots of random things, but we've put it off until after + * svi_curses_init:initscr() was called. Do it now. + */ + if (svi_term_init(sp)) + goto err; + + for (;;) { + /* Reset the cursor. */ + F_SET(SVP(sp), SVI_CUR_INVALID); + + /* + * Run vi. If vi fails, svi data structures may be + * corrupted, be extremely careful what you free up. + */ + if (vi(sp, sp->ep)) { + (void)rcv_sync(sp, sp->ep, + RCV_EMAIL | RCV_ENDSESSION | RCV_PRESERVE); + escreen = 1; + goto err; + } + + force = 0; + switch (F_ISSET(sp, S_MAJOR_CHANGE)) { + case S_EXIT_FORCE: + force = 1; + /* FALLTHROUGH */ + case S_EXIT: + F_CLR(sp, S_EXIT_FORCE | S_EXIT); + if (file_end(sp, sp->ep, force))/* File end. */ + break; + /* + * !!! + * NB: sp->frp may now be NULL, if it was a tmp file. + */ + (void)svi_join(sp, &tsp); /* Find a new screen. */ + if (tsp == NULL) + (void)svi_swap(sp, &tsp, NULL); + if (tsp == NULL) { + escreen = 1; + goto ret; + } + (void)screen_end(sp); /* Screen end. */ + sp = tsp; + break; + case 0: /* Exit vi mode. */ + svi_dtoh(sp, "Exit from vi"); + goto ret; + case S_FSWITCH: /* File switch. */ + F_CLR(sp, S_FSWITCH); + F_SET(sp, S_REFORMAT); + break; + case S_SSWITCH: /* Screen switch. */ + F_CLR(sp, S_SSWITCH); + sp = sp->nextdisp; + break; + default: + abort(); + } + } + + if (0) { +err: rval = 1; + } + +ret: if (svi_term_end(sp)) /* Terminal end (uses sp). */ + rval = 1; + if (ecurses && svi_curses_end(sp)) /* Curses end (uses sp). */ + rval = 1; + if (escreen && screen_end(sp)) /* Screen end. */ + rval = 1; + return (rval); +} + +/* + * svi_crel -- + * Change the relative size of the current screen. + */ +int +svi_crel(sp, count) + SCR *sp; + long count; +{ + /* Can't grow beyond the size of the window. */ + if (count > O_VAL(sp, O_WINDOW)) + count = O_VAL(sp, O_WINDOW); + + sp->t_minrows = sp->t_rows = count; + if (sp->t_rows > sp->rows - 1) + sp->t_minrows = sp->t_rows = sp->rows - 1; + TMAP = HMAP + (sp->t_rows - 1); + F_SET(sp, S_REDRAW); + return (0); +} + +/* + * svi_dtoh -- + * Move all but the current screen to the hidden queue. + */ +void +svi_dtoh(sp, emsg) + SCR *sp; + char *emsg; +{ + SCR *tsp; + int hidden; + + for (hidden = 0; + (tsp = sp->gp->dq.cqh_first) != (void *)&sp->gp->dq; ++hidden) { + if (_HMAP(tsp) != NULL) { + FREE(_HMAP(tsp), SIZE_HMAP(tsp) * sizeof(SMAP)); + _HMAP(tsp) = NULL; + } + SIGBLOCK(sp->gp); + CIRCLEQ_REMOVE(&sp->gp->dq, tsp, q); + CIRCLEQ_INSERT_TAIL(&sp->gp->hq, tsp, q); + SIGUNBLOCK(sp->gp); + } + SIGBLOCK(sp->gp); + CIRCLEQ_REMOVE(&sp->gp->hq, sp, q); + CIRCLEQ_INSERT_TAIL(&sp->gp->dq, sp, q); + SIGUNBLOCK(sp->gp); + if (hidden > 1) + msgq(sp, M_INFO, + "%s backgrounded %d screens; use :display to list the screens", + emsg, hidden - 1); +} diff --git a/usr.bin/vi/svi/svi_screen.h b/usr.bin/vi/svi/svi_screen.h new file mode 100644 index 00000000000..3b4643dc10d --- /dev/null +++ b/usr.bin/vi/svi/svi_screen.h @@ -0,0 +1,262 @@ +/*- + * Copyright (c) 1993, 1994 + * 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. + * + * @(#)svi_screen.h 8.52 (Berkeley) 7/20/94 + */ + +/* + * Structure for mapping lines to the screen. An SMAP is an array, with one + * structure element per screen line, which holds information describing the + * physical line which is displayed in the screen line. The first two fields + * (lno and off) are all that are necessary to describe a line. The rest of + * the information is useful to keep information from being re-calculated. + * + * Lno is the line number. Off is the screen offset into the line. For + * example, the pair 2:1 would be the first screen of line 2, and 2:2 would + * be the second. If doing left-right scrolling, all of the offsets will be + * the same, i.e. for the second screen, 1:2, 2:2, 3:2, etc. If doing the + * standard vi scrolling, it will be staggered, i.e. 1:1, 1:2, 1:3, 2:1, 3:1, + * etc. + * + * The SMAP is always as large as the physical screen, plus a slot for the + * info line, so that there is room to add any screen into another one at + * screen exit. + */ +typedef struct _smap { + recno_t lno; /* 1-N: Physical file line number. */ + size_t off; /* 1-N: Screen offset in the line. */ + + /* svi_line() cache information. */ + size_t c_sboff; /* 0-N: offset of first character byte. */ + size_t c_eboff; /* 0-N: offset of last character byte. */ + u_char c_scoff; /* 0-N: offset into the first character. */ + u_char c_eclen; /* 1-N: columns from the last character. */ + u_char c_ecsize; /* 1-N: size of the last character. */ +} SMAP; + + /* Macros to flush/test cached information. */ +#define SMAP_CACHE(smp) ((smp)->c_ecsize != 0) +#define SMAP_FLUSH(smp) ((smp)->c_ecsize = 0) + +typedef struct _svi_private { +/* INITIALIZED AT SCREEN CREATE. */ + SMAP *h_smap; /* First slot of the line map. */ + SMAP *t_smap; /* Last slot of the line map. */ + + size_t exlinecount; /* Ex overwrite count. */ + size_t extotalcount; /* Ex overwrite count. */ + size_t exlcontinue; /* Ex line continue value. */ + + /* svi_opt_screens() cache information. */ +#define SVI_SCR_CFLUSH(svp) svp->ss_lno = OOBLNO + recno_t ss_lno; /* 1-N: Line number. */ + size_t ss_screens; /* Return value. */ + + recno_t olno; /* 1-N: old cursor file line. */ + size_t ocno; /* 0-N: old file cursor column. */ + size_t sc_col; /* 0-N: LOGICAL screen column. */ + +/* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */ + size_t srows; /* 1-N: Rows in the terminal/window. */ + + char *VB; /* Visual bell termcap string. */ + +#define SVI_CURSES_INIT 0x001 /* Curses/termcap initialized. */ +#define SVI_CUR_INVALID 0x002 /* Cursor position is unknown. */ +#define SVI_DIVIDER 0x004 /* Screen divider is displayed. */ +#define SVI_INFOLINE 0x008 /* The infoline is being used by v_ntext(). */ +#define SVI_SCREENDIRTY 0x010 /* Screen needs refreshing. */ + u_int8_t flags; +} SVI_PRIVATE; + +#define SVP(sp) ((SVI_PRIVATE *)((sp)->svi_private)) +#define HMAP (SVP(sp)->h_smap) +#define TMAP (SVP(sp)->t_smap) +#define _HMAP(sp) (SVP(sp)->h_smap) +#define _TMAP(sp) (SVP(sp)->t_smap) + +/* + * One extra slot is always allocated for the map so that we can use + * it to do vi :colon command input; see svi_get(). + */ +#define SIZE_HMAP(sp) (SVP(sp)->srows + 1) + +#define O_NUMBER_FMT "%7lu " /* O_NUMBER format, length. */ +#define O_NUMBER_LENGTH 8 + /* Columns on a screen. */ +#define SCREEN_COLS(sp) \ + ((O_ISSET(sp, O_NUMBER) ? (sp)->cols - O_NUMBER_LENGTH : (sp)->cols)) + +#define HALFSCREEN(sp) ((sp)->t_maxrows / 2) /* Half the screen. */ +#define HALFTEXT(sp) ((sp)->t_rows / 2) /* Half the text. */ + +#define INFOLINE(sp) ((sp)->t_maxrows) /* Info line test, offset. */ +#define ISINFOLINE(sp, smp) (((smp) - HMAP) == INFOLINE(sp)) + + /* Small screen test. */ +#define ISSMALLSCREEN(sp) ((sp)->t_minrows != (sp)->t_maxrows) + +/* + * Next tab offset. + * + * !!! + * There are problems with how the historical vi handled tabs. For example, + * by doing "set ts=3" and building lines that fold, you can get it to step + * through tabs as if they were spaces and move inserted characters to new + * positions when <esc> is entered. I think that nvi does tabs correctly, + * but there may be some historical incompatibilities. + */ +#define TAB_OFF(sp, c) (O_VAL(sp, O_TABSTOP) - (c) % O_VAL(sp, O_TABSTOP)) + +/* Move in a screen (absolute), and fail if it doesn't work. */ +#ifdef DEBUG +#define MOVEA(sp, lno, cno) { \ + if (move(lno, cno) == ERR) { \ + msgq(sp, M_ERR, \ + "Error: %s/%d: move:l(%u), c(%u), abs", \ + tail(__FILE__), __LINE__, lno, cno); \ + return (1); \ + } \ +} +#else +#define MOVEA(sp, lno, cno) (void)move(lno, cno) +#endif + +/* Move in a window, and fail if it doesn't work. */ +#ifdef DEBUG +#define MOVE(sp, lno, cno) { \ + size_t __lno = (sp)->woff + (lno); \ + if (move(__lno, cno) == ERR) { \ + msgq(sp, M_ERR, \ + "Error: %s/%d: move:l(%u), c(%u), o(%u)", \ + tail(__FILE__), __LINE__, lno, cno, sp->woff); \ + return (1); \ + } \ +} +#else +#define MOVE(sp, lno, cno) (void)move((sp)->woff + (lno), cno) +#endif + +/* Add a character. */ +#define ADDCH(ch) { \ + CHAR_T __ch = ch; \ + ADDNSTR(KEY_NAME(sp, __ch), KEY_LEN(sp, __ch)); \ +} + +/* Add a string len bytes long. */ +#ifdef DEBUG +#define ADDNSTR(str, len) { \ + if (addnstr(str, len) == ERR) { \ + int __x, __y; \ + getyx(stdscr, __y, __x); \ + msgq(sp, M_ERR, "Error: %s/%d: addnstr: (%d/%u)", \ + tail(__FILE__), __LINE__, __y, __x); \ + return (1); \ + } \ +} +#else +#define ADDNSTR(str, len) (void)addnstr(str, len) +#endif + +/* Add a string. */ +#ifdef DEBUG +#define ADDSTR(str) { \ + if (addstr(str) == ERR) { \ + int __x, __y; \ + getyx(stdscr, __y, __x); \ + msgq(sp, M_ERR, "Error: %s/%d: addstr: (%d/%u)", \ + tail(__FILE__), __LINE__, __y, __x); \ + return (1); \ + } \ +} +#else +#define ADDSTR(str) (void)addstr(str); +#endif + +/* Public routines. */ +void svi_bell __P((SCR *)); +int svi_bg __P((SCR *)); +int svi_busy __P((SCR *, char const *)); +int svi_change __P((SCR *, EXF *, recno_t, enum operation)); +size_t svi_cm_public __P((SCR *, EXF *, recno_t, size_t)); +int svi_column __P((SCR *, EXF *, size_t *)); +enum confirm + svi_confirm __P((SCR *, EXF *, MARK *, MARK *)); +int svi_clear __P((SCR *)); +int svi_crel __P((SCR *, long)); +int svi_ex_cmd __P((SCR *, EXF *, struct _excmdarg *, MARK *)); +int svi_ex_run __P((SCR *, EXF *, MARK *)); +int svi_ex_write __P((void *, const char *, int)); +int svi_fg __P((SCR *, CHAR_T *)); +int svi_fmap __P((SCR *, enum seqtype, CHAR_T *, size_t, CHAR_T *, size_t)); +enum input + svi_get __P((SCR *, EXF *, TEXTH *, ARG_CHAR_T, u_int)); +int svi_optchange __P((SCR *, int)); +int svi_rabs __P((SCR *, long, enum adjust)); +size_t svi_rcm __P((SCR *, EXF *, recno_t)); +int svi_refresh __P((SCR *, EXF *)); +int svi_screen_copy __P((SCR *, SCR *)); +int svi_screen_edit __P((SCR *, EXF *)); +int svi_screen_end __P((SCR *)); +int svi_sm_fill __P((SCR *, EXF *, recno_t, enum position)); +int svi_sm_position __P((SCR *, EXF *, MARK *, u_long, enum position)); +int svi_sm_scroll __P((SCR *, EXF *, MARK *, recno_t, enum sctype)); +int svi_split __P((SCR *, ARGS *[], int)); +int svi_suspend __P((SCR *)); +int svi_swap __P((SCR *, SCR **, char *)); + +/* Private routines. */ +size_t svi_cm_private __P((SCR *, EXF *, recno_t, size_t, size_t)); +int svi_curses_end __P((SCR *)); +int svi_curses_init __P((SCR *)); +void svi_dtoh __P((SCR *, char *)); +int svi_init __P((SCR *)); +int svi_join __P((SCR *, SCR **)); +void svi_keypad __P((SCR *, int)); +int svi_line __P((SCR *, EXF *, SMAP *, size_t *, size_t *)); +int svi_msgflush __P((SCR *)); +int svi_number __P((SCR *, EXF *)); +size_t svi_opt_screens __P((SCR *, EXF *, recno_t, size_t *)); +int svi_paint __P((SCR *, EXF *)); +int svi_sm_1down __P((SCR *, EXF *)); +int svi_sm_1up __P((SCR *, EXF *)); +int svi_sm_cursor __P((SCR *, EXF *, SMAP **)); +int svi_sm_next __P((SCR *, EXF *, SMAP *, SMAP *)); +recno_t svi_sm_nlines __P((SCR *, EXF *, SMAP *, recno_t, size_t)); +int svi_sm_prev __P((SCR *, EXF *, SMAP *, SMAP *)); +int svi_term_end __P((SCR *sp)); +int svi_term_init __P((SCR *sp)); + +/* Private debugging routines. */ +#ifdef DEBUG +int svi_gdbrefresh __P((void)); +#endif diff --git a/usr.bin/vi/svi/svi_smap.c b/usr.bin/vi/svi/svi_smap.c new file mode 100644 index 00000000000..a9205b15208 --- /dev/null +++ b/usr.bin/vi/svi/svi_smap.c @@ -0,0 +1,1216 @@ +/*- + * Copyright (c) 1993, 1994 + * 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.48 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "../vi/vcmd.h" +#include "svi_screen.h" + +static int svi_deleteln __P((SCR *, int)); +static int svi_insertln __P((SCR *, int)); +static int svi_sm_delete __P((SCR *, EXF *, recno_t)); +static int svi_sm_down __P((SCR *, EXF *, + MARK *, recno_t, enum sctype, SMAP *)); +static int svi_sm_erase __P((SCR *)); +static int svi_sm_insert __P((SCR *, EXF *, recno_t)); +static int svi_sm_reset __P((SCR *, EXF *, recno_t)); +static int svi_sm_up __P((SCR *, EXF *, + MARK *, recno_t, enum sctype, SMAP *)); + +/* + * 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) { + switch (op) { + case LINE_APPEND: + abort(); + /* NOTREACHED */ + case LINE_DELETE: + for (p = HMAP; p <= TMAP; ++p) + --p->lno; + if (sp->lno >= lno) + --sp->lno; + F_SET(sp, S_RENUMBER); + break; + case LINE_INSERT: + for (p = HMAP; p <= TMAP; ++p) + ++p->lno; + if (sp->lno >= lno) + ++sp->lno; + F_SET(sp, S_RENUMBER); + break; + case LINE_RESET: + break; + } + return (0); + } + + F_SET(SVP(sp), SVI_SCREENDIRTY); + + /* Invalidate the cursor, if it's on this line. */ + if (sp->lno == lno) + F_SET(SVP(sp), SVI_CUR_INVALID); + + /* Invalidate the line size cache. */ + SVI_SCR_CFLUSH(SVP(sp)); + + getyx(stdscr, oldy, oldx); + + switch (op) { + case LINE_DELETE: + if (svi_sm_delete(sp, ep, lno)) + return (1); + F_SET(sp, S_RENUMBER); + break; + case LINE_INSERT: + if (svi_sm_insert(sp, ep, lno)) + return (1); + F_SET(sp, S_RENUMBER); + 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. 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. + */ +int +svi_sm_fill(sp, ep, lno, pos) + SCR *sp; + EXF *ep; + recno_t lno; + enum position pos; +{ + SMAP *p, tmp; + + /* Flush all cached information from the SMAP. */ + for (p = HMAP; p <= TMAP; ++p) + SMAP_FLUSH(p); + + /* If the map is filled, the screen must be redrawn. */ + F_SET(sp, S_REDRAW); + + 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, HALFTEXT(sp)) <= HALFTEXT(sp)) { + lno = 1; + goto top; + } + + /* See if less than half a screen from the bottom. */ + if (file_lline(sp, ep, &tmp.lno)) + return (1); + if (!O_ISSET(sp, O_LEFTRIGHT)) + tmp.off = svi_opt_screens(sp, ep, tmp.lno, NULL); + if (svi_sm_nlines(sp, ep, + &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { + TMAP->lno = tmp.lno; + if (!O_ISSET(sp, O_LEFTRIGHT)) + TMAP->off = tmp.off; + goto bottom; + } + goto middle; + case P_TOP: + if (lno != OOBLNO) { +top: HMAP->lno = lno; + HMAP->off = 1; + } + /* If we fail, just punt. */ + for (p = HMAP; 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 (lno != OOBLNO) { + TMAP->lno = lno; + if (!O_ISSET(sp, O_LEFTRIGHT)) + TMAP->off = svi_opt_screens(sp, ep, lno, NULL); + } + /* If we fail, guess that the file is too small. */ +bottom: for (p = TMAP; p > HMAP; --p) + if (svi_sm_prev(sp, ep, 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->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. + */ +static 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); + 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); + + 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); + /* svi_sm_next() flushed the cache. */ + if (svi_line(sp, ep, ++p, NULL, NULL)) + return (1); + if (p == TMAP) + break; + } + return (0); +} + +/* + * svi_sm_insert -- + * Insert a line into the SMAP. + */ +static 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); + if (O_ISSET(sp, O_LEFTRIGHT)) + cnt_orig = 1; + else + cnt_orig = svi_opt_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; + SMAP_FLUSH(t); + if (svi_line(sp, ep, t, NULL, NULL)) + return (1); + } + return (0); +} + +/* + * svi_sm_reset -- + * Reset a line in the SMAP. + */ +static 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); + 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 = svi_opt_screens(sp, ep, lno, NULL); + } + + TOO_WEIRD; + + if (cnt_orig == cnt_new) { + do { + SMAP_FLUSH(p); + if (svi_line(sp, ep, 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; + + /* 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; + SMAP_FLUSH(t); + if (svi_line(sp, ep, t, 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; + SMAP_FLUSH(t); + if (svi_line(sp, ep, t, 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); + /* svi_sm_next() flushed the cache. */ + if (svi_line(sp, ep, ++t, NULL, NULL)) + return (1); + if (t == TMAP) + break; + } + } + return (0); +} + +/* + * svi_sm_scroll + * Scroll the SMAP up/down count logical lines. Different + * semantics based on the vi command, *sigh*. + */ +int +svi_sm_scroll(sp, ep, rp, count, scmd) + SCR *sp; + EXF *ep; + MARK *rp; + recno_t count; + enum sctype 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(SVP(sp), SVI_CUR_INVALID); + + /* Find the cursor in the screen. */ + if (svi_sm_cursor(sp, ep, &smp)) + return (1); + + switch (scmd) { + case CNTRL_B: + case CNTRL_U: + case CNTRL_Y: + case Z_CARAT: + if (svi_sm_down(sp, ep, rp, count, scmd, smp)) + return (1); + break; + case CNTRL_D: + case CNTRL_E: + case CNTRL_F: + case Z_PLUS: + if (svi_sm_up(sp, ep, 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, ep, rp->lno, &rp->cno)) + return (1); + + return (0); +} + +/* + * svi_sm_up -- + * Scroll the SMAP up count logical lines. + */ +static int +svi_sm_up(sp, ep, rp, count, scmd, smp) + SCR *sp; + EXF *ep; + MARK *rp; + enum sctype scmd; + recno_t count; + SMAP *smp; +{ + int cursor_set, echanged, zset; + SMAP 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 (svi_sm_next(sp, ep, TMAP, &s1)) + return (1); + if (s1.lno > TMAP->lno && !file_gline(sp, ep, s1.lno, NULL)) { + if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) { + v_eof(sp, ep, NULL); + return (1); + } + if (svi_sm_next(sp, ep, smp, &s1)) + return (1); + if (s1.lno > smp->lno && !file_gline(sp, ep, s1.lno, NULL)) { + v_eof(sp, ep, NULL); + return (1); + } + } + + /* + * Small screens: see svi/svi_refresh.c:svi_refresh, section 2b. + * + * 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 to the + * first 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 (ISSMALLSCREEN(sp)) { + if (count >= sp->t_maxrows || scmd == CNTRL_F) { + s1 = TMAP[0]; + if (svi_sm_erase(sp)) + return (1); + for (; count--; s1 = s2) { + if (svi_sm_next(sp, ep, &s1, &s2)) + return (1); + if (s2.lno != s1.lno && + !file_gline(sp, ep, s2.lno, NULL)) + break; + } + TMAP[0] = s2; + if (svi_sm_fill(sp, ep, OOBLNO, P_BOTTOM)) + return (1); + return (svi_sm_position(sp, ep, rp, 0, P_TOP)); + } + for (; count && + sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { + if (svi_sm_next(sp, ep, TMAP, &s1)) + return (1); + if (TMAP->lno != s1.lno && + !file_gline(sp, ep, s1.lno, NULL)) + break; + *++TMAP = s1; + /* svi_sm_next() flushed the cache. */ + if (svi_line(sp, ep, TMAP, NULL, NULL)) + return (1); + + if (scmd != CNTRL_E && !cursor_set) { + cursor_set = 1; + rp->lno = TMAP->lno; + rp->cno = TMAP->c_sboff; + } + } + if (count == 0) + return (0); + } + + for (echanged = zset = 0; count; --count) { + /* Decide what would show up on the screen. */ + if (svi_sm_next(sp, ep, TMAP, &s1)) + return (1); + + /* If the line doesn't exist, we're done. */ + if (TMAP->lno != s1.lno && !file_gline(sp, ep, s1.lno, NULL)) + break; + + /* Scroll the screen cursor up one logical line. */ + if (svi_sm_1up(sp, ep)) + 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 = + svi_cm_private(sp, ep, smp->lno, smp->off, sp->rcm); + } + return (0); + case CNTRL_F: + /* + * If there are more lines, the ^F command is always + * 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 || + !file_gline(sp, ep, smp[1].lno, NULL)) + break; + break; + case Z_PLUS: + /* The z+ command moves the cursor to the first new line. */ + break; + default: + abort(); + } + + if (!SMAP_CACHE(smp) && svi_line(sp, ep, smp, NULL, NULL)) + return (1); + rp->lno = smp->lno; + rp->cno = smp->c_sboff; + 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 - 1) * sizeof(SMAP)); + if (svi_sm_next(sp, ep, TMAP - 1, TMAP)) + return (1); + } + /* svi_sm_next() flushed the cache. */ + if (svi_line(sp, ep, TMAP, 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. + */ +static 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. + */ +static int +svi_sm_down(sp, ep, rp, count, scmd, smp) + SCR *sp; + EXF *ep; + MARK *rp; + recno_t count; + SMAP *smp; + enum sctype scmd; +{ + SMAP s1, s2; + int cursor_set, ychanged, zset; + + /* Check to see if movement is possible. */ + if (HMAP->lno == 1 && HMAP->off == 1 && + (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) { + v_sof(sp, NULL); + return (1); + } + + /* + * Small screens: see svi/svi_refresh.c:svi_refresh, section 2b. + * + * 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 to the + * first 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 (ISSMALLSCREEN(sp)) { + if (count >= sp->t_maxrows || scmd == CNTRL_B) { + s1 = HMAP[0]; + if (svi_sm_erase(sp)) + return (1); + for (; count--; s1 = s2) { + if (svi_sm_prev(sp, ep, &s1, &s2)) + return (1); + if (s2.lno == 1 && s2.off == 1) + break; + } + HMAP[0] = s2; + if (svi_sm_fill(sp, ep, OOBLNO, P_TOP)) + return (1); + return (svi_sm_position(sp, ep, rp, 0, P_BOTTOM)); + } + for (; count && + sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { + if (HMAP->lno == 1 || HMAP->off == 1) + break; + ++TMAP; + if (svi_sm_1down(sp, ep)) + return (1); + if (scmd != CNTRL_Y && !cursor_set) { + cursor_set = 1; + if (svi_sm_position(sp, ep, rp, 0, P_BOTTOM)) + return (1); + } + } + if (count == 0) + return (0); + } + + for (ychanged = zset = 0; count; --count) { + /* If the line doesn't exist, we're done. */ + if (HMAP->lno == 1 && HMAP->off == 1) + break; + + /* Scroll the screen and cursor down one logical line. */ + if (svi_sm_1down(sp, ep)) + 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 always + * positioned at the last line of the screen. + */ + if (!count) { + smp = TMAP; + 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 = + svi_cm_private(sp, ep, smp->lno, smp->off, sp->rcm); + } + return (0); + case Z_CARAT: + /* The z^ command moves the cursor to the first new line. */ + break; + default: + abort(); + } + + if (!SMAP_CACHE(smp) && svi_line(sp, ep, smp, NULL, NULL)) + return (1); + rp->lno = smp->lno; + rp->cno = smp->c_sboff; + return (0); +} + +/* + * svi_sm_erase -- + * Erase the small screen area for the scrolling functions. + */ +static int +svi_sm_erase(sp) + SCR *sp; +{ + MOVE(sp, INFOLINE(sp), 0); + clrtoeol(); + for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) { + MOVE(sp, TMAP - HMAP, 0); + clrtoeol(); + } + 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 - 1) * sizeof(SMAP)); + if (svi_sm_prev(sp, ep, HMAP + 1, HMAP)) + return (1); + /* svi_sm_prev() flushed the cache. */ + if (svi_line(sp, ep, HMAP, 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. + */ +static 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; + + SMAP_FLUSH(t); + if (O_ISSET(sp, O_LEFTRIGHT)) { + t->lno = p->lno + 1; + t->off = p->off; + } else { + lcnt = svi_opt_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; +{ + SMAP_FLUSH(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_opt_screens(sp, ep, t->lno, NULL); + } + return (t->lno == 0); +} + +/* + * svi_sm_cursor -- + * Return the SMAP entry referenced by the cursor. + */ +int +svi_sm_cursor(sp, ep, smpp) + SCR *sp; + EXF *ep; + 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) && svi_line(sp, ep, 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); +} + +/* + * svi_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. + */ +int +svi_sm_position(sp, ep, rp, cnt, pos) + SCR *sp; + EXF *ep; + MARK *rp; + u_long cnt; + enum position 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 && file_gline(sp, ep, smp->lno, NULL) == NULL) { +sof: msgq(sp, M_BERR, "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 (file_gline(sp, ep, TMAP->lno, NULL) == NULL) { + if (file_lline(sp, ep, &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 (file_gline(sp, ep, smp->lno, NULL) == NULL) { + if (file_lline(sp, ep, &last)) + return (1); + for (; smp->lno > last && smp > HMAP; --smp); + if (cnt > smp - HMAP) { +eof: msgq(sp, M_BERR, + "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) && svi_line(sp, ep, smp, NULL, NULL)) + return (1); + rp->lno = smp->lno; + rp->cno = smp->c_sboff; + + 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)) + 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->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_opt_screens(sp, ep, lno, NULL); + } else { + lno = from_sp->lno; + lcnt = (svi_opt_screens(sp, ep, lno, NULL) - from_sp->off) + 1; + for (; ++lno < to_lno && lcnt <= max;) + lcnt += svi_opt_screens(sp, ep, lno, NULL); + } + return (lcnt); +} diff --git a/usr.bin/vi/svi/svi_split.c b/usr.bin/vi/svi/svi_split.c new file mode 100644 index 00000000000..dbf151ce0bc --- /dev/null +++ b/usr.bin/vi/svi/svi_split.c @@ -0,0 +1,635 @@ +/*- + * Copyright (c) 1993, 1994 + * 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_split.c 8.49 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "svi_screen.h" + +/* + * svi_split -- + * Split the screen. + */ +int +svi_split(sp, argv, argc) + SCR *sp; + ARGS *argv[]; + int argc; +{ + MSG *mp, *next; + SCR *tsp, saved_sp; + SVI_PRIVATE saved_svp; + SMAP *smp; + size_t cnt, half; + int issmallscreen, splitup; + char **ap; + + /* Check to see if it's possible. */ + half = sp->rows / 2; + if (half < MINIMUM_SCREEN_ROWS) { + msgq(sp, M_ERR, "Screen must be larger than %d to split", + MINIMUM_SCREEN_ROWS); + return (1); + } + + /* Get a new screen. */ + if (screen_init(sp, &tsp, 0)) + return (1); + CALLOC(sp, _HMAP(tsp), SMAP *, SIZE_HMAP(sp), sizeof(SMAP)); + if (_HMAP(tsp) == NULL) + return (1); + + /* + * We're about to modify the current screen. Save the contents + * in case something goes horribly, senselessly wrong. + */ + saved_sp = *sp; + saved_svp = *SVP(sp); + +/* INITIALIZED AT SCREEN CREATE. */ + +/* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */ + /* + * Small screens: see svi/svi_refresh.c:svi_refresh, section 3b. + * Set a flag so we know to fix the screen up later. + */ + issmallscreen = ISSMALLSCREEN(sp); + + /* + * Split the screen, and link the screens together. If the cursor + * is in the top half of the current screen, the new screen goes + * under the current screen. Else, it goes above the current screen. + * + * The columns in the screen don't change. + */ + tsp->cols = sp->cols; + + cnt = svi_sm_cursor(sp, sp->ep, &smp) ? 0 : smp - HMAP; + if (cnt <= half) { /* Parent is top half. */ + /* Child. */ + tsp->rows = sp->rows - half; + tsp->woff = sp->woff + half; + tsp->t_maxrows = tsp->rows - 1; + + /* Parent. */ + sp->rows = half; + sp->t_maxrows = sp->rows - 1; + + splitup = 0; + } else { /* Parent is bottom half. */ + /* Child. */ + tsp->rows = sp->rows - half; + tsp->woff = sp->woff; + tsp->t_maxrows = tsp->rows - 1; + + /* Parent. */ + sp->rows = half; + sp->woff += tsp->rows; + sp->t_maxrows = sp->rows - 1; + + /* Shift the parent's map down. */ + memmove(_HMAP(sp), + _HMAP(sp) + tsp->rows, sp->t_maxrows * sizeof(SMAP)); + + splitup = 1; + } + + /* + * Small screens: see svi/svi_refresh.c:svi_refresh, section 3b. + * + * The child may have different screen options sizes than the + * parent, so use them. Make sure that the text counts aren't + * larger than the new screen sizes. + */ + if (issmallscreen) { + /* Fix the text line count for the parent. */ + if (splitup) + sp->t_rows -= tsp->rows; + + /* Fix the parent screen. */ + if (sp->t_rows > sp->t_maxrows) + sp->t_rows = sp->t_maxrows; + if (sp->t_minrows > sp->t_maxrows) + sp->t_minrows = sp->t_maxrows; + + /* Fix the child screen. */ + tsp->t_minrows = tsp->t_rows = O_VAL(sp, O_WINDOW); + if (tsp->t_rows > tsp->t_maxrows) + tsp->t_rows = tsp->t_maxrows; + if (tsp->t_minrows > tsp->t_maxrows) + tsp->t_minrows = tsp->t_maxrows; + + /* + * If we split up, i.e. the child is on top, lines that + * were painted in the parent may not be painted in the + * child. Clear any lines not being used in the child + * screen. + * + */ + if (splitup) + for (cnt = tsp->t_rows; ++cnt <= tsp->t_maxrows;) { + MOVE(tsp, cnt, 0); + clrtoeol(); + } + } else { + sp->t_minrows = sp->t_rows = sp->rows - 1; + + /* + * The new screen may be a small screen, even though the + * parent was not. Don't complain if O_WINDOW is too large, + * we're splitting the screen so the screen is much smaller + * than normal. Clear any lines not being used in the child + * screen. + */ + tsp->t_minrows = tsp->t_rows = O_VAL(sp, O_WINDOW); + if (tsp->t_rows > tsp->rows - 1) + tsp->t_minrows = tsp->t_rows = tsp->rows - 1; + else + for (cnt = tsp->t_rows; ++cnt <= tsp->t_maxrows;) { + MOVE(tsp, cnt, 0); + clrtoeol(); + } + } + + /* Adjust the ends of both maps. */ + _TMAP(sp) = _HMAP(sp) + (sp->t_rows - 1); + _TMAP(tsp) = _HMAP(tsp) + (tsp->t_rows - 1); + + /* Reset the length of the default scroll. */ + sp->defscroll = sp->t_maxrows / 2; + tsp->defscroll = tsp->t_maxrows / 2; + + /* + * If files specified, build the file list, else, link to the + * current file. + */ + if (argv == NULL) { + if ((tsp->frp = file_add(tsp, sp->frp->name)) == NULL) + goto err; + } else { + /* Create a new argument list. */ + CALLOC(sp, tsp->argv, char **, argc + 1, sizeof(char *)); + if (tsp->argv == NULL) + goto err; + for (ap = tsp->argv, argv; argv[0]->len != 0; ++ap, ++argv) + if ((*ap = + v_strdup(sp, argv[0]->bp, argv[0]->len)) == NULL) + goto err; + *ap = NULL; + + /* Switch to the first one. */ + tsp->cargv = tsp->argv; + if ((tsp->frp = file_add(tsp, *tsp->cargv)) == NULL) + goto err; + } + + /* + * Copy the file state flags, start the file. Fill the child's + * screen map. If the file is unchanged, keep the screen and + * cursor the same. + */ + if (argv == NULL) { + tsp->ep = sp->ep; + ++sp->ep->refcnt; + + tsp->frp->flags = sp->frp->flags; + tsp->frp->lno = sp->lno; + tsp->frp->cno = sp->cno; + F_SET(tsp->frp, FR_CURSORSET); + + /* Copy the parent's map into the child's map. */ + memmove(_HMAP(tsp), _HMAP(sp), tsp->t_rows * sizeof(SMAP)); + } else { + if (file_init(tsp, tsp->frp, NULL, 0)) + goto err; + (void)svi_sm_fill(tsp, tsp->ep, 1, P_TOP); + } + + /* Everything's initialized, put the screen on the displayed queue.*/ + SIGBLOCK(sp->gp); + if (splitup) { + /* Link in before the parent. */ + CIRCLEQ_INSERT_BEFORE(&sp->gp->dq, sp, tsp, q); + } else { + /* Link in after the parent. */ + CIRCLEQ_INSERT_AFTER(&sp->gp->dq, sp, tsp, q); + } + SIGUNBLOCK(sp->gp); + + /* Clear the current information lines in both screens. */ + MOVE(sp, INFOLINE(sp), 0); + clrtoeol(); + MOVE(tsp, INFOLINE(tsp), 0); + clrtoeol(); + + /* Redraw the status line for the parent screen. */ + (void)msg_status(sp, sp->ep, sp->lno, 0); + + /* Save the parent screen's cursor information. */ + sp->frp->lno = sp->lno; + sp->frp->cno = sp->cno; + F_SET(sp->frp, FR_CURSORSET); + + /* Completely redraw the child screen. */ + F_SET(tsp, S_REDRAW); + + /* Switch screens. */ + sp->nextdisp = tsp; + F_SET(sp, S_SSWITCH); + return (0); + + /* Recover the original screen. */ +err: *sp = saved_sp; + *SVP(sp) = saved_svp; + + /* Copy any (probably error) messages in the new screen. */ + for (mp = tsp->msgq.lh_first; mp != NULL; mp = next) { + if (!F_ISSET(mp, M_EMPTY)) + msg_app(sp->gp, sp, + mp->flags & M_INV_VIDEO, mp->mbuf, mp->len); + next = mp->q.le_next; + if (mp->mbuf != NULL) + free(mp->mbuf); + free(mp); + } + + /* Free the new screen. */ + if (tsp->argv != NULL) { + for (ap = tsp->argv; *ap != NULL; ++ap) + free(*ap); + free(tsp->argv); + } + free(_HMAP(tsp)); + free(SVP(tsp)); + FREE(tsp, sizeof(SCR)); + return (1); +} + +/* + * svi_bg -- + * Background the screen, and switch to the next one. + */ +int +svi_bg(csp) + SCR *csp; +{ + SCR *sp; + + /* Try and join with another screen. */ + if ((svi_join(csp, &sp))) + return (1); + if (sp == NULL) { + msgq(csp, M_ERR, + "You may not background your only displayed screen"); + return (1); + } + + /* Move the old screen to the hidden queue. */ + SIGBLOCK(csp->gp); + CIRCLEQ_REMOVE(&csp->gp->dq, csp, q); + CIRCLEQ_INSERT_TAIL(&csp->gp->hq, csp, q); + SIGUNBLOCK(csp->gp); + + /* Switch screens. */ + csp->nextdisp = sp; + F_SET(csp, S_SSWITCH); + + return (0); +} + +/* + * svi_join -- + * Join the screen into a related screen, if one exists, + * and return that screen. + */ +int +svi_join(csp, nsp) + SCR *csp, **nsp; +{ + SCR *sp; + size_t cnt; + + /* + * If a split screen, add space to parent/child. Make no effort + * to clean up the screen's values. If it's not exiting, we'll + * get it when the user asks to show it again. + */ + if ((sp = csp->q.cqe_prev) == (void *)&csp->gp->dq) { + if ((sp = csp->q.cqe_next) == (void *)&csp->gp->dq) { + *nsp = NULL; + return (0); + } + sp->woff = csp->woff; + } + sp->rows += csp->rows; + if (ISSMALLSCREEN(sp)) { + sp->t_maxrows += csp->rows; + for (cnt = sp->t_rows; ++cnt <= sp->t_maxrows;) { + MOVE(sp, cnt, 0); + clrtoeol(); + } + TMAP = HMAP + (sp->t_rows - 1); + } else { + sp->t_maxrows += csp->rows; + sp->t_rows = sp->t_minrows = sp->t_maxrows; + TMAP = HMAP + (sp->t_rows - 1); + if (svi_sm_fill(sp, sp->ep, sp->lno, P_FILL)) + return (1); + F_SET(sp, S_REDRAW); + } + + /* Reset the length of the default scroll. */ + sp->defscroll = sp->t_maxrows / 2; + + /* + * Save the old screen's cursor information. + * + * XXX + * If called after file_end(), if the underlying file was a tmp + * file it may have gone away. + */ + if (csp->frp != NULL) { + csp->frp->lno = csp->lno; + csp->frp->cno = csp->cno; + F_SET(csp->frp, FR_CURSORSET); + } + + *nsp = sp; + return (0); +} + +/* + * svi_fg -- + * Background the current screen, and foreground a new one. + */ +int +svi_fg(csp, name) + SCR *csp; + CHAR_T *name; +{ + SCR *sp; + + if (svi_swap(csp, &sp, name)) + return (1); + if (sp == NULL) { + if (name == NULL) + msgq(csp, M_ERR, "There are no background screens"); + else + msgq(csp, M_ERR, + "There's no background screen editing a file named %s", + name); + return (1); + } + + /* Move the old screen to the hidden queue. */ + SIGBLOCK(csp->gp); + CIRCLEQ_REMOVE(&csp->gp->dq, csp, q); + CIRCLEQ_INSERT_TAIL(&csp->gp->hq, csp, q); + SIGUNBLOCK(csp->gp); + + return (0); +} + +/* + * svi_swap -- + * Swap the current screen with a hidden one. + */ +int +svi_swap(csp, nsp, name) + SCR *csp, **nsp; + char *name; +{ + SCR *sp; + int issmallscreen; + + /* Find the screen, or, if name is NULL, the first screen. */ + for (sp = csp->gp->hq.cqh_first; + sp != (void *)&csp->gp->hq; sp = sp->q.cqe_next) + if (name == NULL || !strcmp(sp->frp->name, name)) + break; + if (sp == (void *)&csp->gp->hq) { + *nsp = NULL; + return (0); + } + *nsp = sp; + + /* + * Save the old screen's cursor information. + * + * XXX + * If called after file_end(), if the underlying file was a tmp + * file it may have gone away. + */ + if (csp->frp != NULL) { + csp->frp->lno = csp->lno; + csp->frp->cno = csp->cno; + F_SET(csp->frp, FR_CURSORSET); + } + + /* Switch screens. */ + csp->nextdisp = sp; + F_SET(csp, S_SSWITCH); + + /* Initialize terminal information. */ + SVP(sp)->srows = SVP(csp)->srows; + + issmallscreen = ISSMALLSCREEN(sp); + + /* Initialize screen information. */ + sp->rows = csp->rows; + sp->cols = csp->cols; + sp->woff = csp->woff; + + /* + * Small screens: see svi/svi_refresh.c:svi_refresh, section 3b. + * + * The new screens may have different screen options sizes than the + * old one, so use them. Make sure that text counts aren't larger + * than the new screen sizes. + */ + if (issmallscreen) { + sp->t_minrows = sp->t_rows = O_VAL(sp, O_WINDOW); + if (sp->t_rows > csp->t_maxrows) + sp->t_rows = sp->t_maxrows; + if (sp->t_minrows > csp->t_maxrows) + sp->t_minrows = sp->t_maxrows; + } else + sp->t_rows = sp->t_maxrows = sp->t_minrows = sp->rows - 1; + + /* Reset the length of the default scroll. */ + sp->defscroll = sp->t_maxrows / 2; + + /* + * Don't change the screen's cursor information other than to + * note that the cursor is wrong. + */ + F_SET(SVP(sp), SVI_CUR_INVALID); + + /* + * The HMAP may be NULL, if the screen got resized and + * a bunch of screens had to be hidden. + */ + if (HMAP == NULL) + CALLOC_RET(sp, HMAP, SMAP *, SIZE_HMAP(sp), sizeof(SMAP)); + TMAP = HMAP + (sp->t_rows - 1); + + /* Fill the map. */ + if (svi_sm_fill(sp, sp->ep, sp->lno, P_FILL)) + return (1); + + /* + * The new screen replaces the old screen in the parent/child list. + * We insert the new screen after the old one. If we're exiting, + * the exit will delete the old one, if we're foregrounding, the fg + * code will move the old one to the hidden queue. + */ + SIGBLOCK(sp->gp); + CIRCLEQ_REMOVE(&sp->gp->hq, sp, q); + CIRCLEQ_INSERT_AFTER(&csp->gp->dq, csp, sp, q); + SIGUNBLOCK(sp->gp); + + F_SET(sp, S_REDRAW); + return (0); +} + +/* + * svi_rabs -- + * Change the absolute size of the current screen. + */ +int +svi_rabs(sp, count, adj) + SCR *sp; + long count; + enum adjust adj; +{ + SCR *g, *s; + + /* + * Figure out which screens will grow, which will shrink, and + * make sure it's possible. + */ + if (count == 0) + return (0); + if (adj == A_SET) { + if (sp->t_maxrows == count) + return (0); + if (sp->t_maxrows > count) { + adj = A_DECREASE; + count = sp->t_maxrows - count; + } else { + adj = A_INCREASE; + count = count - sp->t_maxrows; + } + } + if (adj == A_DECREASE) { + if (count < 0) + count = -count; + s = sp; + if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) + goto toosmall; + if ((g = sp->q.cqe_prev) == (void *)&sp->gp->dq) { + if ((g = sp->q.cqe_next) == (void *)&sp->gp->dq) + goto toobig; + g->woff -= count; + } else + s->woff += count; + } else { + g = sp; + if ((s = sp->q.cqe_next) != (void *)&sp->gp->dq) + if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) + s = NULL; + else + s->woff += count; + else + s = NULL; + if (s == NULL) { + if ((s = sp->q.cqe_prev) == (void *)&sp->gp->dq) { +toobig: msgq(sp, M_BERR, "The screen cannot %s", + adj == A_DECREASE ? "shrink" : "grow"); + return (1); + } + if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) { +toosmall: msgq(sp, M_BERR, + "The screen can only shrink to %d rows", + MINIMUM_SCREEN_ROWS); + return (1); + } + g->woff -= count; + } + } + + /* + * Update the screens; we could optimize the reformatting of the + * screen, but this isn't likely to be a common enough operation + * to make it worthwhile. + */ + g->rows += count; + g->t_rows += count; + if (g->t_minrows == g->t_maxrows) + g->t_minrows += count; + g->t_maxrows += count; + _TMAP(g) += count; + (void)msg_status(g, g->ep, g->lno, 0); + F_SET(g, S_REFORMAT); + + s->rows -= count; + s->t_rows -= count; + s->t_maxrows -= count; + if (s->t_minrows > s->t_maxrows) + s->t_minrows = s->t_maxrows; + _TMAP(s) -= count; + (void)msg_status(s, s->ep, s->lno, 0); + F_SET(s, S_REFORMAT); + + return (0); +} diff --git a/usr.bin/vi/svi/svi_term.c b/usr.bin/vi/svi/svi_term.c new file mode 100644 index 00000000000..2b2add3c44a --- /dev/null +++ b/usr.bin/vi/svi/svi_term.c @@ -0,0 +1,310 @@ +/*- + * Copyright (c) 1993, 1994 + * 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_term.c 8.7 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "../vi/vcmd.h" +#include "excmd.h" +#include "svi_screen.h" + +/* + * XXX + * THIS REQUIRES THAT ALL SCREENS SHARE A TERMINAL TYPE. + */ +typedef struct _tklist { + char *ts; /* Key's termcap string. */ + char *output; /* Corresponding vi command. */ + char *name; /* Name. */ + u_char value; /* Special value (for lookup). */ +} TKLIST; +static TKLIST const c_tklist[] = { /* Command mappings. */ +#ifdef SYSV_CURSES + {"kil1", "O", "insert line"}, + {"kdch1", "x", "delete character"}, + {"kcud1", "j", "cursor down"}, + {"kel", "D", "delete to eol"}, + {"kind", "\004", "scroll down"}, + {"kll", "$", "go to eol"}, + {"khome", "^", "go to sol"}, + {"kich1", "i", "insert at cursor"}, + {"kdl1", "dd", "delete line"}, + {"kcub1", "h", "cursor left"}, + {"knp", "\006", "page down"}, + {"kpp", "\002", "page up"}, + {"kri", "\025", "scroll up"}, + {"ked", "dG", "delete to end of screen"}, + {"kcuf1", "l", "cursor right"}, + {"kcuu1", "k", "cursor up"}, +#else + {"kA", "O", "insert line"}, + {"kD", "x", "delete character"}, + {"kd", "j", "cursor down"}, + {"kE", "D", "delete to eol"}, + {"kF", "\004", "scroll down"}, + {"kH", "$", "go to eol"}, + {"kh", "^", "go to sol"}, + {"kI", "i", "insert at cursor"}, + {"kL", "dd", "delete line"}, + {"kl", "h", "cursor left"}, + {"kN", "\006", "page down"}, + {"kP", "\002", "page up"}, + {"kR", "\025", "scroll up"}, + {"kS", "dG", "delete to end of screen"}, + {"kr", "l", "cursor right"}, + {"ku", "k", "cursor up"}, +#endif + {NULL}, +}; +static TKLIST const m1_tklist[] = { /* Input mappings (lookup). */ + {NULL}, +}; +static TKLIST const m2_tklist[] = { /* Input mappings (set or delete). */ +#ifdef SYSV_CURSES + {"kcud1", "\033ja", "cursor down"}, + {"kcub1", "\033ha", "cursor left"}, + {"kcuu1", "\033ka", "cursor up"}, + {"kcuf1", "\033la", "cursor right"}, +#else + {"kd", "\033ja", "cursor down"}, + {"kl", "\033ha", "cursor left"}, + {"ku", "\033ka", "cursor up"}, + {"kr", "\033la", "cursor right"}, +#endif + {NULL}, +}; + +/* + * svi_term_init -- + * Initialize the special keys defined by the termcap/terminfo entry. + */ +int +svi_term_init(sp) + SCR *sp; +{ + KEYLIST *kp; + SEQ *qp; + TKLIST const *tkp; + size_t len; + char *sbp, *s, *t, sbuf[1024]; + + /* Command mappings. */ + for (tkp = c_tklist; tkp->name != NULL; ++tkp) { +#ifdef SYSV_CURSES + if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) + continue; +#else + sbp = sbuf; + if ((t = tgetstr(tkp->ts, &sbp)) == NULL) + continue; +#endif + if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t), + tkp->output, strlen(tkp->output), SEQ_COMMAND, SEQ_SCREEN)) + return (1); + } + + /* Input mappings needing to be looked up. */ + for (tkp = m1_tklist; tkp->name != NULL; ++tkp) { +#ifdef SYSV_CURSES + if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) + continue; +#else + sbp = sbuf; + if ((t = tgetstr(tkp->ts, &sbp)) == NULL) + continue; +#endif + for (kp = keylist;; ++kp) + if (kp->value == tkp->value) + break; + if (kp == NULL) + continue; + if (seq_set(sp, tkp->name, strlen(tkp->name), + t, strlen(t), &kp->ch, 1, SEQ_INPUT, SEQ_SCREEN)) + return (1); + } + + /* Input mappings that are already set or are text deletions. */ + for (tkp = m2_tklist; tkp->name != NULL; ++tkp) { +#ifdef SYSV_CURSES + if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) + continue; +#else + sbp = sbuf; + if ((t = tgetstr(tkp->ts, &sbp)) == NULL) + continue; +#endif + /* + * !!! + * Some terminals' <cursor_left> keys send single <backspace> + * characters. This is okay in command mapping, but not okay + * in input mapping. That combination is the only one we'll + * ever see, hopefully, so kluge it here for now. + */ + if (!strcmp(t, "\b")) + continue; + if (tkp->output == NULL) { + if (seq_set(sp, tkp->name, strlen(tkp->name), + t, strlen(t), NULL, 0, SEQ_INPUT, SEQ_SCREEN)) + return (1); + } else + if (seq_set(sp, tkp->name, strlen(tkp->name), + t, strlen(t), tkp->output, strlen(tkp->output), + SEQ_INPUT, SEQ_SCREEN)) + return (1); + } + + /* Rework any function key mappings. */ + for (qp = sp->gp->seqq.lh_first; qp != NULL; qp = qp->q.le_next) { + if (!F_ISSET(qp, SEQ_FUNCMAP)) + continue; + (void)svi_fmap(sp, qp->stype, + qp->input, qp->ilen, qp->output, qp->olen); + } + + /* Set up the visual bell information. */ + t = sbuf; + if (tgetstr("vb", &t) != NULL && (len = t - sbuf) != 0) { + MALLOC_RET(sp, s, char *, len); + memmove(s, sbuf, len); + if (SVP(sp)->VB != NULL) + free(SVP(sp)->VB); + SVP(sp)->VB = s; + return (0); + } + + return (0); +} + +/* + * svi_term_end -- + * End the special keys defined by the termcap/terminfo entry. + */ +int +svi_term_end(sp) + SCR *sp; +{ + SEQ *qp, *nqp; + + /* Delete screen specific mappings. */ + for (qp = sp->gp->seqq.lh_first; qp != NULL; qp = nqp) { + nqp = qp->q.le_next; + if (!F_ISSET(qp, SEQ_SCREEN)) + continue; + (void)seq_mdel(qp); + } + return (0); +} + +/* + * svi_fmap -- + * Map a function key. + */ +int +svi_fmap(sp, stype, from, flen, to, tlen) + SCR *sp; + enum seqtype stype; + CHAR_T *from, *to; + size_t flen, tlen; +{ + char *t, keyname[64]; + size_t nlen; + + /* If the terminal isn't initialized, there's nothing to do. */ + if (!F_ISSET(SVP(sp), SVI_CURSES_INIT)) + return (0); + +#ifdef SYSV_CURSES + (void)snprintf(keyname, sizeof(keyname), "kf%d", atoi(from + 1)); + if ((t = tigetstr(keyname)) == NULL || t == (char *)-1) + t = NULL; +#else + /* + * !!! + * Historically, the 4BSD termcap code didn't support functions keys + * greater than 9. This was silently enforced -- asking for key k12 + * returned the value for k1. We try and get around this by using + * the tables specified in the terminfo(TI_ENV) man page from the 3rd + * Edition SVID. This assumes that the implementors of any System V + * compatibility code or an extended termcap used those codes. + */ + { int n; char *sbp, sbuf[1024]; + static const char codes[] = { +/* 0-10 */ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';', +/* 11-19 */ '1', '2', '3', '4', '5', '6', '7', '8', '9', +/* 20-63 */ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + }; + if ((n = atoi(from + 1)) > 63) { + msgq(sp, M_ERR, + "Termcap has no code for the %s function key", + from); + return (1); + } + (void)snprintf(keyname, sizeof(keyname), + "%c%c", n <= 10 ? 'k' : 'F', codes[n]); + sbp = sbuf; + t = tgetstr(keyname, &sbp); + } +#endif + if (t == NULL) { + msgq(sp, M_ERR, "This terminal has no %s key", from); + return (1); + } + nlen = snprintf(keyname, + sizeof(keyname), "function key %d", atoi(from + 1)); + return (seq_set(sp, keyname, nlen, t, strlen(t), + to, tlen, stype, SEQ_SCREEN | SEQ_USERDEF)); +} diff --git a/usr.bin/vi/svi/svi_util.c b/usr.bin/vi/svi/svi_util.c new file mode 100644 index 00000000000..65422ede1a6 --- /dev/null +++ b/usr.bin/vi/svi/svi_util.c @@ -0,0 +1,347 @@ +/*- + * Copyright (c) 1993, 1994 + * 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_util.c 8.55 (Berkeley) 8/17/94"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "compat.h" +#include <curses.h> +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "../vi/vcmd.h" +#include "excmd.h" +#include "svi_screen.h" +#include "../sex/sex_screen.h" + +/* + * svi_bell -- + * Ring the bell. + */ +void +svi_bell(sp) + SCR *sp; +{ +#ifdef SYSV_CURSES + if (O_ISSET(sp, O_FLASH)) + flash(); + else + beep(); +#else + if (O_ISSET(sp, O_FLASH) && SVP(sp)->VB != NULL) { + (void)tputs(SVP(sp)->VB, 1, vi_putchar); + (void)fflush(stdout); + } else + (void)write(STDOUT_FILENO, "\007", 1); /* '\a' */ +#endif + F_CLR(sp, S_BELLSCHED); +} + +/* + * svi_optchange -- + * Screen specific "option changed" routine. + */ +int +svi_optchange(sp, opt) + SCR *sp; + int opt; +{ + switch (opt) { + case O_TERM: + /* Toss any saved visual bell information. */ + if (SVP(sp)->VB != NULL) { + FREE(SVP(sp)->VB, strlen(SVP(sp)->VB) + 1); + SVP(sp)->VB = NULL; + } + + /* Reset the screen size. */ + if (sp->s_window(sp, 0)) + return (1); + F_SET(sp, S_RESIZE); + break; + case O_WINDOW: + if (svi_crel(sp, O_VAL(sp, O_WINDOW))) + return (1); + break; + } + + (void)v_optchange(sp, opt); + (void)ex_optchange(sp, opt); + + return (0); +} + +/* + * svi_busy -- + * Put the cursor somewhere so the user will think we're busy. + */ +int +svi_busy(sp, msg) + SCR *sp; + char const *msg; +{ + /* + * search.c:f_search() is called from ex/ex_tag.c:ex_tagfirst(), + * which runs before the screen really exists. Make sure we don't + * step on anything. + * + * If the terminal isn't initialized, there's nothing to do. + */ + if (!F_ISSET(SVP(sp), SVI_CURSES_INIT)) + return (0); + + MOVE(sp, INFOLINE(sp), 0); + if (msg) { + ADDSTR(msg); + clrtoeol(); + } + refresh(); + F_SET(SVP(sp), SVI_CUR_INVALID); + return (0); +} + +/* + * svi_keypad -- + * Put the keypad/cursor arrows into or out of application mode. + */ +void +svi_keypad(sp, on) + SCR *sp; + int on; +{ +#ifdef SYSV_CURSES + keypad(stdscr, on ? TRUE : FALSE); +#else + char *sbp, *t, sbuf[128]; + + sbp = sbuf; + if ((t = tgetstr(on ? "ks" : "ke", &sbp)) == NULL) + return; + (void)tputs(t, 0, vi_putchar); + (void)fflush(stdout); +#endif +} + +/* + * svi_clear -- + * Clear from the row down to the end of the screen. + */ +int +svi_clear(sp) + SCR *sp; +{ + size_t oldy, oldx, row; + + getyx(stdscr, oldy, oldx); + for (row = SVP(sp)->srows - 1; row >= oldy; --row) { + MOVEA(sp, row, 0); + clrtoeol(); + } + MOVEA(sp, oldy, oldx); + refresh(); + return (0); +} + +/* + * svi_suspend -- + * Suspend an svi screen. + * + * See signal.c for a long discussion of what's going on here. Let + * me put it this way, it's NOT my fault. + */ +int +svi_suspend(sp) + SCR *sp; +{ + struct termios sv_term; + sigset_t set; + int oldx, oldy, rval; + char *sbp, *t, sbuf[128]; + + rval = 0; + + /* + * Block SIGALRM, because vi uses timers to decide when to paint + * busy messages on the screen. + */ + (void)sigemptyset(&set); + (void)sigaddset(&set, SIGALRM); + if (sigprocmask(SIG_BLOCK, &set, NULL)) { + msgq(sp, M_SYSERR, "suspend: sigblock"); + return (1); + } + + /* Save the current cursor position. */ + getyx(stdscr, oldy, oldx); + + /* + * Move the cursor to the bottom of the screen. + * + * XXX + * Some curses implementations don't turn off inverse video when + * standend() is called, waiting to see what the next character is + * going to be, instead. Write a character to force inverse video + * off, and then clear the line. + */ + MOVE(sp, INFOLINE(sp), 0); + ADDCH('.'); + refresh(); + MOVE(sp, INFOLINE(sp), 0); + clrtoeol(); + refresh(); + + /* Restore the cursor keys to normal mode. */ + svi_keypad(sp, 0); + + /* Send VE/TE. */ +#ifdef SYSV_CURSES + if ((t = tigetstr("cnorm")) != NULL && t != (char *)-1) + (void)tputs(t, 0, vi_putchar); + if ((t = tigetstr("rmcup")) != NULL && t != (char *)-1) + (void)tputs(t, 0, vi_putchar); +#else + sbp = sbuf; + if ((t = tgetstr("ve", &sbp)) != NULL) + (void)tputs(t, 0, vi_putchar); + sbp = sbuf; + if ((t = tgetstr("te", &sbp)) != NULL) + (void)tputs(t, 0, vi_putchar); +#endif + (void)fflush(stdout); + + /* Save current terminal settings, and restore the original ones. */ + if (tcgetattr(STDIN_FILENO, &sv_term)) { + msgq(sp, M_SYSERR, "suspend: tcgetattr"); + return (1); + } + if (tcsetattr(STDIN_FILENO, + TCSASOFT | TCSADRAIN, &sp->gp->original_termios)) { + msgq(sp, M_SYSERR, "suspend: tcsetattr original"); + return (1); + } + + /* Push out any waiting messages. */ + (void)write(STDOUT_FILENO, "\n", 1); + (void)sex_refresh(sp, sp->ep); + + /* Stop the process group. */ + if (kill(0, SIGTSTP)) { + msgq(sp, M_SYSERR, "suspend: kill"); + rval = 1; + } + + /* Time passes ... */ + + /* Restore current terminal settings. */ + if (tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &sv_term)) { + msgq(sp, M_SYSERR, "suspend: tcsetattr current"); + rval = 1; + } + + /* Send TI/VS. */ +#ifdef SYSV_CURSES + if ((t = tigetstr("smcup")) != NULL && t != (char *)-1) + (void)tputs(t, 0, vi_putchar); + if ((t = tigetstr("cvvis")) != NULL && t != (char *)-1) + (void)tputs(t, 0, vi_putchar); +#else + sbp = sbuf; + if ((t = tgetstr("ti", &sbp)) != NULL) + (void)tputs(t, 0, vi_putchar); + sbp = sbuf; + if ((t = tgetstr("vs", &sbp)) != NULL) + (void)tputs(t, 0, vi_putchar); +#endif + (void)fflush(stdout); + + /* Put the cursor keys into application mode. */ + svi_keypad(sp, 1); + + /* + * If the screen changed size, do a full refresh. Otherwise, + * System V has curses repaint it. 4BSD curses will repaint + * it in the wrefresh() call below. + */ + if (!sp->s_window(sp, 1)) + (void)sp->s_refresh(sp, sp->ep); +#ifdef SYSV_CURSES + else + redrawwin(stdscr); +#endif + + /* + * Restore the cursor. + * + * !!! + * Don't use MOVE/MOVEA, we don't want to return without resetting + * the signals, regardless. + */ + (void)move(oldy, oldx); + (void)wrefresh(curscr); + + /* Reset the signals. */ + if (sigprocmask(SIG_UNBLOCK, &set, NULL)) { + msgq(sp, M_SYSERR, "suspend: sigblock"); + rval = 1; + } + return (rval); +} + +/* + * svi_gdbrefresh -- + * Stub routine so can flush out screen changes using gdb. + */ +#ifdef DEBUG +int +svi_gdbrefresh() +{ + refresh(); + return (0); +} +#endif |