summaryrefslogtreecommitdiffstats
path: root/usr.bin/vi/svi/svi_ex.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/vi/svi/svi_ex.c')
-rw-r--r--usr.bin/vi/svi/svi_ex.c650
1 files changed, 650 insertions, 0 deletions
diff --git a/usr.bin/vi/svi/svi_ex.c b/usr.bin/vi/svi/svi_ex.c
new file mode 100644
index 0000000..9b81080b
--- /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.54 (Berkeley) 8/14/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);
+}
OpenPOWER on IntegriCloud