diff options
Diffstat (limited to 'usr.bin/vi/common/line.c')
-rw-r--r-- | usr.bin/vi/common/line.c | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/usr.bin/vi/common/line.c b/usr.bin/vi/common/line.c new file mode 100644 index 0000000..12d22e4 --- /dev/null +++ b/usr.bin/vi/common/line.c @@ -0,0 +1,492 @@ +/*- + * 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[] = "@(#)line.c 8.30 (Berkeley) 6/30/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 <string.h> +#include <termios.h> + +#include "compat.h" +#include <db.h> +#include <regex.h> + +#include "vi.h" +#include "excmd.h" + +static __inline int scr_update + __P((SCR *, EXF *, recno_t, enum operation, int)); + +/* + * file_gline -- + * Look in the text buffers for a line; if it's not there + * call file_rline to retrieve it from the database. + */ +char * +file_gline(sp, ep, lno, lenp) + SCR *sp; + EXF *ep; + recno_t lno; /* Line number. */ + size_t *lenp; /* Length store. */ +{ + TEXT *tp; + recno_t l1, l2; + + /* + * The underlying recno stuff handles zero by returning NULL, but + * have to have an oob condition for the look-aside into the input + * buffer anyway. + */ + if (lno == 0) + return (NULL); + + /* + * Look-aside into the TEXT buffers and see if the line we want + * is there. + */ + if (F_ISSET(sp, S_INPUT)) { + l1 = ((TEXT *)sp->tiqp->cqh_first)->lno; + l2 = ((TEXT *)sp->tiqp->cqh_last)->lno; + if (l1 <= lno && l2 >= lno) { + for (tp = sp->tiqp->cqh_first; + tp->lno != lno; tp = tp->q.cqe_next); + if (lenp) + *lenp = tp->len; + return (tp->lb); + } + /* + * Adjust the line number for the number of lines used + * by the text input buffers. + */ + if (lno > l2) + lno -= l2 - l1; + } + return (file_rline(sp, ep, lno, lenp)); +} + +/* + * file_rline -- + * Look in the cache for a line; if it's not there retrieve + * it from the file. + */ +char * +file_rline(sp, ep, lno, lenp) + SCR *sp; + EXF *ep; + recno_t lno; /* Line number. */ + size_t *lenp; /* Length store. */ +{ + DBT data, key; + + /* Check the cache. */ + if (lno == ep->c_lno) { + if (lenp) + *lenp = ep->c_len; + return (ep->c_lp); + } + ep->c_lno = OOBLNO; + + /* Get the line from the underlying database. */ + key.data = &lno; + key.size = sizeof(lno); + switch (ep->db->get(ep->db, &key, &data, 0)) { + case -1: + msgq(sp, M_ERR, + "Error: %s/%d: unable to get line %u: %s", + tail(__FILE__), __LINE__, lno, strerror(errno)); + /* FALLTHROUGH */ + case 1: + return (NULL); + /* NOTREACHED */ + } + if (lenp) + *lenp = data.size; + + /* Fill the cache. */ + ep->c_lno = lno; + ep->c_len = data.size; + ep->c_lp = data.data; + + return (data.data); +} + +/* + * file_dline -- + * Delete a line from the file. + */ +int +file_dline(sp, ep, lno) + SCR *sp; + EXF *ep; + recno_t lno; +{ + DBT key; + +#if defined(DEBUG) && 0 + TRACE(sp, "delete line %lu\n", lno); +#endif + /* + * XXX + * Marks and global commands have to know when lines are + * inserted or deleted. + */ + mark_insdel(sp, ep, LINE_DELETE, lno); + global_insdel(sp, ep, LINE_DELETE, lno); + + /* Log change. */ + log_line(sp, ep, lno, LOG_LINE_DELETE); + + /* Update file. */ + key.data = &lno; + key.size = sizeof(lno); + SIGBLOCK(sp->gp); + if (ep->db->del(ep->db, &key, 0) == 1) { + msgq(sp, M_ERR, + "Error: %s/%d: unable to delete line %u: %s", + tail(__FILE__), __LINE__, lno, strerror(errno)); + return (1); + } + SIGUNBLOCK(sp->gp); + + /* Flush the cache, update line count, before screen update. */ + if (lno <= ep->c_lno) + ep->c_lno = OOBLNO; + if (ep->c_nlines != OOBLNO) + --ep->c_nlines; + + /* File now dirty. */ + if (F_ISSET(ep, F_FIRSTMODIFY)) + (void)rcv_init(sp, ep); + F_SET(ep, F_MODIFIED); + + /* Update screen. */ + return (scr_update(sp, ep, lno, LINE_DELETE, 1)); +} + +/* + * file_aline -- + * Append a line into the file. + */ +int +file_aline(sp, ep, update, lno, p, len) + SCR *sp; + EXF *ep; + int update; + recno_t lno; + char *p; + size_t len; +{ + DBT data, key; + recno_t lline; + +#if defined(DEBUG) && 0 + TRACE(sp, "append to %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p); +#endif + /* + * XXX + * Very nasty special case. The historic vi code displays a single + * space (or a '$' if the list option is set) for the first line in + * an "empty" file. If we "insert" a line, that line gets scrolled + * down, not repainted, so it's incorrect when we refresh the the + * screen. This is really hard to find and fix in the vi code -- the + * text input functions detect it explicitly and don't insert a new + * line. The hack here is to repaint the screen if we're appending + * to an empty file. The reason that the test is in file_aline, and + * not in file_iline or file_sline, is that all of the ex commands + * that work in empty files end up here. + */ + if (lno == 0) { + if (file_lline(sp, ep, &lline)) + return (1); + if (lline == 0) + F_SET(sp, S_REDRAW); + } + + /* Update file. */ + key.data = &lno; + key.size = sizeof(lno); + data.data = p; + data.size = len; + SIGBLOCK(sp->gp); + if (ep->db->put(ep->db, &key, &data, R_IAFTER) == -1) { + msgq(sp, M_ERR, + "Error: %s/%d: unable to append to line %u: %s", + tail(__FILE__), __LINE__, lno, strerror(errno)); + return (1); + } + SIGUNBLOCK(sp->gp); + + /* Flush the cache, update line count, before screen update. */ + if (lno < ep->c_lno) + ep->c_lno = OOBLNO; + if (ep->c_nlines != OOBLNO) + ++ep->c_nlines; + + /* File now dirty. */ + if (F_ISSET(ep, F_FIRSTMODIFY)) + (void)rcv_init(sp, ep); + F_SET(ep, F_MODIFIED); + + /* Log change. */ + log_line(sp, ep, lno + 1, LOG_LINE_APPEND); + + /* + * XXX + * Marks and global commands have to know when lines are + * inserted or deleted. + * + * XXX + * See comment above about empty files. If the file was empty, + * then we're adding the first line, which is a replacement, not + * an append. So, we shouldn't whack the marks. + */ + if (lno != 0) { + mark_insdel(sp, ep, LINE_INSERT, lno + 1); + global_insdel(sp, ep, LINE_INSERT, lno + 1); + } + + /* + * Update screen. + * + * XXX + * Nasty hack. If multiple lines are input by the user, they aren't + * committed until an <ESC> is entered. The problem is the screen was + * updated/scrolled as each line was entered. So, when this routine + * is called to copy the new lines from the cut buffer into the file, + * it has to know not to update the screen again. + */ + return (scr_update(sp, ep, lno, LINE_APPEND, update)); +} + +/* + * file_iline -- + * Insert a line into the file. + */ +int +file_iline(sp, ep, lno, p, len) + SCR *sp; + EXF *ep; + recno_t lno; + char *p; + size_t len; +{ + DBT data, key; + recno_t lline; + +#if defined(DEBUG) && 0 + TRACE(sp, + "insert before %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p); +#endif + + /* Very nasty special case. See comment in file_aline(). */ + if (lno == 1) { + if (file_lline(sp, ep, &lline)) + return (1); + if (lline == 0) + F_SET(sp, S_REDRAW); + } + + /* Update file. */ + key.data = &lno; + key.size = sizeof(lno); + data.data = p; + data.size = len; + SIGBLOCK(sp->gp); + if (ep->db->put(ep->db, &key, &data, R_IBEFORE) == -1) { + msgq(sp, M_ERR, + "Error: %s/%d: unable to insert at line %u: %s", + tail(__FILE__), __LINE__, lno, strerror(errno)); + return (1); + } + SIGUNBLOCK(sp->gp); + + /* Flush the cache, update line count, before screen update. */ + if (lno >= ep->c_lno) + ep->c_lno = OOBLNO; + if (ep->c_nlines != OOBLNO) + ++ep->c_nlines; + + /* File now dirty. */ + if (F_ISSET(ep, F_FIRSTMODIFY)) + (void)rcv_init(sp, ep); + F_SET(ep, F_MODIFIED); + + /* Log change. */ + log_line(sp, ep, lno, LOG_LINE_INSERT); + + /* + * XXX + * Marks and global commands have to know when lines are + * inserted or deleted. + */ + mark_insdel(sp, ep, LINE_INSERT, lno); + global_insdel(sp, ep, LINE_INSERT, lno); + + /* Update screen. */ + return (scr_update(sp, ep, lno, LINE_INSERT, 1)); +} + +/* + * file_sline -- + * Store a line in the file. + */ +int +file_sline(sp, ep, lno, p, len) + SCR *sp; + EXF *ep; + recno_t lno; + char *p; + size_t len; +{ + DBT data, key; + +#if defined(DEBUG) && 0 + TRACE(sp, + "replace line %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p); +#endif + /* Log before change. */ + log_line(sp, ep, lno, LOG_LINE_RESET_B); + + /* Update file. */ + key.data = &lno; + key.size = sizeof(lno); + data.data = p; + data.size = len; + SIGBLOCK(sp->gp); + if (ep->db->put(ep->db, &key, &data, 0) == -1) { + msgq(sp, M_ERR, + "Error: %s/%d: unable to store line %u: %s", + tail(__FILE__), __LINE__, lno, strerror(errno)); + return (1); + } + SIGUNBLOCK(sp->gp); + + /* Flush the cache, before logging or screen update. */ + if (lno == ep->c_lno) + ep->c_lno = OOBLNO; + + /* File now dirty. */ + if (F_ISSET(ep, F_FIRSTMODIFY)) + (void)rcv_init(sp, ep); + F_SET(ep, F_MODIFIED); + + /* Log after change. */ + log_line(sp, ep, lno, LOG_LINE_RESET_F); + + /* Update screen. */ + return (scr_update(sp, ep, lno, LINE_RESET, 1)); +} + +/* + * file_lline -- + * Return the number of lines in the file. + */ +int +file_lline(sp, ep, lnop) + SCR *sp; + EXF *ep; + recno_t *lnop; +{ + DBT data, key; + recno_t lno; + + /* Check the cache. */ + if (ep->c_nlines != OOBLNO) { + *lnop = (F_ISSET(sp, S_INPUT) && + ((TEXT *)sp->tiqp->cqh_last)->lno > ep->c_nlines ? + ((TEXT *)sp->tiqp->cqh_last)->lno : ep->c_nlines); + return (0); + } + + key.data = &lno; + key.size = sizeof(lno); + + switch (ep->db->seq(ep->db, &key, &data, R_LAST)) { + case -1: + msgq(sp, M_ERR, + "Error: %s/%d: unable to get last line: %s", + tail(__FILE__), __LINE__, strerror(errno)); + *lnop = 0; + return (1); + case 1: + *lnop = 0; + return (0); + default: + break; + } + + /* Fill the cache. */ + memmove(&lno, key.data, sizeof(lno)); + ep->c_nlines = ep->c_lno = lno; + ep->c_len = data.size; + ep->c_lp = data.data; + + /* Return the value. */ + *lnop = (F_ISSET(sp, S_INPUT) && + ((TEXT *)sp->tiqp->cqh_last)->lno > lno ? + ((TEXT *)sp->tiqp->cqh_last)->lno : lno); + return (0); +} + +/* + * scr_update -- + * Update all of the screens that are backed by the file that + * just changed. + */ +static __inline int +scr_update(sp, ep, lno, op, current) + SCR *sp; + EXF *ep; + recno_t lno; + enum operation op; + int current; +{ + SCR *tsp; + + if (ep->refcnt != 1) + for (tsp = sp->gp->dq.cqh_first; + tsp != (void *)&sp->gp->dq; tsp = tsp->q.cqe_next) + if (sp != tsp && tsp->ep == ep) + (void)sp->s_change(tsp, ep, lno, op); + return (current && sp->s_change(sp, ep, lno, op)); +} |