diff options
Diffstat (limited to 'usr.bin/vi/common/msg.c')
-rw-r--r-- | usr.bin/vi/common/msg.c | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/usr.bin/vi/common/msg.c b/usr.bin/vi/common/msg.c new file mode 100644 index 00000000000..36639c1c347 --- /dev/null +++ b/usr.bin/vi/common/msg.c @@ -0,0 +1,428 @@ +/*- + * Copyright (c) 1991, 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[] = "@(#)msg.c 8.12 (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 <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#ifdef __STDC__ +#include <stdarg.h> +#else +#include <varargs.h> +#endif + +#include "compat.h" +#include <db.h> +#include <regex.h> + +#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; + size_t 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. If sp is NULL, ignore the special cases and + * just build the message, using __global_list. + */ + if (sp == NULL) + goto nullsp; + + switch (mt) { + case M_BERR: + if (!F_ISSET(sp, S_EXSILENT) && + F_ISSET(sp->gp, G_STDIN_TTY) && !O_ISSET(sp, O_VERBOSE)) { + F_SET(sp, S_BELLSCHED); + return; + } + mt = M_ERR; + break; + case M_VINFO: + if (!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(); + } + +nullsp: len = 0; + +#define EPREFIX "Error: " + if (mt == M_SYSERR) { + memmove(msgbuf, EPREFIX, sizeof(EPREFIX) - 1); + len += sizeof(EPREFIX) - 1; + } + + if (sp != NULL && sp->if_name != NULL) { + len += snprintf(msgbuf + len, sizeof(msgbuf) - len, + "%s, %d: ", sp->if_name, sp->if_lno); + if (len >= sizeof(msgbuf)) + goto err; + } + + if (fmt != NULL) { + len += vsnprintf(msgbuf + len, sizeof(msgbuf) - len, fmt, ap); + if (len >= sizeof(msgbuf)) + goto err; + } + + if (mt == M_SYSERR) { + len += snprintf(msgbuf + len, + sizeof(msgbuf) - len, ": %s", strerror(errno)); + if (len >= sizeof(msgbuf)) + goto err; + } + + /* + * If len >= the size, some characters were discarded. + * Ignore trailing nul. + */ +err: if (len >= sizeof(msgbuf)) + len = sizeof(msgbuf) - 1; + +#ifdef DEBUG + if (sp != NULL) + TRACE(sp, "%.*s\n", len, msgbuf); +#endif + 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; + + /* + * We can be entered as the result of a signal arriving, trying + * to sync the file and failing. This shouldn't be a hot spot, + * block the signals. + */ + SIGBLOCK(gp); + + /* + * Find an empty structure, or allocate a new one. Use the + * screen structure if it exists, 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 && + (mp->mbuf = binc(sp, mp->mbuf, &mp->blen, len)) == NULL) + goto ret; + + /* Store the message. */ + memmove(mp->mbuf, p, len); + mp->len = len; + mp->flags = inv_video ? M_INV_VIDEO : 0; + +ret: reenter = 0; + SIGUNBLOCK(gp); +} + +/* + * msg_rpt -- + * 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. + * + * Line counts weren't right in historic vi, either. For example, given the + * file: + * abc + * def + * the command 2d}, from the 'b' would report that two lines were deleted, + * not one. + */ +int +msg_rpt(sp, is_message) + SCR *sp; + int is_message; +{ + static char * const action[] = { + "added", "changed", "deleted", "joined", "moved", + "left shifted", "right shifted", "yanked", + NULL, + }; + recno_t total; + u_long rptval; + int first, cnt; + size_t blen, len; + char * const *ap; + char *bp, *p, number[40]; + + if (F_ISSET(sp, S_EXSILENT)) + return (0); + + if ((rptval = 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 lines %s", + first ? "" : "; ", sp->rptlines[cnt], *ap); + memmove(p, number, len); + p += len; + first = 0; + } + + /* + * If nothing to report, return. + * + * !!! + * And now, a special vi clone test. Historically, vi reported if + * the number of changed lines was > than the value, not >=. Which + * means that users can't report on single line changes, btw.) In + * any case, if it was a yank command, it was >=, not >. No lie. I + * got complaints, so we do it right. + */ + if (total > rptval || sp->rptlines[L_YANKED] >= rptval) { + *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: sp->rptlchange = OOBLNO; + memset(sp->rptlines, 0, sizeof(sp->rptlines)); + return (0); +} + +/* + * msg_status -- + * Report on the file's status. + */ +int +msg_status(sp, ep, lno, showlast) + SCR *sp; + EXF *ep; + recno_t lno; + int showlast; +{ + recno_t last; + char *mo, *nc, *nf, *pid, *ro, *ul; +#ifdef DEBUG + char pbuf[50]; + + (void)snprintf(pbuf, sizeof(pbuf), " (pid %u)", getpid()); + pid = pbuf; +#else + pid = ""; +#endif + /* + * See nvi/exf.c:file_init() for a description of how and + * when the read-only bit is set. + * + * !!! + * The historic display for "name changed" was "[Not edited]". + */ + if (F_ISSET(sp->frp, FR_NEWFILE)) { + F_CLR(sp->frp, FR_NEWFILE); + nf = "new file"; + mo = nc = ""; + } else { + nf = ""; + if (F_ISSET(sp->frp, FR_NAMECHANGE)) { + nc = "name changed"; + mo = F_ISSET(ep, F_MODIFIED) ? + ", modified" : ", unmodified"; + } else { + nc = ""; + mo = F_ISSET(ep, F_MODIFIED) ? + "modified" : "unmodified"; + } + } + ro = F_ISSET(sp->frp, FR_RDONLY) ? ", readonly" : ""; + ul = F_ISSET(sp->frp, FR_UNLOCKED) ? ", UNLOCKED" : ""; + if (showlast) { + if (file_lline(sp, ep, &last)) + return (1); + if (last >= 1) + msgq(sp, M_INFO, + "%s: %s%s%s%s%s: line %lu of %lu [%ld%%]%s", + sp->frp->name, nf, nc, mo, ul, ro, lno, + last, (lno * 100) / last, pid); + else + msgq(sp, M_INFO, "%s: %s%s%s%s%s: empty file%s", + sp->frp->name, nf, nc, mo, ul, ro, pid); + } else + msgq(sp, M_INFO, "%s: %s%s%s%s%s: line %lu%s", + sp->frp->name, nf, nc, mo, ul, ro, lno, pid); + return (0); +} + +#ifdef MSG_CATALOG +/* + * get_msg -- + * Return a format based on a message number. + */ +char * +get_msg(sp, msgno) + SCR *sp; + char *s_msgno; +{ + DBT data, key; + GS *gp; + recno_t msgno; + char *msg, *p; + + gp = sp == NULL ? __global_list : sp->gp; + if (gp->msgdb == NULL) { + p = sp == NULL ? _PATH_MSGDEF : O_STR(sp, O_CATALOG); + if ((gp->msgdb = dbopen(p, + O_NONBLOCK | O_RDONLY, 444, DB_RECNO, NULL)) == NULL) { + if ((fmt = malloc(256)) == NULL) + return (""); + (void)snprintf(fmt, + "unable to open %s: %s", p, strerror(errno)); + return (fmt); + } + } + msgno = atoi(s_msgno); + key.data = &msgno; + key.size = sizeof(recno_t); + switch (gp->msgdb->get(gp->msgdb, &key, &data, 0)) { + case 0: + return (data.data); + case 1: + p = "no catalog record %ls"; + break; + case -1: + p = "catalog record %s: %s"; + break; + } + if ((fmt = malloc(256)) == NULL) + return (""); + (void)snprintf(fmt, p, msgno, strerror(errno)); + return (fmt); +} +#endif |