aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/vi/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/vi/util.c')
-rw-r--r--usr.bin/vi/util.c578
1 files changed, 578 insertions, 0 deletions
diff --git a/usr.bin/vi/util.c b/usr.bin/vi/util.c
new file mode 100644
index 000000000000..79a06516af48
--- /dev/null
+++ b/usr.bin/vi/util.c
@@ -0,0 +1,578 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)util.c 8.34 (Berkeley) 12/23/93";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <ctype.h>
+#include <curses.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+
+#include "vi.h"
+
+/*
+ * msgq --
+ * Display a message.
+ */
+void
+#ifdef __STDC__
+msgq(SCR *sp, enum msgtype mt, const char *fmt, ...)
+#else
+msgq(sp, mt, fmt, va_alist)
+ SCR *sp;
+ enum msgtype mt;
+ char *fmt;
+ va_dcl
+#endif
+{
+ va_list ap;
+ int len;
+ char msgbuf[1024];
+
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ /*
+ * It's possible to enter msg when there's no screen to hold
+ * the message. Always check sp before using it, and, if it's
+ * NULL, use __global_list.
+ */
+ switch (mt) {
+ case M_BERR:
+ if (sp != NULL && !O_ISSET(sp, O_VERBOSE)) {
+ F_SET(sp, S_BELLSCHED);
+ return;
+ }
+ mt = M_ERR;
+ break;
+ case M_VINFO:
+ if (sp != NULL && !O_ISSET(sp, O_VERBOSE))
+ return;
+ mt = M_INFO;
+ /* FALLTHROUGH */
+ case M_INFO:
+ if (F_ISSET(sp, S_EXSILENT))
+ return;
+ break;
+ case M_ERR:
+ case M_SYSERR:
+ break;
+ default:
+ abort();
+ }
+
+ /* Length is the min length of the message or the buffer. */
+ if (mt == M_SYSERR)
+ if (sp->if_name != NULL)
+ len = snprintf(msgbuf, sizeof(msgbuf),
+ "Error: %s, %d: %s%s%s.", sp->if_name, sp->if_lno,
+ fmt == NULL ? "" : fmt, fmt == NULL ? "" : ": ",
+ strerror(errno));
+ else
+ len = snprintf(msgbuf, sizeof(msgbuf),
+ "Error: %s%s%s.",
+ fmt == NULL ? "" : fmt, fmt == NULL ? "" : ": ",
+ strerror(errno));
+ else {
+ len = sp->if_name == NULL ? 0 : snprintf(msgbuf, sizeof(msgbuf),
+ "%s, %d: ", sp->if_name, sp->if_lno);
+ len += vsnprintf(msgbuf + len, sizeof(msgbuf) - len, fmt, ap);
+ }
+
+ /*
+ * If len >= the size, some characters were discarded.
+ * Ignore trailing nul.
+ */
+ if (len >= sizeof(msgbuf))
+ len = sizeof(msgbuf) - 1;
+
+ msg_app(__global_list, sp, mt == M_ERR ? 1 : 0, msgbuf, len);
+}
+
+/*
+ * msg_app --
+ * Append a message into the queue. This can fail, but there's
+ * nothing we can do if it does.
+ */
+void
+msg_app(gp, sp, inv_video, p, len)
+ GS *gp;
+ SCR *sp;
+ int inv_video;
+ char *p;
+ size_t len;
+{
+ static int reenter; /* STATIC: Re-entrancy check. */
+ MSG *mp, *nmp;
+
+ /*
+ * It's possible to reenter msg when it allocates space.
+ * We're probably dead anyway, but no reason to drop core.
+ */
+ if (reenter)
+ return;
+ reenter = 1;
+
+ /*
+ * Find an empty structure, or allocate a new one. Use the
+ * screen structure if possible, otherwise the global one.
+ */
+ if (sp != NULL) {
+ if ((mp = sp->msgq.lh_first) == NULL) {
+ CALLOC(sp, mp, MSG *, 1, sizeof(MSG));
+ if (mp == NULL)
+ goto ret;
+ LIST_INSERT_HEAD(&sp->msgq, mp, q);
+ goto store;
+ }
+ } else if ((mp = gp->msgq.lh_first) == NULL) {
+ CALLOC(sp, mp, MSG *, 1, sizeof(MSG));
+ if (mp == NULL)
+ goto ret;
+ LIST_INSERT_HEAD(&gp->msgq, mp, q);
+ goto store;
+ }
+ while (!F_ISSET(mp, M_EMPTY) && mp->q.le_next != NULL)
+ mp = mp->q.le_next;
+ if (!F_ISSET(mp, M_EMPTY)) {
+ CALLOC(sp, nmp, MSG *, 1, sizeof(MSG));
+ if (nmp == NULL)
+ goto ret;
+ LIST_INSERT_AFTER(mp, nmp, q);
+ mp = nmp;
+ }
+
+ /* Get enough memory for the message. */
+store: if (len > mp->blen && binc(sp, &mp->mbuf, &mp->blen, len))
+ goto ret;
+
+ /* Store the message. */
+ memmove(mp->mbuf, p, len);
+ mp->len = len;
+ mp->flags = inv_video ? M_INV_VIDEO : 0;
+
+ret: reenter = 0;
+}
+
+/*
+ * msgrpt --
+ * Report on the lines that changed.
+ *
+ * !!!
+ * Historic vi documentation (USD:15-8) claimed that "The editor will also
+ * always tell you when a change you make affects text which you cannot see."
+ * This isn't true -- edit a large file and do "100d|1". We don't implement
+ * this semantic as it would require that we track each line that changes
+ * during a command instead of just keeping count.
+ */
+int
+msg_rpt(sp, is_message)
+ SCR *sp;
+ int is_message;
+{
+ static const char *const action[] = {
+ "added", "changed", "copied", "deleted", "joined", "moved",
+ "put", "left shifted", "right shifted", "yanked", NULL,
+ };
+ recno_t total;
+ u_long rval;
+ int first, cnt;
+ size_t blen, len;
+ const char *const *ap;
+ char *bp, *p, number[40];
+
+ if (F_ISSET(sp, S_EXSILENT))
+ return (0);
+
+ if ((rval = O_VAL(sp, O_REPORT)) == 0)
+ goto norpt;
+
+ GET_SPACE_RET(sp, bp, blen, 512);
+ p = bp;
+
+ total = 0;
+ for (ap = action, cnt = 0, first = 1; *ap != NULL; ++ap, ++cnt)
+ if (sp->rptlines[cnt] != 0) {
+ total += sp->rptlines[cnt];
+ len = snprintf(number, sizeof(number),
+ "%s%lu line%s %s", first ? "" : "; ",
+ sp->rptlines[cnt],
+ sp->rptlines[cnt] > 1 ? "s" : "", *ap);
+ memmove(p, number, len);
+ p += len;
+ first = 0;
+ }
+
+ /*
+ * If nothing to report, return. Note that the number of lines
+ * must be > than the user's value, not >=. This is historic
+ * practice and means that users cannot report on single line
+ * changes.
+ */
+ if (total > rval) {
+ *p = '\0';
+
+ if (is_message)
+ msgq(sp, M_INFO, "%s", bp);
+ else
+ ex_printf(EXCOOKIE, "%s\n", bp);
+ }
+
+ FREE_SPACE(sp, bp, blen);
+
+ /* Clear after each report. */
+norpt: memset(sp->rptlines, 0, sizeof(sp->rptlines));
+ return (0);
+}
+
+/*
+ * binc --
+ * Increase the size of a buffer.
+ */
+int
+binc(sp, argp, bsizep, min)
+ SCR *sp; /* MAY BE NULL */
+ void *argp;
+ size_t *bsizep, min;
+{
+ void *bpp;
+ size_t csize;
+
+ /* If already larger than the minimum, just return. */
+ csize = *bsizep;
+ if (min && csize >= min)
+ return (0);
+
+ csize += MAX(min, 256);
+ bpp = *(char **)argp;
+
+ /* For non-ANSI C realloc implementations. */
+ if (bpp == NULL)
+ bpp = malloc(csize * sizeof(CHAR_T));
+ else
+ bpp = realloc(bpp, csize * sizeof(CHAR_T));
+ if (bpp == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ *bsizep = 0;
+ return (1);
+ }
+ *(char **)argp = bpp;
+ *bsizep = csize;
+ return (0);
+}
+
+/*
+ * nonblank --
+ * Set the column number of the first non-blank character
+ * including or after the starting column. On error, set
+ * the column to 0, it's safest.
+ */
+int
+nonblank(sp, ep, lno, cnop)
+ SCR *sp;
+ EXF *ep;
+ recno_t lno;
+ size_t *cnop;
+{
+ char *p;
+ size_t cnt, len, off;
+
+ /* Default. */
+ off = *cnop;
+ *cnop = 0;
+
+ /* Get the line. */
+ if ((p = file_gline(sp, ep, lno, &len)) == NULL) {
+ if (file_lline(sp, ep, &lno))
+ return (1);
+ if (lno == 0)
+ return (0);
+ GETLINE_ERR(sp, lno);
+ return (1);
+ }
+
+ /* Set the offset. */
+ if (len == 0 || off >= len)
+ return (0);
+
+ for (cnt = off, p = &p[off],
+ len -= off; len && isblank(*p); ++cnt, ++p, --len);
+
+ /* Set the return. */
+ *cnop = len ? cnt : cnt - 1;
+ return (0);
+}
+
+/*
+ * tail --
+ * Return tail of a path.
+ */
+char *
+tail(path)
+ char *path;
+{
+ char *p;
+
+ if ((p = strrchr(path, '/')) == NULL)
+ return (path);
+ return (p + 1);
+}
+
+/*
+ * set_window_size --
+ * Set the window size, the row may be provided as an argument.
+ */
+int
+set_window_size(sp, set_row, ign_env)
+ SCR *sp;
+ u_int set_row;
+ int ign_env;
+{
+ struct winsize win;
+ size_t col, row;
+ int user_set;
+ ARGS *argv[2], a, b;
+ char *s, buf[2048];
+
+ /*
+ * Get the screen rows and columns. If the values are wrong, it's
+ * not a big deal -- as soon as the user sets them explicitly the
+ * environment will be set and the screen package will use the new
+ * values.
+ *
+ * Try TIOCGWINSZ.
+ */
+ row = col = 0;
+#ifdef TIOCGWINSZ
+ if (ioctl(STDERR_FILENO, TIOCGWINSZ, &win) != -1) {
+ row = win.ws_row;
+ col = win.ws_col;
+ }
+#endif
+
+ /* If TIOCGWINSZ failed, or had entries of 0, try termcap. */
+ if (row == 0 || col == 0) {
+ s = NULL;
+ if (F_ISSET(&sp->opts[O_TERM], OPT_SET))
+ s = O_STR(sp, O_TERM);
+ else
+ s = getenv("TERM");
+ if (s != NULL && tgetent(buf, s) == 1) {
+ if (row == 0)
+ row = tgetnum("li");
+ if (col == 0)
+ col = tgetnum("co");
+ }
+ }
+ /* If nothing else, well, it's probably a VT100. */
+ if (row == 0)
+ row = 24;
+ if (col == 0)
+ col = 80;
+
+ /*
+ * POSIX 1003.2 requires the environment to override, however,
+ * if we're here because of a signal, we don't want to use the
+ * old values.
+ */
+ if (!ign_env) {
+ if ((s = getenv("LINES")) != NULL)
+ row = strtol(s, NULL, 10);
+ if ((s = getenv("COLUMNS")) != NULL)
+ col = strtol(s, NULL, 10);
+ }
+
+ /* But, if we got an argument for the rows, use it. */
+ if (set_row)
+ row = set_row;
+
+ a.bp = buf;
+ b.bp = NULL;
+ b.len = 0;
+ argv[0] = &a;
+ argv[1] = &b;;
+
+ /*
+ * Tell the options code that the screen size has changed.
+ * Since the user didn't do the set, clear the set bits.
+ */
+ user_set = F_ISSET(&sp->opts[O_LINES], OPT_SET);
+ a.len = snprintf(buf, sizeof(buf), "lines=%u", row);
+ if (opts_set(sp, argv))
+ return (1);
+ if (user_set)
+ F_CLR(&sp->opts[O_LINES], OPT_SET);
+ user_set = F_ISSET(&sp->opts[O_COLUMNS], OPT_SET);
+ a.len = snprintf(buf, sizeof(buf), "columns=%u", col);
+ if (opts_set(sp, argv))
+ return (1);
+ if (user_set)
+ F_CLR(&sp->opts[O_COLUMNS], OPT_SET);
+ return (0);
+}
+
+/*
+ * set_alt_name --
+ * Set the alternate file name.
+ *
+ * Swap the alternate file name. It's a routine because I wanted some place
+ * to hang this comment. The alternate file name (normally referenced using
+ * the special character '#' during file expansion) is set by many
+ * operations. In the historic vi, the commands "ex", and "edit" obviously
+ * set the alternate file name because they switched the underlying file.
+ * Less obviously, the "read", "file", "write" and "wq" commands set it as
+ * well. In this implementation, some new commands have been added to the
+ * list. Where it gets interesting is that the alternate file name is set
+ * multiple times by some commands. If an edit attempt fails (for whatever
+ * reason, like the current file is modified but as yet unwritten), it is
+ * set to the file name that the user was unable to edit. If the edit
+ * succeeds, it is set to the last file name that was edited. Good fun.
+ *
+ * If the user edits a temporary file, there are time when there isn't an
+ * alternative file name. A name argument of NULL turns it off.
+ */
+void
+set_alt_name(sp, name)
+ SCR *sp;
+ char *name;
+{
+ if (sp->alt_name != NULL)
+ FREE(sp->alt_name, strlen(sp->alt_name) + 1);
+ if (name == NULL)
+ sp->alt_name = NULL;
+ else if ((sp->alt_name = strdup(name)) == NULL)
+ msgq(sp, M_SYSERR, NULL);
+}
+
+/*
+ * baud_from_bval --
+ * Return the baud rate using the standard defines.
+ */
+u_long
+baud_from_bval(sp)
+ SCR *sp;
+{
+ speed_t v;
+
+ switch (v = cfgetospeed(&sp->gp->original_termios)) {
+ case B50:
+ return (50);
+ case B75:
+ return (75);
+ case B110:
+ return (110);
+ case B134:
+ return (134);
+ case B150:
+ return (150);
+ case B200:
+ return (200);
+ case B300:
+ return (300);
+ case B600:
+ return (600);
+ case B1200:
+ return (1200);
+ case B1800:
+ return (1800);
+ case B2400:
+ return (2400);
+ case B4800:
+ return (4800);
+ case B0: /* Hangup -- ignore. */
+ case B9600:
+ return (9600);
+ case B19200:
+ return (19200);
+ case B38400:
+ return (38400);
+ default:
+ /*
+ * EXTA and EXTB aren't required by POSIX 1003.1, and
+ * are almost certainly the same as some of the above
+ * values, so they can't be part of the case statement.
+ */
+#ifdef EXTA
+ if (v == EXTA)
+ return (19200);
+#endif
+#ifdef EXTB
+ if (v == EXTB)
+ return (38400);
+#endif
+#ifdef B57600
+ if (v == B57600)
+ return (57600);
+#endif
+#ifdef B115200
+ if (v == B115200)
+ return (115200);
+#endif
+ msgq(sp, M_ERR, "Unknown terminal baud rate %u.\n", v);
+ return (9600);
+ }
+}
+
+/*
+ * v_strdup --
+ * Strdup for wide character strings with an associated length.
+ */
+CHAR_T *
+v_strdup(sp, str, len)
+ SCR *sp;
+ CHAR_T *str;
+ size_t len;
+{
+ CHAR_T *copy;
+
+ MALLOC(sp, copy, CHAR_T *, len);
+ if (copy == NULL)
+ return (NULL);
+ memmove(copy, str, len * sizeof(CHAR_T));
+ return (copy);
+}