diff options
Diffstat (limited to 'contrib/nvi/vi')
40 files changed, 16669 insertions, 0 deletions
diff --git a/contrib/nvi/vi/getc.c b/contrib/nvi/vi/getc.c new file mode 100644 index 0000000..5e60ade --- /dev/null +++ b/contrib/nvi/vi/getc.c @@ -0,0 +1,234 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)getc.c 10.10 (Berkeley) 3/6/96"; +#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 <stdio.h> +#include <stdlib.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * Character stream routines -- + * These routines return the file a character at a time. There are two + * special cases. First, the end of a line, end of a file, start of a + * file and empty lines are returned as special cases, and no character + * is returned. Second, empty lines include lines that have only white + * space in them, because the vi search functions don't care about white + * space, and this makes it easier for them to be consistent. + */ + +/* + * cs_init -- + * Initialize character stream routines. + * + * PUBLIC: int cs_init __P((SCR *, VCS *)); + */ +int +cs_init(sp, csp) + SCR *sp; + VCS *csp; +{ + int isempty; + + if (db_eget(sp, csp->cs_lno, &csp->cs_bp, &csp->cs_len, &isempty)) { + if (isempty) + msgq(sp, M_BERR, "177|Empty file"); + return (1); + } + if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) { + csp->cs_cno = 0; + csp->cs_flags = CS_EMP; + } else { + csp->cs_flags = 0; + csp->cs_ch = csp->cs_bp[csp->cs_cno]; + } + return (0); +} + +/* + * cs_next -- + * Retrieve the next character. + * + * PUBLIC: int cs_next __P((SCR *, VCS *)); + */ +int +cs_next(sp, csp) + SCR *sp; + VCS *csp; +{ + char *p; + + switch (csp->cs_flags) { + case CS_EMP: /* EMP; get next line. */ + case CS_EOL: /* EOL; get next line. */ + if (db_get(sp, ++csp->cs_lno, 0, &p, &csp->cs_len)) { + --csp->cs_lno; + csp->cs_flags = CS_EOF; + } else { + csp->cs_bp = p; + if (csp->cs_len == 0 || + v_isempty(csp->cs_bp, csp->cs_len)) { + csp->cs_cno = 0; + csp->cs_flags = CS_EMP; + } else { + csp->cs_flags = 0; + csp->cs_ch = csp->cs_bp[csp->cs_cno = 0]; + } + } + break; + case 0: + if (csp->cs_cno == csp->cs_len - 1) + csp->cs_flags = CS_EOL; + else + csp->cs_ch = csp->cs_bp[++csp->cs_cno]; + break; + case CS_EOF: /* EOF. */ + break; + default: + abort(); + /* NOTREACHED */ + } + return (0); +} + +/* + * cs_fspace -- + * If on a space, eat forward until something other than a + * whitespace character. + * + * XXX + * Semantics of checking the current character were coded for the fword() + * function -- once the other word routines are converted, they may have + * to change. + * + * PUBLIC: int cs_fspace __P((SCR *, VCS *)); + */ +int +cs_fspace(sp, csp) + SCR *sp; + VCS *csp; +{ + if (csp->cs_flags != 0 || !isblank(csp->cs_ch)) + return (0); + for (;;) { + if (cs_next(sp, csp)) + return (1); + if (csp->cs_flags != 0 || !isblank(csp->cs_ch)) + break; + } + return (0); +} + +/* + * cs_fblank -- + * Eat forward to the next non-whitespace character. + * + * PUBLIC: int cs_fblank __P((SCR *, VCS *)); + */ +int +cs_fblank(sp, csp) + SCR *sp; + VCS *csp; +{ + for (;;) { + if (cs_next(sp, csp)) + return (1); + if (csp->cs_flags == CS_EOL || csp->cs_flags == CS_EMP || + csp->cs_flags == 0 && isblank(csp->cs_ch)) + continue; + break; + } + return (0); +} + +/* + * cs_prev -- + * Retrieve the previous character. + * + * PUBLIC: int cs_prev __P((SCR *, VCS *)); + */ +int +cs_prev(sp, csp) + SCR *sp; + VCS *csp; +{ + switch (csp->cs_flags) { + case CS_EMP: /* EMP; get previous line. */ + case CS_EOL: /* EOL; get previous line. */ + if (csp->cs_lno == 1) { /* SOF. */ + csp->cs_flags = CS_SOF; + break; + } + if (db_get(sp, /* The line should exist. */ + --csp->cs_lno, DBG_FATAL, &csp->cs_bp, &csp->cs_len)) { + ++csp->cs_lno; + return (1); + } + if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) { + csp->cs_cno = 0; + csp->cs_flags = CS_EMP; + } else { + csp->cs_flags = 0; + csp->cs_cno = csp->cs_len - 1; + csp->cs_ch = csp->cs_bp[csp->cs_cno]; + } + break; + case CS_EOF: /* EOF: get previous char. */ + case 0: + if (csp->cs_cno == 0) + if (csp->cs_lno == 1) + csp->cs_flags = CS_SOF; + else + csp->cs_flags = CS_EOL; + else + csp->cs_ch = csp->cs_bp[--csp->cs_cno]; + break; + case CS_SOF: /* SOF. */ + break; + default: + abort(); + /* NOTREACHED */ + } + return (0); +} + +/* + * cs_bblank -- + * Eat backward to the next non-whitespace character. + * + * PUBLIC: int cs_bblank __P((SCR *, VCS *)); + */ +int +cs_bblank(sp, csp) + SCR *sp; + VCS *csp; +{ + for (;;) { + if (cs_prev(sp, csp)) + return (1); + if (csp->cs_flags == CS_EOL || csp->cs_flags == CS_EMP || + csp->cs_flags == 0 && isblank(csp->cs_ch)) + continue; + break; + } + return (0); +} diff --git a/contrib/nvi/vi/v_at.c b/contrib/nvi/vi/v_at.c new file mode 100644 index 0000000..d266f27 --- /dev/null +++ b/contrib/nvi/vi/v_at.c @@ -0,0 +1,109 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_at.c 10.8 (Berkeley) 4/27/96"; +#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 <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_at -- @ + * Execute a buffer. + * + * PUBLIC: int v_at __P((SCR *, VICMD *)); + */ +int +v_at(sp, vp) + SCR *sp; + VICMD *vp; +{ + CB *cbp; + CHAR_T name; + TEXT *tp; + size_t len; + char nbuf[20]; + + /* + * !!! + * Historically, [@*]<carriage-return> and [@*][@*] executed the most + * recently executed buffer in ex mode. In vi mode, only @@ repeated + * the last buffer. We change historic practice and make @* work from + * vi mode as well, it's simpler and more consistent. + * + * My intent is that *[buffer] will, in the future, pass the buffer to + * whatever interpreter is loaded. + */ + name = F_ISSET(vp, VC_BUFFER) ? vp->buffer : '@'; + if (name == '@' || name == '*') { + if (!F_ISSET(sp, SC_AT_SET)) { + ex_emsg(sp, NULL, EXM_NOPREVBUF); + return (1); + } + name = sp->at_lbuf; + } + F_SET(sp, SC_AT_SET); + + CBNAME(sp, cbp, name); + if (cbp == NULL) { + ex_emsg(sp, KEY_NAME(sp, name), EXM_EMPTYBUF); + return (1); + } + + /* Save for reuse. */ + sp->at_lbuf = name; + + /* + * The buffer is executed in vi mode, while in vi mode, so simply + * push it onto the terminal queue and continue. + * + * !!! + * Historic practice is that if the buffer was cut in line mode, + * <newlines> were appended to each line as it was pushed onto + * the stack. If the buffer was cut in character mode, <newlines> + * were appended to all lines but the last one. + * + * XXX + * Historic practice is that execution of an @ buffer could be + * undone by a single 'u' command, i.e. the changes were grouped + * together. We don't get this right; I'm waiting for the new DB + * logging code to be available. + */ + for (tp = cbp->textq.cqh_last; + tp != (void *)&cbp->textq; tp = tp->q.cqe_prev) + if ((F_ISSET(cbp, CB_LMODE) || + tp->q.cqe_next != (void *)&cbp->textq) && + v_event_push(sp, NULL, "\n", 1, 0) || + v_event_push(sp, NULL, tp->lb, tp->len, 0)) + return (1); + + /* + * !!! + * If any count was supplied, it applies to the first command in the + * at buffer. + */ + if (F_ISSET(vp, VC_C1SET)) { + len = snprintf(nbuf, sizeof(nbuf), "%lu", vp->count); + if (v_event_push(sp, NULL, nbuf, len, 0)) + return (1); + } + return (0); +} diff --git a/contrib/nvi/vi/v_ch.c b/contrib/nvi/vi/v_ch.c new file mode 100644 index 0000000..6a1b611 --- /dev/null +++ b/contrib/nvi/vi/v_ch.c @@ -0,0 +1,295 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_ch.c 10.8 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../common/common.h" +#include "vi.h" + +static void notfound __P((SCR *, ARG_CHAR_T)); +static void noprev __P((SCR *)); + +/* + * v_chrepeat -- [count]; + * Repeat the last F, f, T or t search. + * + * PUBLIC: int v_chrepeat __P((SCR *, VICMD *)); + */ +int +v_chrepeat(sp, vp) + SCR *sp; + VICMD *vp; +{ + vp->character = VIP(sp)->lastckey; + + switch (VIP(sp)->csearchdir) { + case CNOTSET: + noprev(sp); + return (1); + case FSEARCH: + return (v_chF(sp, vp)); + case fSEARCH: + return (v_chf(sp, vp)); + case TSEARCH: + return (v_chT(sp, vp)); + case tSEARCH: + return (v_cht(sp, vp)); + default: + abort(); + } + /* NOTREACHED */ +} + +/* + * v_chrrepeat -- [count], + * Repeat the last F, f, T or t search in the reverse direction. + * + * PUBLIC: int v_chrrepeat __P((SCR *, VICMD *)); + */ +int +v_chrrepeat(sp, vp) + SCR *sp; + VICMD *vp; +{ + cdir_t savedir; + int rval; + + vp->character = VIP(sp)->lastckey; + savedir = VIP(sp)->csearchdir; + + switch (VIP(sp)->csearchdir) { + case CNOTSET: + noprev(sp); + return (1); + case FSEARCH: + rval = v_chf(sp, vp); + break; + case fSEARCH: + rval = v_chF(sp, vp); + break; + case TSEARCH: + rval = v_cht(sp, vp); + break; + case tSEARCH: + rval = v_chT(sp, vp); + break; + default: + abort(); + } + VIP(sp)->csearchdir = savedir; + return (rval); +} + +/* + * v_cht -- [count]tc + * Search forward in the line for the character before the next + * occurrence of the specified character. + * + * PUBLIC: int v_cht __P((SCR *, VICMD *)); + */ +int +v_cht(sp, vp) + SCR *sp; + VICMD *vp; +{ + if (v_chf(sp, vp)) + return (1); + + /* + * v_chf places the cursor on the character, where the 't' + * command wants it to its left. We know this is safe since + * we had to move right for v_chf() to have succeeded. + */ + --vp->m_stop.cno; + + /* + * Make any necessary correction to the motion decision made + * by the v_chf routine. + */ + if (!ISMOTION(vp)) + vp->m_final = vp->m_stop; + + VIP(sp)->csearchdir = tSEARCH; + return (0); +} + +/* + * v_chf -- [count]fc + * Search forward in the line for the next occurrence of the + * specified character. + * + * PUBLIC: int v_chf __P((SCR *, VICMD *)); + */ +int +v_chf(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + u_long cnt; + int isempty, key; + char *endp, *p, *startp; + + /* + * !!! + * If it's a dot command, it doesn't reset the key for which we're + * searching, e.g. in "df1|f2|.|;", the ';' searches for a '2'. + */ + key = vp->character; + if (!F_ISSET(vp, VC_ISDOT)) + VIP(sp)->lastckey = key; + VIP(sp)->csearchdir = fSEARCH; + + if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { + if (isempty) + goto empty; + return (1); + } + + if (len == 0) { +empty: notfound(sp, key); + return (1); + } + + endp = (startp = p) + len; + p += vp->m_start.cno; + for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { + while (++p < endp && *p != key); + if (p == endp) { + notfound(sp, key); + return (1); + } + } + + vp->m_stop.cno = p - startp; + + /* + * Non-motion commands move to the end of the range. + * Delete and yank stay at the start, ignore others. + */ + vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; + return (0); +} + +/* + * v_chT -- [count]Tc + * Search backward in the line for the character after the next + * occurrence of the specified character. + * + * PUBLIC: int v_chT __P((SCR *, VICMD *)); + */ +int +v_chT(sp, vp) + SCR *sp; + VICMD *vp; +{ + if (v_chF(sp, vp)) + return (1); + + /* + * v_chF places the cursor on the character, where the 'T' + * command wants it to its right. We know this is safe since + * we had to move left for v_chF() to have succeeded. + */ + ++vp->m_stop.cno; + vp->m_final = vp->m_stop; + + VIP(sp)->csearchdir = TSEARCH; + return (0); +} + +/* + * v_chF -- [count]Fc + * Search backward in the line for the next occurrence of the + * specified character. + * + * PUBLIC: int v_chF __P((SCR *, VICMD *)); + */ +int +v_chF(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + u_long cnt; + int isempty, key; + char *endp, *p; + + /* + * !!! + * If it's a dot command, it doesn't reset the key for which + * we're searching, e.g. in "df1|f2|.|;", the ';' searches + * for a '2'. + */ + key = vp->character; + if (!F_ISSET(vp, VC_ISDOT)) + VIP(sp)->lastckey = key; + VIP(sp)->csearchdir = FSEARCH; + + if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { + if (isempty) + goto empty; + return (1); + } + + if (len == 0) { +empty: notfound(sp, key); + return (1); + } + + endp = p - 1; + p += vp->m_start.cno; + for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { + while (--p > endp && *p != key); + if (p == endp) { + notfound(sp, key); + return (1); + } + } + + vp->m_stop.cno = (p - endp) - 1; + + /* + * All commands move to the end of the range. Motion commands + * adjust the starting point to the character before the current + * one. + */ + vp->m_final = vp->m_stop; + if (ISMOTION(vp)) + --vp->m_start.cno; + return (0); +} + +static void +noprev(sp) + SCR *sp; +{ + msgq(sp, M_BERR, "178|No previous F, f, T or t search"); +} + +static void +notfound(sp, ch) + SCR *sp; + ARG_CHAR_T ch; +{ + msgq(sp, M_BERR, "179|%s not found", KEY_NAME(sp, ch)); +} diff --git a/contrib/nvi/vi/v_cmd.c b/contrib/nvi/vi/v_cmd.c new file mode 100644 index 0000000..cc9e30f --- /dev/null +++ b/contrib/nvi/vi/v_cmd.c @@ -0,0 +1,505 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_cmd.c 10.9 (Berkeley) 3/28/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * This array maps keystrokes to vi command functions. It is known + * in ex/ex_usage.c that it takes four columns to name a vi character. + */ +VIKEYS const vikeys [MAXVIKEY + 1] = { +/* 000 NUL -- The code in vi.c expects key 0 to be undefined. */ + {NULL}, +/* 001 ^A */ + {v_searchw, V_ABS|V_CNT|V_MOVE|V_KEYW|VM_CUTREQ|VM_RCM_SET, + "[count]^A", + "^A search forward for cursor word"}, +/* 002 ^B */ + {v_pageup, V_CNT|VM_RCM_SET, + "[count]^B", + "^B scroll up by screens"}, +/* 003 ^C */ + {NULL, 0, + "^C", + "^C interrupt an operation (e.g. read, write, search)"}, +/* 004 ^D */ + {v_hpagedown, V_CNT|VM_RCM_SET, + "[count]^D", + "^D scroll down by half screens (setting count)"}, +/* 005 ^E */ + {v_linedown, V_CNT, + "[count]^E", + "^E scroll down by lines"}, +/* 006 ^F */ + {v_pagedown, V_CNT|VM_RCM_SET, + "[count]^F", + "^F scroll down by screens"}, +/* 007 ^G */ + {v_status, 0, + "^G", + "^G file status"}, +/* 010 ^H */ + {v_left, V_CNT|V_MOVE|VM_RCM_SET, + "[count]^H", + "^H move left by characters"}, +/* 011 ^I */ + {NULL}, +/* 012 ^J */ + {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM, + "[count]^J", + "^J move down by lines"}, +/* 013 ^K */ + {NULL}, +/* 014 ^L */ + {v_redraw, 0, + "^L", + "^L redraw screen"}, +/* 015 ^M */ + {v_cr, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, + "[count]^M", + "^M move down by lines (to first non-blank)"}, +/* 016 ^N */ + {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM, + "[count]^N", + "^N move down by lines"}, +/* 017 ^O */ + {NULL}, +/* 020 ^P */ + {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM, + "[count]^P", + "^P move up by lines"}, +/* 021 ^Q -- same as ^V if not used for hardware flow control. */ + {NULL}, +/* 022 ^R */ + {v_redraw, 0, + "^R", + "^R redraw screen"}, +/* 023 ^S -- not available, used for hardware flow control. */ + {NULL}, +/* 024 ^T */ + {v_tagpop, V_ABS|VM_RCM_SET, + "^T", + "^T tag pop"}, +/* 025 ^U */ + {v_hpageup, V_CNT|VM_RCM_SET, + "[count]^U", + "^U half page up (set count)"}, +/* 026 ^V */ + {NULL, 0, + "^V", + "^V input a literal character"}, +/* 027 ^W */ + {v_screen, 0, + "^W", + "^W move to next screen"}, +/* 030 ^X */ + {NULL}, +/* 031 ^Y */ + {v_lineup, V_CNT, + "[count]^Y", + "^Y page up by lines"}, +/* 032 ^Z */ + {v_suspend, V_SECURE, + "^Z", + "^Z suspend editor"}, +/* 033 ^[ */ + {NULL, 0, + "^[ <escape>", + "^[ <escape> exit input mode, cancel partial commands"}, +/* 034 ^\ */ + {v_exmode, 0, + "^\\", + " ^\\ switch to ex mode"}, +/* 035 ^] */ + {v_tagpush, V_ABS|V_KEYW|VM_RCM_SET, + "^]", + "^] tag push cursor word"}, +/* 036 ^^ */ + {v_switch, 0, + "^^", + "^^ switch to previous file"}, +/* 037 ^_ */ + {NULL}, +/* 040 ' ' */ + {v_right, V_CNT|V_MOVE|VM_RCM_SET, + "[count]' '", + " <space> move right by columns"}, +/* 041 ! */ + {v_filter, V_CNT|V_DOT|V_MOTION|V_SECURE|VM_RCM_SET, + "[count]![count]motion command(s)", + " ! filter through command(s) to motion"}, +/* 042 " */ + {NULL}, +/* 043 # */ + {v_increment, V_CHAR|V_CNT|V_DOT|VM_RCM_SET, + "[count]# +|-|#", + " # number increment/decrement"}, +/* 044 $ */ + {v_dollar, V_CNT|V_MOVE|VM_RCM_SETLAST, + " [count]$", + " $ move to last column"}, +/* 045 % */ + {v_match, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, + "%", + " % move to match"}, +/* 046 & */ + {v_again, 0, + "&", + " & repeat substitution"}, +/* 047 ' */ + {v_fmark, V_ABS_L|V_CHAR|V_MOVE|VM_LMODE|VM_RCM_SET, + "'['a-z]", + " ' move to mark (to first non-blank)"}, +/* 050 ( */ + {v_sentenceb, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, + "[count](", + " ( move back sentence"}, +/* 051 ) */ + {v_sentencef, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, + "[count])", + " ) move forward sentence"}, +/* 052 * */ + {NULL}, +/* 053 + */ + {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, + "[count]+", + " + move down by lines (to first non-blank)"}, +/* 054 , */ + {v_chrrepeat, V_CNT|V_MOVE|VM_RCM_SET, + "[count],", + " , reverse last F, f, T or t search"}, +/* 055 - */ + {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, + "[count]-", + " - move up by lines (to first non-blank)"}, +/* 056 . */ + {NULL, 0, + ".", + " . repeat the last command"}, +/* 057 / */ + {v_searchf, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, + "/RE[/ offset]", + " / search forward"}, +/* 060 0 */ + {v_zero, V_MOVE|VM_RCM_SET, + "0", + " 0 move to first character"}, +/* 061 1 */ + {NULL}, +/* 062 2 */ + {NULL}, +/* 063 3 */ + {NULL}, +/* 064 4 */ + {NULL}, +/* 065 5 */ + {NULL}, +/* 066 6 */ + {NULL}, +/* 067 7 */ + {NULL}, +/* 070 8 */ + {NULL}, +/* 071 9 */ + {NULL}, +/* 072 : */ + {v_ex, 0, + ":command [| command] ...", + " : ex command"}, +/* 073 ; */ + {v_chrepeat, V_CNT|V_MOVE|VM_RCM_SET, + "[count];", + " ; repeat last F, f, T or t search"}, +/* 074 < */ + {v_shiftl, V_CNT|V_DOT|V_MOTION|VM_RCM_SET, + "[count]<[count]motion", + " < shift lines left to motion"}, +/* 075 = */ + {NULL}, +/* 076 > */ + {v_shiftr, V_CNT|V_DOT|V_MOTION|VM_RCM_SET, + "[count]>[count]motion", + " > shift lines right to motion"}, +/* 077 ? */ + {v_searchb, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, + "?RE[? offset]", + " ? search backward"}, +/* 100 @ */ + {v_at, V_CNT|V_RBUF|VM_RCM_SET, + "@buffer", + " @ execute buffer"}, +/* 101 A */ + {v_iA, V_CNT|V_DOT|VM_RCM_SET, + "[count]A", + " A append to the line"}, +/* 102 B */ + {v_wordB, V_CNT|V_MOVE|VM_RCM_SET, + "[count]B", + " B move back bigword"}, +/* 103 C */ + {NULL, 0, + "[buffer][count]C", + " C change to end-of-line"}, +/* 104 D */ + {NULL, 0, + "[buffer]D", + " D delete to end-of-line"}, +/* 105 E */ + {v_wordE, V_CNT|V_MOVE|VM_RCM_SET, + "[count]E", + " E move to end of bigword"}, +/* 106 F */ + {v_chF, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, + "[count]F character", + " F character in line backward search"}, +/* 107 G */ + {v_lgoto, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, + "[count]G", + " G move to line"}, +/* 110 H */ + {v_home, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB, + "[count]H", + " H move to count lines from screen top"}, +/* 111 I */ + {v_iI, V_CNT|V_DOT|VM_RCM_SET, + "[count]I", + " I insert before first nonblank"}, +/* 112 J */ + {v_join, V_CNT|V_DOT|VM_RCM_SET, + "[count]J", + " J join lines"}, +/* 113 K */ + {NULL}, +/* 114 L */ + {v_bottom, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB, + "[count]L", + " L move to screen bottom"}, +/* 115 M */ + {v_middle, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB, + "M", + " M move to screen middle"}, +/* 116 N */ + {v_searchN, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, + "n", + " N reverse last search"}, +/* 117 O */ + {v_iO, V_CNT|V_DOT|VM_RCM_SET, + "[count]O", + " O insert above line"}, +/* 120 P */ + {v_Put, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, + "[buffer]P", + " P insert before cursor from buffer"}, +/* 121 Q */ + {v_exmode, 0, + "Q", + " Q switch to ex mode"}, +/* 122 R */ + {v_Replace, V_CNT|V_DOT|VM_RCM_SET, + "[count]R", + " R replace characters"}, +/* 123 S */ + {NULL, 0, + "[buffer][count]S", + " S substitute for the line(s)"}, +/* 124 T */ + {v_chT, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, + "[count]T character", + " T before character in line backward search"}, +/* 125 U */ + {v_Undo, VM_RCM_SET, + "U", + " U Restore the current line"}, +/* 126 V */ + {NULL}, +/* 127 W */ + {v_wordW, V_CNT|V_MOVE|VM_RCM_SET, + "[count]W", + " W move to next bigword"}, +/* 130 X */ + {v_Xchar, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, + "[buffer][count]X", + " X delete character before cursor"}, +/* 131 Y */ + {NULL, 0, + "[buffer][count]Y", + " Y copy line"}, +/* 132 Z */ + {v_zexit, 0, + "ZZ", + "ZZ save file and exit"}, +/* 133 [ */ + {v_sectionb, V_ABS|V_CNT|V_MOVE|VM_RCM_SET, + "[[", + "[[ move back section"}, +/* 134 \ */ + {NULL}, +/* 135 ] */ + {v_sectionf, V_ABS|V_CNT|V_MOVE|VM_RCM_SET, + "]]", + "]] move forward section"}, +/* 136 ^ */ + /* + * DON'T set the VM_RCM_SETFNB flag, the function has to do the work + * anyway, in case it's a motion component. DO set VM_RCM_SET, so + * that any motion that's part of a command is preserved. + */ + {v_first, V_CNT|V_MOVE|VM_RCM_SET, + "^", + " ^ move to first non-blank"}, +/* 137 _ */ + /* + * Needs both to set the VM_RCM_SETFNB flag, and to do the work + * in the function, in case it's a delete. + */ + {v_cfirst, V_CNT|V_MOVE|VM_RCM_SETFNB, + "_", + " _ move to first non-blank"}, +/* 140 ` */ + {v_bmark, V_ABS_C|V_CHAR|V_MOVE|VM_CUTREQ|VM_RCM_SET, + "`[`a-z]", + " ` move to mark"}, +/* 141 a */ + {v_ia, V_CNT|V_DOT|VM_RCM_SET, + "[count]a", + " a append after cursor"}, +/* 142 b */ + {v_wordb, V_CNT|V_MOVE|VM_RCM_SET, + "[count]b", + " b move back word"}, +/* 143 c */ + {v_change, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET, + "[buffer][count]c[count]motion", + " c change to motion"}, +/* 144 d */ + {v_delete, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET, + "[buffer][count]d[count]motion", + " d delete to motion"}, +/* 145 e */ + {v_worde, V_CNT|V_MOVE|VM_RCM_SET, + "[count]e", + " e move to end of word"}, +/* 146 f */ + {v_chf, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, + "[count]f character", + " f character in line forward search"}, +/* 147 g */ + {NULL}, +/* 150 h */ + {v_left, V_CNT|V_MOVE|VM_RCM_SET, + "[count]h", + " h move left by columns"}, +/* 151 i */ + {v_ii, V_CNT|V_DOT|VM_RCM_SET, + "[count]i", + " i insert before cursor"}, +/* 152 j */ + {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM, + "[count]j", + " j move down by lines"}, +/* 153 k */ + {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM, + "[count]k", + " k move up by lines"}, +/* 154 l */ + {v_right, V_CNT|V_MOVE|VM_RCM_SET, + "[count]l", + " l move right by columns"}, +/* 155 m */ + {v_mark, V_CHAR, + "m[a-z]", + " m set mark"}, +/* 156 n */ + {v_searchn, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, + "n", + " n repeat last search"}, +/* 157 o */ + {v_io, V_CNT|V_DOT|VM_RCM_SET, + "[count]o", + " o append after line"}, +/* 160 p */ + {v_put, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, + "[buffer]p", + " p insert after cursor from buffer"}, +/* 161 q */ + {NULL}, +/* 162 r */ + {v_replace, V_CNT|V_DOT|VM_RCM_SET, + "[count]r character", + " r replace character"}, +/* 163 s */ + {v_subst, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, + "[buffer][count]s", + " s substitute character"}, +/* 164 t */ + {v_cht, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, + "[count]t character", + " t before character in line forward search"}, +/* 165 u */ + /* + * DON'T set the V_DOT flag, it' more complicated than that. + * See vi/vi.c for details. + */ + {v_undo, VM_RCM_SET, + "u", + " u undo last change"}, +/* 166 v */ + {NULL}, +/* 167 w */ + {v_wordw, V_CNT|V_MOVE|VM_RCM_SET, + "[count]w", + " w move to next word"}, +/* 170 x */ + {v_xchar, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, + "[buffer][count]x", + " x delete character"}, +/* 171 y */ + {v_yank, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET, + "[buffer][count]y[count]motion", + " y copy text to motion into a cut buffer"}, +/* 172 z */ + /* + * DON'T set the V_CHAR flag, the char isn't required, + * so it's handled specially in getcmd(). + */ + {v_z, V_ABS_L|V_CNT|VM_RCM_SETFNB, + "[line]z[window_size][-|.|+|^|<CR>]", + " z reposition the screen"}, +/* 173 { */ + {v_paragraphb, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, + "[count]{", + " { move back paragraph"}, +/* 174 | */ + {v_ncol, V_CNT|V_MOVE|VM_RCM_SET, + "[count]|", + " | move to column"}, +/* 175 } */ + {v_paragraphf, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, + "[count]}", + " } move forward paragraph"}, +/* 176 ~ */ + {v_ulcase, V_CNT|V_DOT|VM_RCM_SET, + "[count]~", + " ~ reverse case"}, +}; diff --git a/contrib/nvi/vi/v_delete.c b/contrib/nvi/vi/v_delete.c new file mode 100644 index 0000000..70df78b --- /dev/null +++ b/contrib/nvi/vi/v_delete.c @@ -0,0 +1,107 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_delete.c 10.9 (Berkeley) 10/23/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_delete -- [buffer][count]d[count]motion + * [buffer][count]D + * Delete a range of text. + * + * PUBLIC: int v_delete __P((SCR *, VICMD *)); + */ +int +v_delete(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t nlines; + size_t len; + int lmode; + + lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0; + + /* Yank the lines. */ + if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, + &vp->m_start, &vp->m_stop, + lmode | (F_ISSET(vp, VM_CUTREQ) ? CUT_NUMREQ : CUT_NUMOPT))) + return (1); + + /* Delete the lines. */ + if (del(sp, &vp->m_start, &vp->m_stop, lmode)) + return (1); + + /* + * Check for deletion of the entire file. Try to check a close + * by line so we don't go to the end of the file unnecessarily. + */ + if (!db_exist(sp, vp->m_final.lno + 1)) { + if (db_last(sp, &nlines)) + return (1); + if (nlines == 0) { + vp->m_final.lno = 1; + vp->m_final.cno = 0; + return (0); + } + } + + /* + * One special correction, in case we've deleted the current line or + * character. We check it here instead of checking in every command + * that can be a motion component. + */ + if (db_get(sp, vp->m_final.lno, 0, NULL, &len)) { + if (db_get(sp, nlines, DBG_FATAL, NULL, &len)) + return (1); + vp->m_final.lno = nlines; + } + + /* + * !!! + * Cursor movements, other than those caused by a line mode command + * moving to another line, historically reset the relative position. + * + * This currently matches the check made in v_yank(), I'm hoping that + * they should be consistent... + */ + if (!F_ISSET(vp, VM_LMODE)) { + F_CLR(vp, VM_RCM_MASK); + F_SET(vp, VM_RCM_SET); + + /* Make sure the set cursor position exists. */ + if (vp->m_final.cno >= len) + vp->m_final.cno = len ? len - 1 : 0; + } + + /* + * !!! + * The "dd" command moved to the first non-blank; "d<motion>" + * didn't. + */ + if (F_ISSET(vp, VM_LDOUBLE)) { + F_CLR(vp, VM_RCM_MASK); + F_SET(vp, VM_RCM_SETFNB); + } + return (0); +} diff --git a/contrib/nvi/vi/v_ex.c b/contrib/nvi/vi/v_ex.c new file mode 100644 index 0000000..359080c --- /dev/null +++ b/contrib/nvi/vi/v_ex.c @@ -0,0 +1,696 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_ex.c 10.42 (Berkeley) 6/28/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "vi.h" + +static int v_ecl __P((SCR *)); +static int v_ecl_init __P((SCR *)); +static int v_ecl_log __P((SCR *, TEXT *)); +static int v_ex_done __P((SCR *, VICMD *)); +static int v_exec_ex __P((SCR *, VICMD *, EXCMD *)); + +/* + * v_again -- & + * Repeat the previous substitution. + * + * PUBLIC: int v_again __P((SCR *, VICMD *)); + */ +int +v_again(sp, vp) + SCR *sp; + VICMD *vp; +{ + ARGS *ap[2], a; + EXCMD cmd; + + ex_cinit(&cmd, C_SUBAGAIN, 2, vp->m_start.lno, vp->m_start.lno, 1, ap); + ex_cadd(&cmd, &a, "", 1); + + return (v_exec_ex(sp, vp, &cmd)); +} + +/* + * v_exmode -- Q + * Switch the editor into EX mode. + * + * PUBLIC: int v_exmode __P((SCR *, VICMD *)); + */ +int +v_exmode(sp, vp) + SCR *sp; + VICMD *vp; +{ + GS *gp; + + gp = sp->gp; + + /* Try and switch screens -- the screen may not permit it. */ + if (gp->scr_screen(sp, SC_EX)) { + msgq(sp, M_ERR, + "207|The Q command requires the ex terminal interface"); + return (1); + } + (void)gp->scr_attr(sp, SA_ALTERNATE, 0); + + /* Save the current cursor position. */ + sp->frp->lno = sp->lno; + sp->frp->cno = sp->cno; + F_SET(sp->frp, FR_CURSORSET); + + /* Switch to ex mode. */ + F_CLR(sp, SC_VI | SC_SCR_VI); + F_SET(sp, SC_EX); + + /* Move out of the vi screen. */ + (void)ex_puts(sp, "\n"); + + return (0); +} + +/* + * v_join -- [count]J + * Join lines together. + * + * PUBLIC: int v_join __P((SCR *, VICMD *)); + */ +int +v_join(sp, vp) + SCR *sp; + VICMD *vp; +{ + EXCMD cmd; + int lno; + + /* + * YASC. + * The general rule is that '#J' joins # lines, counting the current + * line. However, 'J' and '1J' are the same as '2J', i.e. join the + * current and next lines. This doesn't map well into the ex command + * (which takes two line numbers), so we handle it here. Note that + * we never test for EOF -- historically going past the end of file + * worked just fine. + */ + lno = vp->m_start.lno + 1; + if (F_ISSET(vp, VC_C1SET) && vp->count > 2) + lno = vp->m_start.lno + (vp->count - 1); + + ex_cinit(&cmd, C_JOIN, 2, vp->m_start.lno, lno, 0, NULL); + return (v_exec_ex(sp, vp, &cmd)); +} + +/* + * v_shiftl -- [count]<motion + * Shift lines left. + * + * PUBLIC: int v_shiftl __P((SCR *, VICMD *)); + */ +int +v_shiftl(sp, vp) + SCR *sp; + VICMD *vp; +{ + ARGS *ap[2], a; + EXCMD cmd; + + ex_cinit(&cmd, C_SHIFTL, 2, vp->m_start.lno, vp->m_stop.lno, 0, ap); + ex_cadd(&cmd, &a, "<", 1); + return (v_exec_ex(sp, vp, &cmd)); +} + +/* + * v_shiftr -- [count]>motion + * Shift lines right. + * + * PUBLIC: int v_shiftr __P((SCR *, VICMD *)); + */ +int +v_shiftr(sp, vp) + SCR *sp; + VICMD *vp; +{ + ARGS *ap[2], a; + EXCMD cmd; + + ex_cinit(&cmd, C_SHIFTR, 2, vp->m_start.lno, vp->m_stop.lno, 0, ap); + ex_cadd(&cmd, &a, ">", 1); + return (v_exec_ex(sp, vp, &cmd)); +} + +/* + * v_suspend -- ^Z + * Suspend vi. + * + * PUBLIC: int v_suspend __P((SCR *, VICMD *)); + */ +int +v_suspend(sp, vp) + SCR *sp; + VICMD *vp; +{ + ARGS *ap[2], a; + EXCMD cmd; + + ex_cinit(&cmd, C_STOP, 0, OOBLNO, OOBLNO, 0, ap); + ex_cadd(&cmd, &a, "suspend", sizeof("suspend") - 1); + return (v_exec_ex(sp, vp, &cmd)); +} + +/* + * v_switch -- ^^ + * Switch to the previous file. + * + * PUBLIC: int v_switch __P((SCR *, VICMD *)); + */ +int +v_switch(sp, vp) + SCR *sp; + VICMD *vp; +{ + ARGS *ap[2], a; + EXCMD cmd; + char *name; + + /* + * Try the alternate file name, then the previous file + * name. Use the real name, not the user's current name. + */ + if ((name = sp->alt_name) == NULL) { + msgq(sp, M_ERR, "180|No previous file to edit"); + return (1); + } + + /* If autowrite is set, write out the file. */ + if (file_m1(sp, 0, FS_ALL)) + return (1); + + ex_cinit(&cmd, C_EDIT, 0, OOBLNO, OOBLNO, 0, ap); + ex_cadd(&cmd, &a, name, strlen(name)); + return (v_exec_ex(sp, vp, &cmd)); +} + +/* + * v_tagpush -- ^[ + * Do a tag search on the cursor keyword. + * + * PUBLIC: int v_tagpush __P((SCR *, VICMD *)); + */ +int +v_tagpush(sp, vp) + SCR *sp; + VICMD *vp; +{ + ARGS *ap[2], a; + EXCMD cmd; + + ex_cinit(&cmd, C_TAG, 0, OOBLNO, 0, 0, ap); + ex_cadd(&cmd, &a, VIP(sp)->keyw, strlen(VIP(sp)->keyw)); + return (v_exec_ex(sp, vp, &cmd)); +} + +/* + * v_tagpop -- ^T + * Pop the tags stack. + * + * PUBLIC: int v_tagpop __P((SCR *, VICMD *)); + */ +int +v_tagpop(sp, vp) + SCR *sp; + VICMD *vp; +{ + EXCMD cmd; + + ex_cinit(&cmd, C_TAGPOP, 0, OOBLNO, 0, 0, NULL); + return (v_exec_ex(sp, vp, &cmd)); +} + +/* + * v_filter -- [count]!motion command(s) + * Run range through shell commands, replacing text. + * + * PUBLIC: int v_filter __P((SCR *, VICMD *)); + */ +int +v_filter(sp, vp) + SCR *sp; + VICMD *vp; +{ + EXCMD cmd; + TEXT *tp; + + /* + * !!! + * Historical vi permitted "!!" in an empty file, and it's handled + * as a special case in the ex_bang routine. Don't modify this setup + * without understanding that one. In particular, note that we're + * manipulating the ex argument structures behind ex's back. + * + * !!! + * Historical vi did not permit the '!' command to be associated with + * a non-line oriented motion command, in general, although it did + * with search commands. So, !f; and !w would fail, but !/;<CR> + * would succeed, even if they all moved to the same location in the + * current line. I don't see any reason to disallow '!' using any of + * the possible motion commands. + * + * !!! + * Historical vi ran the last bang command if N or n was used as the + * search motion. + */ + if (F_ISSET(vp, VC_ISDOT) || + ISCMD(vp->rkp, 'N') || ISCMD(vp->rkp, 'n')) { + ex_cinit(&cmd, C_BANG, + 2, vp->m_start.lno, vp->m_stop.lno, 0, NULL); + EXP(sp)->argsoff = 0; /* XXX */ + + if (argv_exp1(sp, &cmd, "!", 1, 1)) + return (1); + cmd.argc = EXP(sp)->argsoff; /* XXX */ + cmd.argv = EXP(sp)->args; /* XXX */ + return (v_exec_ex(sp, vp, &cmd)); + } + + /* Get the command from the user. */ + if (v_tcmd(sp, vp, + '!', TXT_BS | TXT_CR | TXT_ESCAPE | TXT_FILEC | TXT_PROMPT)) + return (1); + + /* + * Check to see if the user changed their mind. + * + * !!! + * Entering <escape> on an empty line was historically an error, + * this implementation doesn't bother. + */ + tp = sp->tiq.cqh_first; + if (tp->term != TERM_OK) { + vp->m_final.lno = sp->lno; + vp->m_final.cno = sp->cno; + return (0); + } + + /* Home the cursor. */ + vs_home(sp); + + ex_cinit(&cmd, C_BANG, 2, vp->m_start.lno, vp->m_stop.lno, 0, NULL); + EXP(sp)->argsoff = 0; /* XXX */ + + if (argv_exp1(sp, &cmd, tp->lb + 1, tp->len - 1, 1)) + return (1); + cmd.argc = EXP(sp)->argsoff; /* XXX */ + cmd.argv = EXP(sp)->args; /* XXX */ + return (v_exec_ex(sp, vp, &cmd)); +} + +/* + * v_event_exec -- + * Execute some command(s) based on an event. + * + * PUBLIC: int v_event_exec __P((SCR *, VICMD *)); + */ +int +v_event_exec(sp, vp) + SCR *sp; + VICMD *vp; +{ + EXCMD cmd; + + switch (vp->ev.e_event) { + case E_QUIT: + ex_cinit(&cmd, C_QUIT, 0, OOBLNO, OOBLNO, 0, NULL); + break; + case E_WRITE: + ex_cinit(&cmd, C_WRITE, 0, OOBLNO, OOBLNO, 0, NULL); + break; + default: + abort(); + } + return (v_exec_ex(sp, vp, &cmd)); +} + +/* + * v_exec_ex -- + * Execute an ex command. + */ +static int +v_exec_ex(sp, vp, exp) + SCR *sp; + VICMD *vp; + EXCMD *exp; +{ + int rval; + + rval = exp->cmd->fn(sp, exp); + return (v_ex_done(sp, vp) || rval); +} + +/* + * v_ex -- : + * Execute a colon command line. + * + * PUBLIC: int v_ex __P((SCR *, VICMD *)); + */ +int +v_ex(sp, vp) + SCR *sp; + VICMD *vp; +{ + GS *gp; + TEXT *tp; + int do_cedit, do_resolution, ifcontinue; + + gp = sp->gp; + + /* + * !!! + * If we put out more than a single line of messages, or ex trashes + * the screen, the user may continue entering ex commands. We find + * this out when we do the screen/message resolution. We can't enter + * completely into ex mode however, because the user can elect to + * return into vi mode by entering any key, i.e. we have to be in raw + * mode. + */ + for (do_cedit = do_resolution = 0;;) { + /* + * !!! + * There may already be an ex command waiting to run. If + * so, we continue with it. + */ + if (!EXCMD_RUNNING(gp)) { + /* Get a command. */ + if (v_tcmd(sp, vp, ':', + TXT_BS | TXT_CEDIT | TXT_FILEC | TXT_PROMPT)) + return (1); + tp = sp->tiq.cqh_first; + + /* + * If the user entered a single <esc>, they want to + * edit their colon command history. If they already + * entered some text, move it into the edit history. + */ + if (tp->term == TERM_CEDIT) { + if (tp->len > 1 && v_ecl_log(sp, tp)) + return (1); + do_cedit = 1; + break; + } + + /* If the user didn't enter anything, return. */ + if (tp->term == TERM_BS) + break; + + /* Log the command. */ + if (O_STR(sp, O_CEDIT) != NULL && v_ecl_log(sp, tp)) + return (1); + + /* Push a command on the command stack. */ + if (ex_run_str(sp, NULL, tp->lb, tp->len, 0, 1)) + return (1); + } + + /* Home the cursor. */ + vs_home(sp); + + /* + * !!! + * If the editor wrote the screen behind curses back, put out + * a <newline> so that we don't overwrite the user's command + * with its output or the next want-to-continue? message. This + * doesn't belong here, but I can't find another place to put + * it. See, we resolved the output from the last ex command, + * and the user entered another one. This is the only place + * where we have control before the ex command writes output. + * We could get control in vs_msg(), but we have no way to know + * if command didn't put out any output when we try and resolve + * this command. This fixes a bug where combinations of ex + * commands, e.g. ":set<CR>:!date<CR>:set" didn't look right. + */ + if (F_ISSET(sp, SC_SCR_EXWROTE)) + (void)putchar('\n'); + + /* Call the ex parser. */ + (void)ex_cmd(sp); + + /* Flush ex messages. */ + (void)ex_fflush(sp); + + /* Resolve any messages. */ + if (vs_ex_resolve(sp, &ifcontinue)) + return (1); + + /* + * Continue or return. If continuing, make sure that we + * eventually do resolution. + */ + if (!ifcontinue) + break; + do_resolution = 1; + + /* If we're continuing, it's a new command. */ + ++sp->ccnt; + } + + /* + * If the user previously continued an ex command, we have to do + * resolution to clean up the screen. Don't wait, we already did + * that. + */ + if (do_resolution) { + F_SET(sp, SC_EX_WAIT_NO); + if (vs_ex_resolve(sp, &ifcontinue)) + return (1); + } + + /* Cleanup from the ex command. */ + if (v_ex_done(sp, vp)) + return (1); + + /* The user may want to edit their colon command history. */ + if (do_cedit) + return (v_ecl(sp)); + + return (0); +} + +/* + * v_ex_done -- + * Cleanup from an ex command. + */ +static int +v_ex_done(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + + /* + * The only cursor modifications are real, however, the underlying + * line may have changed; don't trust anything. This code has been + * a remarkably fertile place for bugs. 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 cares less about the + * column and it may have disappeared. + * + * Don't trust ANYTHING. + * + * XXX + * Ex will soon have to start handling the column correctly; see + * the POSIX 1003.2 standard. + */ + if (db_eget(sp, sp->lno, NULL, &len, NULL)) { + sp->lno = 1; + sp->cno = 0; + } else if (sp->cno >= len) + sp->cno = len ? len - 1 : 0; + + vp->m_final.lno = sp->lno; + vp->m_final.cno = sp->cno; + + /* + * Don't re-adjust the cursor after executing an ex command, + * and ex movements are permanent. + */ + F_CLR(vp, VM_RCM_MASK); + F_SET(vp, VM_RCM_SET); + + return (0); +} + +/* + * v_ecl -- + * Start an edit window on the colon command-line commands. + */ +static int +v_ecl(sp) + SCR *sp; +{ + GS *gp; + SCR *new; + + /* Initialize the screen, if necessary. */ + gp = sp->gp; + if (gp->ccl_sp == NULL && v_ecl_init(sp)) + return (1); + + /* Get a new screen. */ + if (screen_init(gp, sp, &new)) + return (1); + if (vs_split(sp, new, 1)) { + (void)screen_end(new); + return (1); + } + + /* Attach to the screen. */ + new->ep = gp->ccl_sp->ep; + ++new->ep->refcnt; + + new->frp = gp->ccl_sp->frp; + new->frp->flags = sp->frp->flags; + + /* Move the cursor to the end. */ + (void)db_last(new, &new->lno); + if (new->lno == 0) + new->lno = 1; + + /* Remember the originating window. */ + sp->ccl_parent = sp; + + /* It's a special window. */ + F_SET(new, SC_COMEDIT); + + /* Set up the switch. */ + sp->nextdisp = new; + F_SET(sp, SC_SSWITCH); + return (0); +} + +/* + * v_ecl_exec -- + * Execute a command from a colon command-line window. + * + * PUBLIC: int v_ecl_exec __P((SCR *)); + */ +int +v_ecl_exec(sp) + SCR *sp; +{ + size_t len; + char *p; + + if (db_get(sp, sp->lno, 0, &p, &len) && sp->lno == 1) { + v_emsg(sp, NULL, VIM_EMPTY); + return (1); + } + if (len == 0) { + msgq(sp, M_BERR, "307|No ex command to execute"); + return (1); + } + + /* Push the command on the command stack. */ + if (ex_run_str(sp, NULL, p, len, 0, 0)) + return (1); + + /* Set up the switch. */ + sp->nextdisp = sp->ccl_parent; + F_SET(sp, SC_EXIT); + return (0); +} + +/* + * v_ecl_log -- + * Log a command into the colon command-line log file. + */ +static int +v_ecl_log(sp, tp) + SCR *sp; + TEXT *tp; +{ + EXF *save_ep; + recno_t lno; + int rval; + + /* Initialize the screen, if necessary. */ + if (sp->gp->ccl_sp == NULL && v_ecl_init(sp)) + return (1); + + /* + * Don't log colon command window commands into the colon command + * window... + */ + if (sp->ep == sp->gp->ccl_sp->ep) + return (0); + + /* + * XXX + * Swap the current EXF with the colon command file EXF. This + * isn't pretty, but too many routines "know" that sp->ep points + * to the current EXF. + */ + save_ep = sp->ep; + sp->ep = sp->gp->ccl_sp->ep; + if (db_last(sp, &lno)) { + sp->ep = save_ep; + return (1); + } + rval = db_append(sp, 0, lno, tp->lb, tp->len); + sp->ep = save_ep; + return (rval); +} + +/* + * v_ecl_init -- + * Initialize the colon command-line log file. + */ +static int +v_ecl_init(sp) + SCR *sp; +{ + FREF *frp; + GS *gp; + + gp = sp->gp; + + /* Get a temporary file. */ + if ((frp = file_add(sp, NULL)) == NULL) + return (1); + + /* + * XXX + * Create a screen -- the file initialization code wants one. + */ + if (screen_init(gp, sp, &gp->ccl_sp)) + return (1); + if (file_init(gp->ccl_sp, frp, NULL, 0)) { + (void)screen_end(gp->ccl_sp); + return (1); + } + + /* The underlying file isn't recoverable. */ + F_CLR(gp->ccl_sp->ep, F_RCV_ON); + + return (0); +} diff --git a/contrib/nvi/vi/v_increment.c b/contrib/nvi/vi/v_increment.c new file mode 100644 index 0000000..45e763f --- /dev/null +++ b/contrib/nvi/vi/v_increment.c @@ -0,0 +1,267 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_increment.c 10.12 (Berkeley) 3/19/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +static char * const fmt[] = { +#define DEC 0 + "%ld", +#define SDEC 1 + "%+ld", +#define HEXC 2 + "0X%0*lX", +#define HEXL 3 + "0x%0*lx", +#define OCTAL 4 + "%#0*lo", +}; + +static void inc_err __P((SCR *, enum nresult)); + +/* + * v_increment -- [count]#[#+-] + * Increment/decrement a keyword number. + * + * PUBLIC: int v_increment __P((SCR *, VICMD *)); + */ +int +v_increment(sp, vp) + SCR *sp; + VICMD *vp; +{ + enum nresult nret; + u_long ulval; + long change, ltmp, lval; + size_t beg, blen, end, len, nlen, wlen; + int base, isempty, rval; + char *bp, *ntype, *p, *t, nbuf[100]; + + /* Validate the operator. */ + if (vp->character == '#') + vp->character = '+'; + if (vp->character != '+' && vp->character != '-') { + v_emsg(sp, vp->kp->usage, VIM_USAGE); + return (1); + } + + /* If new value set, save it off, but it has to fit in a long. */ + if (F_ISSET(vp, VC_C1SET)) { + if (vp->count > LONG_MAX) { + inc_err(sp, NUM_OVER); + return (1); + } + change = vp->count; + } else + change = 1; + + /* Get the line. */ + if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { + if (isempty) + goto nonum; + return (1); + } + + /* + * Skip any leading space before the number. Getting a cursor word + * implies moving the cursor to its beginning, if we moved, refresh + * now. + */ + for (beg = vp->m_start.cno; beg < len && isspace(p[beg]); ++beg); + if (beg >= len) + goto nonum; + if (beg != vp->m_start.cno) { + sp->cno = beg; + (void)vs_refresh(sp, 0); + } + +#undef ishex +#define ishex(c) (isdigit(c) || strchr("abcdefABCDEF", c)) +#undef isoctal +#define isoctal(c) (isdigit(c) && (c) != '8' && (c) != '9') + + /* + * Look for 0[Xx], or leading + or - signs, guess at the base. + * The character after that must be a number. Wlen is set to + * the remaining characters in the line that could be part of + * the number. + */ + wlen = len - beg; + if (p[beg] == '0' && wlen > 2 && + (p[beg + 1] == 'X' || p[beg + 1] == 'x')) { + base = 16; + end = beg + 2; + if (!ishex(p[end])) + goto decimal; + ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL]; + } else if (p[beg] == '0' && wlen > 1) { + base = 8; + end = beg + 1; + if (!isoctal(p[end])) + goto decimal; + ntype = fmt[OCTAL]; + } else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) { + base = 10; + end = beg + 1; + ntype = fmt[SDEC]; + if (!isdigit(p[end])) + goto nonum; + } else { +decimal: base = 10; + end = beg; + ntype = fmt[DEC]; + if (!isdigit(p[end])) { +nonum: msgq(sp, M_ERR, "181|Cursor not in a number"); + return (1); + } + } + + /* Find the end of the word, possibly correcting the base. */ + while (++end < len) { + switch (base) { + case 8: + if (isoctal(p[end])) + continue; + if (p[end] == '8' || p[end] == '9') { + base = 10; + ntype = fmt[DEC]; + continue; + } + break; + case 10: + if (isdigit(p[end])) + continue; + break; + case 16: + if (ishex(p[end])) + continue; + break; + default: + abort(); + /* NOTREACHED */ + } + break; + } + wlen = (end - beg); + + /* + * XXX + * If the line was at the end of the buffer, we have to copy it + * so we can guarantee that it's NULL-terminated. We make the + * buffer big enough to fit the line changes as well, and only + * allocate once. + */ + GET_SPACE_RET(sp, bp, blen, len + 50); + if (end == len) { + memmove(bp, &p[beg], wlen); + bp[wlen] = '\0'; + t = bp; + } else + t = &p[beg]; + + /* + * Octal or hex deal in unsigned longs, everything else is done + * in signed longs. + */ + if (base == 10) { + if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK) + goto err; + ltmp = vp->character == '-' ? -change : change; + if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) { + nret = NUM_OVER; + goto err; + } + if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) { + nret = NUM_UNDER; + goto err; + } + lval += ltmp; + /* If we cross 0, signed numbers lose their sign. */ + if (lval == 0 && ntype == fmt[SDEC]) + ntype = fmt[DEC]; + nlen = snprintf(nbuf, sizeof(nbuf), ntype, lval); + } else { + if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK) + goto err; + if (vp->character == '+') { + if (!NPFITS(ULONG_MAX, ulval, change)) { + nret = NUM_OVER; + goto err; + } + ulval += change; + } else { + if (ulval < change) { + nret = NUM_UNDER; + goto err; + } + ulval -= change; + } + + /* Correct for literal "0[Xx]" in format. */ + if (base == 16) + wlen -= 2; + + nlen = snprintf(nbuf, sizeof(nbuf), ntype, wlen, ulval); + } + + /* Build the new line. */ + memmove(bp, p, beg); + memmove(bp + beg, nbuf, nlen); + memmove(bp + beg + nlen, p + end, len - beg - (end - beg)); + len = beg + nlen + (len - beg - (end - beg)); + + nret = NUM_OK; + rval = db_set(sp, vp->m_start.lno, bp, len); + + if (0) { +err: rval = 1; + inc_err(sp, nret); + } + if (bp != NULL) + FREE_SPACE(sp, bp, blen); + return (rval); +} + +static void +inc_err(sp, nret) + SCR *sp; + enum nresult nret; +{ + switch (nret) { + case NUM_ERR: + break; + case NUM_OK: + abort(); + /* NOREACHED */ + case NUM_OVER: + msgq(sp, M_ERR, "182|Resulting number too large"); + break; + case NUM_UNDER: + msgq(sp, M_ERR, "183|Resulting number too small"); + break; + } +} diff --git a/contrib/nvi/vi/v_init.c b/contrib/nvi/vi/v_init.c new file mode 100644 index 0000000..ee58de3 --- /dev/null +++ b/contrib/nvi/vi/v_init.c @@ -0,0 +1,126 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_init.c 10.8 (Berkeley) 3/30/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_screen_copy -- + * Copy vi screen. + * + * PUBLIC: int v_screen_copy __P((SCR *, SCR *)); + */ +int +v_screen_copy(orig, sp) + SCR *orig, *sp; +{ + VI_PRIVATE *ovip, *nvip; + + /* Create the private vi structure. */ + CALLOC_RET(orig, nvip, VI_PRIVATE *, 1, sizeof(VI_PRIVATE)); + sp->vi_private = nvip; + + /* Invalidate the line size cache. */ + VI_SCR_CFLUSH(nvip); + + if (orig == NULL) { + nvip->csearchdir = CNOTSET; + } else { + ovip = VIP(orig); + + /* User can replay the last input, but nothing else. */ + if (ovip->rep_len != 0) { + MALLOC_RET(orig, nvip->rep, EVENT *, ovip->rep_len); + memmove(nvip->rep, ovip->rep, ovip->rep_len); + nvip->rep_len = ovip->rep_len; + } + + /* Copy the paragraph/section information. */ + if (ovip->ps != NULL && (nvip->ps = + v_strdup(sp, ovip->ps, strlen(ovip->ps))) == NULL) + return (1); + + nvip->lastckey = ovip->lastckey; + nvip->csearchdir = ovip->csearchdir; + + nvip->srows = ovip->srows; + } + return (0); +} + +/* + * v_screen_end -- + * End a vi screen. + * + * PUBLIC: int v_screen_end __P((SCR *)); + */ +int +v_screen_end(sp) + SCR *sp; +{ + VI_PRIVATE *vip; + + if ((vip = VIP(sp)) == NULL) + return (0); + if (vip->keyw != NULL) + free(vip->keyw); + if (vip->rep != NULL) + free(vip->rep); + if (vip->ps != NULL) + free(vip->ps); + + if (HMAP != NULL) + free(HMAP); + + free(vip); + sp->vi_private = NULL; + + return (0); +} + +/* + * v_optchange -- + * Handle change of options for vi. + * + * PUBLIC: int v_optchange __P((SCR *, int, char *, u_long *)); + */ +int +v_optchange(sp, offset, str, valp) + SCR *sp; + int offset; + char *str; + u_long *valp; +{ + switch (offset) { + case O_PARAGRAPHS: + return (v_buildps(sp, str, O_STR(sp, O_SECTIONS))); + case O_SECTIONS: + return (v_buildps(sp, O_STR(sp, O_PARAGRAPHS), str)); + case O_WINDOW: + return (vs_crel(sp, *valp)); + } + return (0); +} diff --git a/contrib/nvi/vi/v_itxt.c b/contrib/nvi/vi/v_itxt.c new file mode 100644 index 0000000..6cf9377 --- /dev/null +++ b/contrib/nvi/vi/v_itxt.c @@ -0,0 +1,537 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_itxt.c 10.16 (Berkeley) 10/23/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * !!! + * Repeated input in the historic vi is mostly wrong and this isn't very + * backward compatible. For example, if the user entered "3Aab\ncd" in + * the historic vi, the "ab" was repeated 3 times, and the "\ncd" was then + * appended to the result. There was also a hack which I don't remember + * right now, where "3o" would open 3 lines and then let the user fill them + * in, to make screen movements on 300 baud modems more tolerable. I don't + * think it's going to be missed. + * + * !!! + * There's a problem with the way that we do logging for change commands with + * implied motions (e.g. A, I, O, cc, etc.). Since the main vi loop logs the + * starting cursor position before the change command "moves" the cursor, the + * cursor position to which we return on undo will be where the user entered + * the change command, not the start of the change. Several of the following + * routines re-log the cursor to make this work correctly. Historic vi tried + * to do the same thing, and mostly got it right. (The only spectacular way + * it fails is if the user entered 'o' from anywhere but the last character of + * the line, the undo returned the cursor to the start of the line. If the + * user was on the last character of the line, the cursor returned to that + * position.) We also check for mapped keys waiting, i.e. if we're in the + * middle of a map, don't bother logging the cursor. + */ +#define LOG_CORRECT { \ + if (!MAPPED_KEYS_WAITING(sp)) \ + (void)log_cursor(sp); \ +} + +static u_int32_t set_txt_std __P((SCR *, VICMD *, u_int32_t)); + +/* + * v_iA -- [count]A + * Append text to the end of the line. + * + * PUBLIC: int v_iA __P((SCR *, VICMD *)); + */ +int +v_iA(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + + if (!db_get(sp, vp->m_start.lno, 0, NULL, &len)) + sp->cno = len == 0 ? 0 : len - 1; + + LOG_CORRECT; + + return (v_ia(sp, vp)); +} + +/* + * v_ia -- [count]a + * [count]A + * Append text to the cursor position. + * + * PUBLIC: int v_ia __P((SCR *, VICMD *)); + */ +int +v_ia(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + u_int32_t flags; + int isempty; + char *p; + + flags = set_txt_std(sp, vp, 0); + sp->showmode = SM_APPEND; + sp->lno = vp->m_start.lno; + + /* Move the cursor one column to the right and repaint the screen. */ + if (db_eget(sp, sp->lno, &p, &len, &isempty)) { + if (!isempty) + return (1); + len = 0; + LF_SET(TXT_APPENDEOL); + } else if (len) { + if (len == sp->cno + 1) { + sp->cno = len; + LF_SET(TXT_APPENDEOL); + } else + ++sp->cno; + } else + LF_SET(TXT_APPENDEOL); + + return (v_txt(sp, vp, NULL, p, len, + 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); +} + +/* + * v_iI -- [count]I + * Insert text at the first nonblank. + * + * PUBLIC: int v_iI __P((SCR *, VICMD *)); + */ +int +v_iI(sp, vp) + SCR *sp; + VICMD *vp; +{ + sp->cno = 0; + if (nonblank(sp, vp->m_start.lno, &sp->cno)) + return (1); + + LOG_CORRECT; + + return (v_ii(sp, vp)); +} + +/* + * v_ii -- [count]i + * [count]I + * Insert text at the cursor position. + * + * PUBLIC: int v_ii __P((SCR *, VICMD *)); + */ +int +v_ii(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + u_int32_t flags; + int isempty; + char *p; + + flags = set_txt_std(sp, vp, 0); + sp->showmode = SM_INSERT; + sp->lno = vp->m_start.lno; + + if (db_eget(sp, sp->lno, &p, &len, &isempty)) { + if (!isempty) + return (1); + len = 0; + } + + if (len == 0) + LF_SET(TXT_APPENDEOL); + return (v_txt(sp, vp, NULL, p, len, + 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); +} + +enum which { o_cmd, O_cmd }; +static int io __P((SCR *, VICMD *, enum which)); + +/* + * v_iO -- [count]O + * Insert text above this line. + * + * PUBLIC: int v_iO __P((SCR *, VICMD *)); + */ +int +v_iO(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (io(sp, vp, O_cmd)); +} + +/* + * v_io -- [count]o + * Insert text after this line. + * + * PUBLIC: int v_io __P((SCR *, VICMD *)); + */ +int +v_io(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (io(sp, vp, o_cmd)); +} + +static int +io(sp, vp, cmd) + SCR *sp; + VICMD *vp; + enum which cmd; +{ + recno_t ai_line, lno; + size_t len; + u_int32_t flags; + char *p; + + flags = set_txt_std(sp, vp, TXT_ADDNEWLINE | TXT_APPENDEOL); + sp->showmode = SM_INSERT; + + if (sp->lno == 1) { + if (db_last(sp, &lno)) + return (1); + if (lno != 0) + goto insert; + p = NULL; + len = 0; + ai_line = OOBLNO; + } else { +insert: p = ""; + sp->cno = 0; + LOG_CORRECT; + + if (cmd == O_cmd) { + if (db_insert(sp, sp->lno, p, 0)) + return (1); + if (db_get(sp, sp->lno, DBG_FATAL, &p, &len)) + return (1); + ai_line = sp->lno + 1; + } else { + if (db_append(sp, 1, sp->lno, p, 0)) + return (1); + if (db_get(sp, ++sp->lno, DBG_FATAL, &p, &len)) + return (1); + ai_line = sp->lno - 1; + } + } + return (v_txt(sp, vp, NULL, p, len, + 0, ai_line, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); +} + +/* + * v_change -- [buffer][count]c[count]motion + * [buffer][count]C + * [buffer][count]S + * Change command. + * + * PUBLIC: int v_change __P((SCR *, VICMD *)); + */ +int +v_change(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t blen, len; + u_int32_t flags; + int isempty, lmode, rval; + char *bp, *p; + + /* + * 'c' can be combined with motion commands that set the resulting + * cursor position, i.e. "cG". Clear the VM_RCM flags and make the + * resulting cursor position stick, inserting text has its own rules + * for cursor positioning. + */ + F_CLR(vp, VM_RCM_MASK); + F_SET(vp, VM_RCM_SET); + + /* + * Find out if the file is empty, it's easier to handle it as a + * special case. + */ + if (vp->m_start.lno == vp->m_stop.lno && + db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { + if (!isempty) + return (1); + return (v_ia(sp, vp)); + } + + flags = set_txt_std(sp, vp, 0); + sp->showmode = SM_CHANGE; + + /* + * Move the cursor to the start of the change. Note, if autoindent + * is turned on, the cc command in line mode changes from the first + * *non-blank* character of the line, not the first character. And, + * to make it just a bit more exciting, the initial space is handled + * as auto-indent characters. + */ + lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0; + if (lmode) { + vp->m_start.cno = 0; + if (O_ISSET(sp, O_AUTOINDENT)) { + if (nonblank(sp, vp->m_start.lno, &vp->m_start.cno)) + return (1); + LF_SET(TXT_AICHARS); + } + } + sp->lno = vp->m_start.lno; + sp->cno = vp->m_start.cno; + + LOG_CORRECT; + + /* + * If not in line mode and changing within a single line, copy the + * text and overwrite it. + */ + if (!lmode && vp->m_start.lno == vp->m_stop.lno) { + /* + * !!! + * Historic practice, c did not cut into the numeric buffers, + * only the unnamed one. + */ + if (cut(sp, + F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, + &vp->m_start, &vp->m_stop, lmode)) + return (1); + if (len == 0) + LF_SET(TXT_APPENDEOL); + LF_SET(TXT_EMARK | TXT_OVERWRITE); + return (v_txt(sp, vp, &vp->m_stop, p, len, + 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); + } + + /* + * It's trickier if in line mode or changing over multiple lines. If + * we're in line mode delete all of the lines and insert a replacement + * line which the user edits. If there was leading whitespace in the + * first line being changed, we copy it and use it as the replacement. + * If we're not in line mode, we delete the text and start inserting. + * + * !!! + * Copy the text. Historic practice, c did not cut into the numeric + * buffers, only the unnamed one. + */ + if (cut(sp, + F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, + &vp->m_start, &vp->m_stop, lmode)) + return (1); + + /* If replacing entire lines and there's leading text. */ + if (lmode && vp->m_start.cno) { + /* + * Get a copy of the first line changed, and copy out the + * leading text. + */ + if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len)) + return (1); + GET_SPACE_RET(sp, bp, blen, vp->m_start.cno); + memmove(bp, p, vp->m_start.cno); + } else + bp = NULL; + + /* Delete the text. */ + if (del(sp, &vp->m_start, &vp->m_stop, lmode)) + return (1); + + /* If replacing entire lines, insert a replacement line. */ + if (lmode) { + if (db_insert(sp, vp->m_start.lno, bp, vp->m_start.cno)) + return (1); + sp->lno = vp->m_start.lno; + len = sp->cno = vp->m_start.cno; + } + + /* Get the line we're editing. */ + if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { + if (!isempty) + return (1); + len = 0; + } + + /* Check to see if we're appending to the line. */ + if (vp->m_start.cno >= len) + LF_SET(TXT_APPENDEOL); + + rval = v_txt(sp, vp, NULL, p, len, + 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags); + + if (bp != NULL) + FREE_SPACE(sp, bp, blen); + return (rval); +} + +/* + * v_Replace -- [count]R + * Overwrite multiple characters. + * + * PUBLIC: int v_Replace __P((SCR *, VICMD *)); + */ +int +v_Replace(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + u_int32_t flags; + int isempty; + char *p; + + flags = set_txt_std(sp, vp, 0); + sp->showmode = SM_REPLACE; + + if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { + if (!isempty) + return (1); + len = 0; + LF_SET(TXT_APPENDEOL); + } else { + if (len == 0) + LF_SET(TXT_APPENDEOL); + LF_SET(TXT_OVERWRITE | TXT_REPLACE); + } + vp->m_stop.lno = vp->m_start.lno; + vp->m_stop.cno = len ? len - 1 : 0; + + return (v_txt(sp, vp, &vp->m_stop, p, len, + 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); +} + +/* + * v_subst -- [buffer][count]s + * Substitute characters. + * + * PUBLIC: int v_subst __P((SCR *, VICMD *)); + */ +int +v_subst(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + u_int32_t flags; + int isempty; + char *p; + + flags = set_txt_std(sp, vp, 0); + sp->showmode = SM_CHANGE; + + if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { + if (!isempty) + return (1); + len = 0; + LF_SET(TXT_APPENDEOL); + } else { + if (len == 0) + LF_SET(TXT_APPENDEOL); + LF_SET(TXT_EMARK | TXT_OVERWRITE); + } + + vp->m_stop.lno = vp->m_start.lno; + vp->m_stop.cno = + vp->m_start.cno + (F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0); + if (vp->m_stop.cno > len - 1) + vp->m_stop.cno = len - 1; + + if (p != NULL && cut(sp, + F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, + &vp->m_start, &vp->m_stop, 0)) + return (1); + + return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, 1, flags)); +} + +/* + * set_txt_std -- + * Initialize text processing flags. + */ +static u_int32_t +set_txt_std(sp, vp, flags) + SCR *sp; + VICMD *vp; + u_int32_t flags; +{ + LF_SET(TXT_CNTRLT | + TXT_ESCAPE | TXT_MAPINPUT | TXT_RECORD | TXT_RESOLVE); + + if (F_ISSET(vp, VC_ISDOT)) + LF_SET(TXT_REPLAY); + + if (O_ISSET(sp, O_ALTWERASE)) + LF_SET(TXT_ALTWERASE); + if (O_ISSET(sp, O_AUTOINDENT)) + LF_SET(TXT_AUTOINDENT); + if (O_ISSET(sp, O_BEAUTIFY)) + LF_SET(TXT_BEAUTIFY); + if (O_ISSET(sp, O_SHOWMATCH)) + LF_SET(TXT_SHOWMATCH); + if (F_ISSET(sp, SC_SCRIPT)) + LF_SET(TXT_CR); + if (O_ISSET(sp, O_TTYWERASE)) + LF_SET(TXT_TTYWERASE); + + /* + * !!! + * Mapped keys were sometimes unaffected by the wrapmargin option + * in the historic 4BSD vi. Consider the following commands, where + * each is executed on an empty line, in an 80 column screen, with + * the wrapmargin value set to 60. + * + * aABC DEF <ESC>.... + * :map K aABC DEF ^V<ESC><CR>KKKKK + * :map K 5aABC DEF ^V<ESC><CR>K + * + * The first and second commands are affected by wrapmargin. The + * third is not. (If the inserted text is itself longer than the + * wrapmargin value, i.e. if the "ABC DEF " string is replaced by + * something that's longer than 60 columns from the beginning of + * the line, the first two commands behave as before, but the third + * command gets fairly strange.) The problem is that people wrote + * macros that depended on the third command NOT being affected by + * wrapmargin, as in this gem which centers lines: + * + * map #c $mq81a ^V^[81^V^V|D`qld0:s/ / /g^V^M$p + * + * For compatibility reasons, we try and make it all work here. I + * offer no hope that this is right, but it's probably pretty close. + * + * XXX + * Once I work my courage up, this is all gonna go away. It's too + * evil to survive. + */ + if ((O_ISSET(sp, O_WRAPLEN) || O_ISSET(sp, O_WRAPMARGIN)) && + (!MAPPED_KEYS_WAITING(sp) || !F_ISSET(vp, VC_C1SET))) + LF_SET(TXT_WRAPMARGIN); + return (flags); +} diff --git a/contrib/nvi/vi/v_left.c b/contrib/nvi/vi/v_left.c new file mode 100644 index 0000000..cfed80c --- /dev/null +++ b/contrib/nvi/vi/v_left.c @@ -0,0 +1,293 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_left.c 10.7 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_left -- [count]^H, [count]h + * Move left by columns. + * + * PUBLIC: int v_left __P((SCR *, VICMD *)); + */ +int +v_left(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t cnt; + + /* + * !!! + * The ^H and h commands always failed in the first column. + */ + if (vp->m_start.cno == 0) { + v_sol(sp); + return (1); + } + + /* Find the end of the range. */ + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + if (vp->m_start.cno > cnt) + vp->m_stop.cno = vp->m_start.cno - cnt; + else + vp->m_stop.cno = 0; + + /* + * All commands move to the end of the range. Motion commands + * adjust the starting point to the character before the current + * one. + */ + if (ISMOTION(vp)) + --vp->m_start.cno; + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_cfirst -- [count]_ + * Move to the first non-blank character in a line. + * + * PUBLIC: int v_cfirst __P((SCR *, VICMD *)); + */ +int +v_cfirst(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t cnt, lno; + + /* + * !!! + * If the _ is a motion component, it makes the command a line motion + * e.g. "d_" deletes the line. It also means that the cursor doesn't + * move. + * + * The _ command never failed in the first column. + */ + if (ISMOTION(vp)) + F_SET(vp, VM_LMODE); + /* + * !!! + * Historically a specified count makes _ move down count - 1 + * rows, so, "3_" is the same as "2j_". + */ + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + if (cnt != 1) { + --vp->count; + return (v_down(sp, vp)); + } + + /* + * Move to the first non-blank. + * + * Can't just use RCM_SET_FNB, in case _ is used as the motion + * component of another command. + */ + vp->m_stop.cno = 0; + if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) + return (1); + + /* + * !!! + * The _ command has to fail if the file is empty and we're doing + * a delete. If deleting line 1, and 0 is the first nonblank, + * make the check. + */ + if (vp->m_stop.lno == 1 && + vp->m_stop.cno == 0 && ISCMD(vp->rkp, 'd')) { + if (db_last(sp, &lno)) + return (1); + if (lno == 0) { + v_sol(sp); + return (1); + } + } + + /* + * Delete and non-motion commands move to the end of the range, + * yank stays at the start. Ignore others. + */ + vp->m_final = + ISMOTION(vp) && ISCMD(vp->rkp, 'y') ? vp->m_start : vp->m_stop; + return (0); +} + +/* + * v_first -- ^ + * Move to the first non-blank character in this line. + * + * PUBLIC: int v_first __P((SCR *, VICMD *)); + */ +int +v_first(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* + * !!! + * Yielding to none in our quest for compatibility with every + * historical blemish of vi, no matter how strange it might be, + * we permit the user to enter a count and then ignore it. + */ + + /* + * Move to the first non-blank. + * + * Can't just use RCM_SET_FNB, in case ^ is used as the motion + * component of another command. + */ + vp->m_stop.cno = 0; + if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) + return (1); + + /* + * !!! + * The ^ command succeeded if used as a command when the cursor was + * on the first non-blank in the line, but failed if used as a motion + * component in the same situation. + */ + if (ISMOTION(vp) && vp->m_start.cno == vp->m_stop.cno) { + v_sol(sp); + return (1); + } + + /* + * If moving right, non-motion commands move to the end of the range. + * Delete and yank stay at the start. Motion commands adjust the + * ending point to the character before the current ending charcter. + * + * If moving left, all commands move to the end of the range. Motion + * commands adjust the starting point to the character before the + * current starting character. + */ + if (vp->m_start.cno < vp->m_stop.cno) + if (ISMOTION(vp)) { + --vp->m_stop.cno; + vp->m_final = vp->m_start; + } else + vp->m_final = vp->m_stop; + else { + if (ISMOTION(vp)) + --vp->m_start.cno; + vp->m_final = vp->m_stop; + } + return (0); +} + +/* + * v_ncol -- [count]| + * Move to column count or the first column on this line. If the + * requested column is past EOL, move to EOL. The nasty part is + * that we have to know character column widths to make this work. + * + * PUBLIC: int v_ncol __P((SCR *, VICMD *)); + */ +int +v_ncol(sp, vp) + SCR *sp; + VICMD *vp; +{ + if (F_ISSET(vp, VC_C1SET) && vp->count > 1) { + --vp->count; + vp->m_stop.cno = + vs_colpos(sp, vp->m_start.lno, (size_t)vp->count); + /* + * !!! + * The | command succeeded if used as a command and the cursor + * didn't move, but failed if used as a motion component in the + * same situation. + */ + if (ISMOTION(vp) && vp->m_stop.cno == vp->m_start.cno) { + v_nomove(sp); + return (1); + } + } else { + /* + * !!! + * The | command succeeded if used as a command in column 0 + * without a count, but failed if used as a motion component + * in the same situation. + */ + if (ISMOTION(vp) && vp->m_start.cno == 0) { + v_sol(sp); + return (1); + } + vp->m_stop.cno = 0; + } + + /* + * If moving right, non-motion commands move to the end of the range. + * Delete and yank stay at the start. Motion commands adjust the + * ending point to the character before the current ending charcter. + * + * If moving left, all commands move to the end of the range. Motion + * commands adjust the starting point to the character before the + * current starting character. + */ + if (vp->m_start.cno < vp->m_stop.cno) + if (ISMOTION(vp)) { + --vp->m_stop.cno; + vp->m_final = vp->m_start; + } else + vp->m_final = vp->m_stop; + else { + if (ISMOTION(vp)) + --vp->m_start.cno; + vp->m_final = vp->m_stop; + } + return (0); +} + +/* + * v_zero -- 0 + * Move to the first column on this line. + * + * PUBLIC: int v_zero __P((SCR *, VICMD *)); + */ +int +v_zero(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* + * !!! + * The 0 command succeeded if used as a command in the first column + * but failed if used as a motion component in the same situation. + */ + if (ISMOTION(vp) && vp->m_start.cno == 0) { + v_sol(sp); + return (1); + } + + /* + * All commands move to the end of the range. Motion commands + * adjust the starting point to the character before the current + * one. + */ + vp->m_stop.cno = 0; + if (ISMOTION(vp)) + --vp->m_start.cno; + vp->m_final = vp->m_stop; + return (0); +} diff --git a/contrib/nvi/vi/v_mark.c b/contrib/nvi/vi/v_mark.c new file mode 100644 index 0000000..447430e --- /dev/null +++ b/contrib/nvi/vi/v_mark.c @@ -0,0 +1,234 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_mark.c 10.8 (Berkeley) 9/20/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_mark -- m[a-z] + * Set a mark. + * + * PUBLIC: int v_mark __P((SCR *, VICMD *)); + */ +int +v_mark(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (mark_set(sp, vp->character, &vp->m_start, 1)); +} + +enum which {BQMARK, FQMARK}; +static int mark __P((SCR *, VICMD *, enum which)); + + +/* + * v_bmark -- `['`a-z] + * Move to a mark. + * + * Moves to a mark, setting both row and column. + * + * !!! + * Although not commonly known, the "'`" and "'`" forms are historically + * valid. The behavior is determined by the first character, so "`'" is + * the same as "``". Remember this fact -- you'll be amazed at how many + * people don't know it and will be delighted that you are able to tell + * them. + * + * PUBLIC: int v_bmark __P((SCR *, VICMD *)); + */ +int +v_bmark(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (mark(sp, vp, BQMARK)); +} + +/* + * v_fmark -- '['`a-z] + * Move to a mark. + * + * Move to the first nonblank character of the line containing the mark. + * + * PUBLIC: int v_fmark __P((SCR *, VICMD *)); + */ +int +v_fmark(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (mark(sp, vp, FQMARK)); +} + +/* + * mark -- + * Mark commands. + */ +static int +mark(sp, vp, cmd) + SCR *sp; + VICMD *vp; + enum which cmd; +{ + dir_t dir; + MARK m; + size_t len; + + if (mark_get(sp, vp->character, &vp->m_stop, M_BERR)) + return (1); + + /* + * !!! + * Historically, BQMARKS for character positions that no longer + * existed acted as FQMARKS. + * + * FQMARKS move to the first non-blank. + */ + switch (cmd) { + case BQMARK: + if (db_get(sp, vp->m_stop.lno, DBG_FATAL, NULL, &len)) + return (1); + if (vp->m_stop.cno < len || + vp->m_stop.cno == len && len == 0) + break; + + if (ISMOTION(vp)) + F_SET(vp, VM_LMODE); + cmd = FQMARK; + /* FALLTHROUGH */ + case FQMARK: + vp->m_stop.cno = 0; + if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) + return (1); + break; + default: + abort(); + } + + /* Non-motion commands move to the end of the range. */ + if (!ISMOTION(vp)) { + vp->m_final = vp->m_stop; + return (0); + } + + /* + * !!! + * If a motion component to a BQMARK, the cursor has to move. + */ + if (cmd == BQMARK && + vp->m_stop.lno == vp->m_start.lno && + vp->m_stop.cno == vp->m_start.cno) { + v_nomove(sp); + return (1); + } + + /* + * If the motion is in the reverse direction, switch the start and + * stop MARK's so that it's in a forward direction. (There's no + * reason for this other than to make the tests below easier. The + * code in vi.c:vi() would have done the switch.) Both forward + * and backward motions can happen for any kind of search command. + */ + if (vp->m_start.lno > vp->m_stop.lno || + vp->m_start.lno == vp->m_stop.lno && + vp->m_start.cno > vp->m_stop.cno) { + m = vp->m_start; + vp->m_start = vp->m_stop; + vp->m_stop = m; + dir = BACKWARD; + } else + dir = FORWARD; + + /* + * Yank cursor motion, when associated with marks as motion commands, + * historically behaved as follows: + * + * ` motion ' motion + * Line change? Line change? + * Y N Y N + * -------------- --------------- + * FORWARD: | NM NM | NM NM + * | | + * BACKWARD: | M M | M NM(1) + * + * where NM means the cursor didn't move, and M means the cursor + * moved to the mark. + * + * As the cursor was usually moved for yank commands associated + * with backward motions, this implementation regularizes it by + * changing the NM at position (1) to be an M. This makes mark + * motions match search motions, which is probably A Good Thing. + * + * Delete cursor motion was always to the start of the text region, + * regardless. Ignore other motion commands. + */ +#ifdef HISTORICAL_PRACTICE + if (ISCMD(vp->rkp, 'y')) { + if ((cmd == BQMARK || + cmd == FQMARK && vp->m_start.lno != vp->m_stop.lno) && + (vp->m_start.lno > vp->m_stop.lno || + vp->m_start.lno == vp->m_stop.lno && + vp->m_start.cno > vp->m_stop.cno)) + vp->m_final = vp->m_stop; + } else if (ISCMD(vp->rkp, 'd')) + if (vp->m_start.lno > vp->m_stop.lno || + vp->m_start.lno == vp->m_stop.lno && + vp->m_start.cno > vp->m_stop.cno) + vp->m_final = vp->m_stop; +#else + vp->m_final = vp->m_start; +#endif + + /* + * Forward marks are always line oriented, and it's set in the + * vcmd.c table. + */ + if (cmd == FQMARK) + return (0); + + /* + * BQMARK'S moving backward and starting at column 0, and ones moving + * forward and ending at column 0 are corrected to the last column of + * the previous line. Otherwise, adjust the starting/ending point to + * the character before the current one (this is safe because we know + * the search had to move to succeed). + * + * Mark motions become line mode opertions if they start at the first + * nonblank and end at column 0 of another line. + */ + if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) { + if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) + return (1); + vp->m_stop.cno = len ? len - 1 : 0; + len = 0; + if (nonblank(sp, vp->m_start.lno, &len)) + return (1); + if (vp->m_start.cno <= len) + F_SET(vp, VM_LMODE); + } else + --vp->m_stop.cno; + + return (0); +} diff --git a/contrib/nvi/vi/v_match.c b/contrib/nvi/vi/v_match.c new file mode 100644 index 0000000..3996560 --- /dev/null +++ b/contrib/nvi/vi/v_match.c @@ -0,0 +1,170 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_match.c 10.8 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_match -- % + * Search to matching character. + * + * PUBLIC: int v_match __P((SCR *, VICMD *)); + */ +int +v_match(sp, vp) + SCR *sp; + VICMD *vp; +{ + VCS cs; + MARK *mp; + size_t cno, len, off; + int cnt, isempty, matchc, startc, (*gc)__P((SCR *, VCS *)); + char *p; + + /* + * !!! + * Historic practice; ignore the count. + * + * !!! + * Historical practice was to search for the initial character in the + * forward direction only. + */ + if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { + if (isempty) + goto nomatch; + return (1); + } + for (off = vp->m_start.cno;; ++off) { + if (off >= len) { +nomatch: msgq(sp, M_BERR, "184|No match character on this line"); + return (1); + } + switch (startc = p[off]) { + case '(': + matchc = ')'; + gc = cs_next; + break; + case ')': + matchc = '('; + gc = cs_prev; + break; + case '[': + matchc = ']'; + gc = cs_next; + break; + case ']': + matchc = '['; + gc = cs_prev; + break; + case '{': + matchc = '}'; + gc = cs_next; + break; + case '}': + matchc = '{'; + gc = cs_prev; + break; + case '<': + matchc = '>'; + gc = cs_next; + break; + case '>': + matchc = '<'; + gc = cs_prev; + break; + default: + continue; + } + break; + } + + cs.cs_lno = vp->m_start.lno; + cs.cs_cno = off; + if (cs_init(sp, &cs)) + return (1); + for (cnt = 1;;) { + if (gc(sp, &cs)) + return (1); + if (cs.cs_flags != 0) { + if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) + break; + continue; + } + if (cs.cs_ch == startc) + ++cnt; + else if (cs.cs_ch == matchc && --cnt == 0) + break; + } + if (cnt) { + msgq(sp, M_BERR, "185|Matching character not found"); + return (1); + } + + vp->m_stop.lno = cs.cs_lno; + vp->m_stop.cno = cs.cs_cno; + + /* + * If moving right, non-motion commands move to the end of the range. + * Delete and yank stay at the start. + * + * If moving left, all commands move to the end of the range. + * + * !!! + * Don't correct for leftward movement -- historic vi deleted the + * starting cursor position when deleting to a match. + */ + if (vp->m_start.lno < vp->m_stop.lno || + vp->m_start.lno == vp->m_stop.lno && + vp->m_start.cno < vp->m_stop.cno) + vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; + else + vp->m_final = vp->m_stop; + + /* + * !!! + * If the motion is across lines, and the earliest cursor position + * is at or before any non-blank characters in the line, i.e. the + * movement is cutting all of the line's text, and the later cursor + * position has nothing other than whitespace characters between it + * and the end of its line, the buffer is in line mode. + */ + if (!ISMOTION(vp) || vp->m_start.lno == vp->m_stop.lno) + return (0); + mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_start : &vp->m_stop; + if (mp->cno != 0) { + cno = 0; + if (nonblank(sp, mp->lno, &cno)) + return (1); + if (cno < mp->cno) + return (0); + } + mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_stop : &vp->m_start; + if (db_get(sp, mp->lno, DBG_FATAL, &p, &len)) + return (1); + for (p += mp->cno + 1, len -= mp->cno; --len; ++p) + if (!isblank(*p)) + return (0); + F_SET(vp, VM_LMODE); + return (0); +} diff --git a/contrib/nvi/vi/v_paragraph.c b/contrib/nvi/vi/v_paragraph.c new file mode 100644 index 0000000..762e38e --- /dev/null +++ b/contrib/nvi/vi/v_paragraph.c @@ -0,0 +1,344 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_paragraph.c 10.7 (Berkeley) 3/6/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +#define INTEXT_CHECK { \ + if (len == 0 || v_isempty(p, len)) { \ + if (!--cnt) \ + goto found; \ + pstate = P_INBLANK; \ + } \ + /* \ + * !!! \ + * Historic documentation (USD:15-11, 4.2) said that formfeed \ + * characters (^L) in the first column delimited paragraphs. \ + * The historic vi code mentions formfeed characters, but never \ + * implements them. It seems reasonable, do it. \ + */ \ + if (p[0] == '\014') { \ + if (!--cnt) \ + goto found; \ + continue; \ + } \ + if (p[0] != '.' || len < 2) \ + continue; \ + for (lp = VIP(sp)->ps; *lp != '\0'; lp += 2) \ + if (lp[0] == p[1] && \ + (lp[1] == ' ' && len == 2 || lp[1] == p[2]) && \ + !--cnt) \ + goto found; \ +} + +/* + * v_paragraphf -- [count]} + * Move forward count paragraphs. + * + * Paragraphs are empty lines after text, formfeed characters, or values + * from the paragraph or section options. + * + * PUBLIC: int v_paragraphf __P((SCR *, VICMD *)); + */ +int +v_paragraphf(sp, vp) + SCR *sp; + VICMD *vp; +{ + enum { P_INTEXT, P_INBLANK } pstate; + size_t lastlen, len; + recno_t cnt, lastlno, lno; + int isempty; + char *p, *lp; + + /* + * !!! + * If the starting cursor position is at or before any non-blank + * characters in the line, i.e. the movement is cutting all of the + * line's text, the buffer is in line mode. It's a lot easier to + * check here, because we know that the end is going to be the start + * or end of a line. + * + * This was historical practice in vi, with a single exception. If + * the paragraph movement was from the start of the last line to EOF, + * then all the characters were deleted from the last line, but the + * line itself remained. If somebody complains, don't pause, don't + * hesitate, just hit them. + */ + if (ISMOTION(vp)) + if (vp->m_start.cno == 0) + F_SET(vp, VM_LMODE); + else { + vp->m_stop = vp->m_start; + vp->m_stop.cno = 0; + if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) + return (1); + if (vp->m_start.cno <= vp->m_stop.cno) + F_SET(vp, VM_LMODE); + } + + /* Figure out what state we're currently in. */ + lno = vp->m_start.lno; + if (db_get(sp, lno, 0, &p, &len)) + goto eof; + + /* + * If we start in text, we want to switch states + * (2 * N - 1) times, in non-text, (2 * N) times. + */ + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + cnt *= 2; + if (len == 0 || v_isempty(p, len)) + pstate = P_INBLANK; + else { + --cnt; + pstate = P_INTEXT; + } + + for (;;) { + lastlno = lno; + lastlen = len; + if (db_get(sp, ++lno, 0, &p, &len)) + goto eof; + switch (pstate) { + case P_INTEXT: + INTEXT_CHECK; + break; + case P_INBLANK: + if (len == 0 || v_isempty(p, len)) + break; + if (--cnt) { + pstate = P_INTEXT; + break; + } + /* + * !!! + * Non-motion commands move to the end of the range, + * delete and yank stay at the start. Ignore others. + * Adjust the end of the range for motion commands; + * historically, a motion component was to the end of + * the previous line, whereas the movement command was + * to the start of the new "paragraph". + */ +found: if (ISMOTION(vp)) { + vp->m_stop.lno = lastlno; + vp->m_stop.cno = lastlen ? lastlen - 1 : 0; + vp->m_final = vp->m_start; + } else { + vp->m_stop.lno = lno; + vp->m_stop.cno = 0; + vp->m_final = vp->m_stop; + } + return (0); + default: + abort(); + } + } + + /* + * !!! + * Adjust end of the range for motion commands; EOF is a movement + * sink. The } command historically moved to the end of the last + * line, not the beginning, from any position before the end of the + * last line. It also historically worked on empty files, so we + * have to make it okay. + */ +eof: if (vp->m_start.lno == lno || vp->m_start.lno == lno - 1) { + if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { + if (!isempty) + return (1); + vp->m_start.cno = 0; + return (0); + } + if (vp->m_start.cno == (len ? len - 1 : 0)) { + v_eof(sp, NULL); + return (1); + } + } + /* + * !!! + * Non-motion commands move to the end of the range, delete + * and yank stay at the start. Ignore others. + * + * If deleting the line (which happens if deleting to EOF), then + * cursor movement is to the first nonblank. + */ + if (ISMOTION(vp) && ISCMD(vp->rkp, 'd')) { + F_CLR(vp, VM_RCM_MASK); + F_SET(vp, VM_RCM_SETFNB); + } + vp->m_stop.lno = lno - 1; + vp->m_stop.cno = len ? len - 1 : 0; + vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; + return (0); +} + +/* + * v_paragraphb -- [count]{ + * Move backward count paragraphs. + * + * PUBLIC: int v_paragraphb __P((SCR *, VICMD *)); + */ +int +v_paragraphb(sp, vp) + SCR *sp; + VICMD *vp; +{ + enum { P_INTEXT, P_INBLANK } pstate; + size_t len; + recno_t cnt, lno; + char *p, *lp; + + /* + * !!! + * Check for SOF. The historic vi didn't complain if users hit SOF + * repeatedly, unless it was part of a motion command. There is no + * question but that Emerson's editor of choice was vi. + * + * The { command historically moved to the beginning of the first + * line if invoked on the first line. + * + * !!! + * If the starting cursor position is in the first column (backward + * paragraph movements did NOT historically pay attention to non-blank + * characters) i.e. the movement is cutting the entire line, the buffer + * is in line mode. Cuts from the beginning of the line also did not + * cut the current line, but started at the previous EOL. + * + * Correct for a left motion component while we're thinking about it. + */ + lno = vp->m_start.lno; + + if (ISMOTION(vp)) + if (vp->m_start.cno == 0) { + if (vp->m_start.lno == 1) { + v_sof(sp, &vp->m_start); + return (1); + } else + --vp->m_start.lno; + F_SET(vp, VM_LMODE); + } else + --vp->m_start.cno; + + if (vp->m_start.lno <= 1) + goto sof; + + /* Figure out what state we're currently in. */ + if (db_get(sp, lno, 0, &p, &len)) + goto sof; + + /* + * If we start in text, we want to switch states + * (2 * N - 1) times, in non-text, (2 * N) times. + */ + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + cnt *= 2; + if (len == 0 || v_isempty(p, len)) + pstate = P_INBLANK; + else { + --cnt; + pstate = P_INTEXT; + + /* + * !!! + * If the starting cursor is past the first column, + * the current line is checked for a paragraph. + */ + if (vp->m_start.cno > 0) + ++lno; + } + + for (;;) { + if (db_get(sp, --lno, 0, &p, &len)) + goto sof; + switch (pstate) { + case P_INTEXT: + INTEXT_CHECK; + break; + case P_INBLANK: + if (len != 0 && !v_isempty(p, len)) { + if (!--cnt) + goto found; + pstate = P_INTEXT; + } + break; + default: + abort(); + } + } + + /* SOF is a movement sink. */ +sof: lno = 1; + +found: vp->m_stop.lno = lno; + vp->m_stop.cno = 0; + + /* + * All commands move to the end of the range. (We already + * adjusted the start of the range for motion commands). + */ + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_buildps -- + * Build the paragraph command search pattern. + * + * PUBLIC: int v_buildps __P((SCR *, char *, char *)); + */ +int +v_buildps(sp, p_p, s_p) + SCR *sp; + char *p_p, *s_p; +{ + VI_PRIVATE *vip; + size_t p_len, s_len; + char *p; + + /* + * The vi paragraph command searches for either a paragraph or + * section option macro. + */ + p_len = p_p == NULL ? 0 : strlen(p_p); + s_len = s_p == NULL ? 0 : strlen(s_p); + + if (p_len == 0 && s_len == 0) + return (0); + + MALLOC_RET(sp, p, char *, p_len + s_len + 1); + + vip = VIP(sp); + if (vip->ps != NULL) + free(vip->ps); + + if (p_p != NULL) + memmove(p, p_p, p_len + 1); + if (s_p != NULL) + memmove(p + p_len, s_p, s_len + 1); + vip->ps = p; + return (0); +} diff --git a/contrib/nvi/vi/v_put.c b/contrib/nvi/vi/v_put.c new file mode 100644 index 0000000..77220ea --- /dev/null +++ b/contrib/nvi/vi/v_put.c @@ -0,0 +1,146 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_put.c 10.5 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +static void inc_buf __P((SCR *, VICMD *)); + +/* + * v_Put -- [buffer]P + * Insert the contents of the buffer before the cursor. + * + * PUBLIC: int v_Put __P((SCR *, VICMD *)); + */ +int +v_Put(sp, vp) + SCR *sp; + VICMD *vp; +{ + u_long cnt; + + if (F_ISSET(vp, VC_ISDOT)) + inc_buf(sp, vp); + + /* + * !!! + * Historic vi did not support a count with the 'p' and 'P' + * commands. It's useful, so we do. + */ + for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { + if (put(sp, NULL, + F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, + &vp->m_start, &vp->m_final, 0)) + return (1); + vp->m_start = vp->m_final; + if (INTERRUPTED(sp)) + return (1); + } + return (0); +} + +/* + * v_put -- [buffer]p + * Insert the contents of the buffer after the cursor. + * + * PUBLIC: int v_put __P((SCR *, VICMD *)); + */ +int +v_put(sp, vp) + SCR *sp; + VICMD *vp; +{ + u_long cnt; + + if (F_ISSET(vp, VC_ISDOT)) + inc_buf(sp, vp); + + /* + * !!! + * Historic vi did not support a count with the 'p' and 'P' + * commands. It's useful, so we do. + */ + for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { + if (put(sp, NULL, + F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, + &vp->m_start, &vp->m_final, 1)) + return (1); + vp->m_start = vp->m_final; + if (INTERRUPTED(sp)) + return (1); + } + return (0); +} + +/* + * !!! + * Historical whackadoo. The dot command `puts' the numbered buffer + * after the last one put. For example, `"4p.' would put buffer #4 + * and buffer #5. If the user continued to enter '.', the #9 buffer + * would be repeatedly output. This was not documented, and is a bit + * tricky to reconstruct. Historical versions of vi also dropped the + * contents of the default buffer after each put, so after `"4p' the + * default buffer would be empty. This makes no sense to me, so we + * don't bother. Don't assume sequential order of numeric characters. + * + * And, if that weren't exciting enough, failed commands don't normally + * set the dot command. Well, boys and girls, an exception is that + * the buffer increment gets done regardless of the success of the put. + */ +static void +inc_buf(sp, vp) + SCR *sp; + VICMD *vp; +{ + CHAR_T v; + + switch (vp->buffer) { + case '1': + v = '2'; + break; + case '2': + v = '3'; + break; + case '3': + v = '4'; + break; + case '4': + v = '5'; + break; + case '5': + v = '6'; + break; + case '6': + v = '7'; + break; + case '7': + v = '8'; + break; + case '8': + v = '9'; + break; + default: + return; + } + VIP(sp)->sdot.buffer = vp->buffer = v; +} diff --git a/contrib/nvi/vi/v_redraw.c b/contrib/nvi/vi/v_redraw.c new file mode 100644 index 0000000..4c965c7 --- /dev/null +++ b/contrib/nvi/vi/v_redraw.c @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_redraw.c 10.6 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_redraw -- ^L, ^R + * Redraw the screen. + * + * PUBLIC: int v_redraw __P((SCR *, VICMD *)); + */ +int +v_redraw(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (sp->gp->scr_refresh(sp, 1)); +} diff --git a/contrib/nvi/vi/v_replace.c b/contrib/nvi/vi/v_replace.c new file mode 100644 index 0000000..a4712b6 --- /dev/null +++ b/contrib/nvi/vi/v_replace.c @@ -0,0 +1,203 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_replace.c 10.17 (Berkeley) 6/30/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_replace -- [count]r<char> + * + * !!! + * The r command in historic vi was almost beautiful in its badness. For + * example, "r<erase>" and "r<word erase>" beeped the terminal and deleted + * a single character. "Nr<carriage return>", where N was greater than 1, + * inserted a single carriage return. "r<escape>" did cancel the command, + * but "r<literal><escape>" erased a single character. To enter a literal + * <literal> character, it required three <literal> characters after the + * command. This may not be right, but at least it's not insane. + * + * PUBLIC: int v_replace __P((SCR *, VICMD *)); + */ +int +v_replace(sp, vp) + SCR *sp; + VICMD *vp; +{ + EVENT ev; + VI_PRIVATE *vip; + TEXT *tp; + size_t blen, len; + u_long cnt; + int quote, rval; + char *bp, *p; + + vip = VIP(sp); + + /* + * If the line doesn't exist, or it's empty, replacement isn't + * allowed. It's not hard to implement, but: + * + * 1: It's historic practice (vi beeped before the replacement + * character was even entered). + * 2: For consistency, this change would require that the more + * general case, "Nr", when the user is < N characters from + * the end of the line, also work, which would be a bit odd. + * 3: Replacing with a <newline> has somewhat odd semantics. + */ + if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len)) + return (1); + if (len == 0) { + msgq(sp, M_BERR, "186|No characters to replace"); + return (1); + } + + /* + * Figure out how many characters to be replace. For no particular + * reason (other than that the semantics of replacing the newline + * are confusing) only permit the replacement of the characters in + * the current line. I suppose we could append replacement characters + * to the line, but I see no compelling reason to do so. Check this + * before we get the character to match historic practice, where Nr + * failed immediately if there were less than N characters from the + * cursor to the end of the line. + */ + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + vp->m_stop.lno = vp->m_start.lno; + vp->m_stop.cno = vp->m_start.cno + cnt - 1; + if (vp->m_stop.cno > len - 1) { + v_eol(sp, &vp->m_start); + return (1); + } + + /* + * If it's not a repeat, reset the current mode and get a replacement + * character. + */ + quote = 0; + if (!F_ISSET(vp, VC_ISDOT)) { + sp->showmode = SM_REPLACE; + if (vs_refresh(sp, 0)) + return (1); +next: if (v_event_get(sp, &ev, 0, 0)) + return (1); + + switch (ev.e_event) { + case E_CHARACTER: + /* + * <literal_next> means escape the next character. + * <escape> means they changed their minds. + */ + if (!quote) { + if (ev.e_value == K_VLNEXT) { + quote = 1; + goto next; + } + if (ev.e_value == K_ESCAPE) + return (0); + } + vip->rlast = ev.e_c; + vip->rvalue = ev.e_value; + break; + case E_ERR: + case E_EOF: + F_SET(sp, SC_EXIT_FORCE); + return (1); + case E_INTERRUPT: + /* <interrupt> means they changed their minds. */ + return (0); + case E_WRESIZE: + /* <resize> interrupts the input mode. */ + v_emsg(sp, NULL, VIM_WRESIZE); + return (0); + case E_REPAINT: + if (vs_repaint(sp, &ev)) + return (1); + goto next; + default: + v_event_err(sp, &ev); + return (0); + } + } + + /* Copy the line. */ + GET_SPACE_RET(sp, bp, blen, len); + memmove(bp, p, len); + p = bp; + + /* + * Versions of nvi before 1.57 created N new lines when they replaced + * N characters with <carriage-return> or <newline> characters. This + * is different from the historic vi, which replaced N characters with + * a single new line. Users complained, so we match historic practice. + */ + if (!quote && vip->rvalue == K_CR || vip->rvalue == K_NL) { + /* Set return line. */ + vp->m_stop.lno = vp->m_start.lno + 1; + vp->m_stop.cno = 0; + + /* The first part of the current line. */ + if (db_set(sp, vp->m_start.lno, p, vp->m_start.cno)) + goto err_ret; + + /* + * The rest of the current line. And, of course, now it gets + * tricky. If there are characters left in the line and if + * the autoindent edit option is set, white space after the + * replaced character is discarded, autoindent is applied, and + * the cursor moves to the last indent character. + */ + p += vp->m_start.cno + cnt; + len -= vp->m_start.cno + cnt; + if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) + for (; len && isblank(*p); --len, ++p); + + if ((tp = text_init(sp, p, len, len)) == NULL) + goto err_ret; + + if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) { + if (v_txt_auto(sp, vp->m_start.lno, NULL, 0, tp)) + goto err_ret; + vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0; + } else + vp->m_stop.cno = 0; + + vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0; + if (db_append(sp, 1, vp->m_start.lno, tp->lb, tp->len)) +err_ret: rval = 1; + else { + text_free(tp); + rval = 0; + } + } else { + memset(bp + vp->m_start.cno, vip->rlast, cnt); + rval = db_set(sp, vp->m_start.lno, bp, len); + } + FREE_SPACE(sp, bp, blen); + + vp->m_final = vp->m_stop; + return (rval); +} diff --git a/contrib/nvi/vi/v_right.c b/contrib/nvi/vi/v_right.c new file mode 100644 index 0000000..c2f349e --- /dev/null +++ b/contrib/nvi/vi/v_right.c @@ -0,0 +1,145 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_right.c 10.7 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_right -- [count]' ', [count]l + * Move right by columns. + * + * PUBLIC: int v_right __P((SCR *, VICMD *)); + */ +int +v_right(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + int isempty; + + if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) { + if (isempty) + goto eol; + return (1); + } + + /* It's always illegal to move right on empty lines. */ + if (len == 0) { +eol: v_eol(sp, NULL); + return (1); + } + + /* + * Non-motion commands move to the end of the range. Delete and + * yank stay at the start. Ignore others. Adjust the end of the + * range for motion commands. + * + * !!! + * Historically, "[cdsy]l" worked at the end of a line. Also, + * EOL is a count sink. + */ + vp->m_stop.cno = vp->m_start.cno + + (F_ISSET(vp, VC_C1SET) ? vp->count : 1); + if (vp->m_start.cno == len - 1 && !ISMOTION(vp)) { + v_eol(sp, NULL); + return (1); + } + if (vp->m_stop.cno >= len) { + vp->m_stop.cno = len - 1; + vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; + } else if (ISMOTION(vp)) { + --vp->m_stop.cno; + vp->m_final = vp->m_start; + } else + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_dollar -- [count]$ + * Move to the last column. + * + * PUBLIC: int v_dollar __P((SCR *, VICMD *)); + */ +int +v_dollar(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + int isempty; + + /* + * !!! + * A count moves down count - 1 rows, so, "3$" is the same as "2j$". + */ + if ((F_ISSET(vp, VC_C1SET) ? vp->count : 1) != 1) { + /* + * !!! + * Historically, if the $ is a motion, and deleting from + * at or before the first non-blank of the line, it's a + * line motion, and the line motion flag is set. + */ + vp->m_stop.cno = 0; + if (nonblank(sp, vp->m_start.lno, &vp->m_stop.cno)) + return (1); + if (ISMOTION(vp) && vp->m_start.cno <= vp->m_stop.cno) + F_SET(vp, VM_LMODE); + + --vp->count; + if (v_down(sp, vp)) + return (1); + } + + /* + * !!! + * Historically, it was illegal to use $ as a motion command on + * an empty line. Unfortunately, even though C was historically + * aliased to c$, it (and not c$) was special cased to work on + * empty lines. Since we alias C to c$ too, we have a problem. + * To fix it, we let c$ go through, on the assumption that it's + * not a problem for it to work. + */ + if (db_eget(sp, vp->m_stop.lno, NULL, &len, &isempty)) { + if (!isempty) + return (1); + len = 0; + } + + if (len == 0) { + if (ISMOTION(vp) && !ISCMD(vp->rkp, 'c')) { + v_eol(sp, NULL); + return (1); + } + return (0); + } + + /* + * Non-motion commands move to the end of the range. Delete + * and yank stay at the start. Ignore others. + */ + vp->m_stop.cno = len ? len - 1 : 0; + vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; + return (0); +} diff --git a/contrib/nvi/vi/v_screen.c b/contrib/nvi/vi/v_screen.c new file mode 100644 index 0000000..85cd1e3 --- /dev/null +++ b/contrib/nvi/vi/v_screen.c @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_screen.c 10.10 (Berkeley) 4/27/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_screen -- ^W + * Switch screens. + * + * PUBLIC: int v_screen __P((SCR *, VICMD *)); + */ +int +v_screen(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* + * You can't leave a colon command-line edit window -- it's not that + * it won't work, but it gets real weird, real fast when you execute + * a colon command out of a window that was forked from a window that's + * now backgrounded... You get the idea. + */ + if (F_ISSET(sp, SC_COMEDIT)) { + msgq(sp, M_ERR, + "308|Enter <CR> to execute a command, :q to exit"); + return (1); + } + + /* + * Try for the next lower screen, or, go back to the first + * screen on the stack. + */ + if (sp->q.cqe_next != (void *)&sp->gp->dq) + sp->nextdisp = sp->q.cqe_next; + else if (sp->gp->dq.cqh_first == sp) { + msgq(sp, M_ERR, "187|No other screen to switch to"); + return (1); + } else + sp->nextdisp = sp->gp->dq.cqh_first; + + F_SET(sp->nextdisp, SC_STATUS); + F_SET(sp, SC_SSWITCH | SC_STATUS); + return (0); +} diff --git a/contrib/nvi/vi/v_scroll.c b/contrib/nvi/vi/v_scroll.c new file mode 100644 index 0000000..92def4b --- /dev/null +++ b/contrib/nvi/vi/v_scroll.c @@ -0,0 +1,474 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_scroll.c 10.9 (Berkeley) 4/27/96"; +#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 <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +static void goto_adjust __P((VICMD *)); + +/* + * The historic vi had a problem in that all movements were by physical + * lines, not by logical, or screen lines. Arguments can be made that this + * is the right thing to do. For example, single line movements, such as + * 'j' or 'k', should probably work on physical lines. Commands like "dj", + * or "j.", where '.' is a change command, make more sense for physical lines + * than they do for logical lines. + * + * These arguments, however, don't apply to scrolling commands like ^D and + * ^F -- if the window is fairly small, using physical lines can result in + * a half-page scroll repainting the entire screen, which is not what the + * user wanted. Second, if the line is larger than the screen, using physical + * lines can make it impossible to display parts of the line -- there aren't + * any commands that don't display the beginning of the line in historic vi, + * and if both the beginning and end of the line can't be on the screen at + * the same time, you lose. This is even worse in the case of the H, L, and + * M commands -- for large lines, they may all refer to the same line and + * will result in no movement at all. + * + * Another issue is that page and half-page scrolling commands historically + * moved to the first non-blank character in the new line. If the line is + * approximately the same size as the screen, this loses because the cursor + * before and after a ^D, may refer to the same location on the screen. In + * this implementation, scrolling commands set the cursor to the first non- + * blank character if the line changes because of the scroll. Otherwise, + * the cursor is left alone. + * + * This implementation does the scrolling (^B, ^D, ^F, ^U, ^Y, ^E), and the + * cursor positioning commands (H, L, M) commands using logical lines, not + * physical. + */ + +/* + * v_lgoto -- [count]G + * Go to first non-blank character of the line count, the last line + * of the file by default. + * + * PUBLIC: int v_lgoto __P((SCR *, VICMD *)); + */ +int +v_lgoto(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t nlines; + + if (F_ISSET(vp, VC_C1SET)) { + if (!db_exist(sp, vp->count)) { + /* + * !!! + * Historically, 1G was legal in an empty file. + */ + if (vp->count == 1) { + if (db_last(sp, &nlines)) + return (1); + if (nlines == 0) + return (0); + } + v_eof(sp, &vp->m_start); + return (1); + } + vp->m_stop.lno = vp->count; + } else { + if (db_last(sp, &nlines)) + return (1); + vp->m_stop.lno = nlines ? nlines : 1; + } + goto_adjust(vp); + return (0); +} + +/* + * v_home -- [count]H + * Move to the first non-blank character of the logical line + * count - 1 from the top of the screen, 0 by default. + * + * PUBLIC: int v_home __P((SCR *, VICMD *)); + */ +int +v_home(sp, vp) + SCR *sp; + VICMD *vp; +{ + if (vs_sm_position(sp, &vp->m_stop, + F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_TOP)) + return (1); + goto_adjust(vp); + return (0); +} + +/* + * v_middle -- M + * Move to the first non-blank character of the logical line + * in the middle of the screen. + * + * PUBLIC: int v_middle __P((SCR *, VICMD *)); + */ +int +v_middle(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* + * Yielding to none in our quest for compatibility with every + * historical blemish of vi, no matter how strange it might be, + * we permit the user to enter a count and then ignore it. + */ + if (vs_sm_position(sp, &vp->m_stop, 0, P_MIDDLE)) + return (1); + goto_adjust(vp); + return (0); +} + +/* + * v_bottom -- [count]L + * Move to the first non-blank character of the logical line + * count - 1 from the bottom of the screen, 0 by default. + * + * PUBLIC: int v_bottom __P((SCR *, VICMD *)); + */ +int +v_bottom(sp, vp) + SCR *sp; + VICMD *vp; +{ + if (vs_sm_position(sp, &vp->m_stop, + F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_BOTTOM)) + return (1); + goto_adjust(vp); + return (0); +} + +static void +goto_adjust(vp) + VICMD *vp; +{ + /* Guess that it's the end of the range. */ + vp->m_final = vp->m_stop; + + /* + * Non-motion commands move the cursor to the end of the range, and + * then to the NEXT nonblank of the line. Historic vi always moved + * to the first nonblank in the line; since the H, M, and L commands + * are logical motions in this implementation, we do the next nonblank + * so that it looks approximately the same to the user. To make this + * happen, the VM_RCM_SETNNB flag is set in the vcmd.c command table. + * + * If it's a motion, it's more complicated. The best possible solution + * is probably to display the first nonblank of the line the cursor + * will eventually rest on. This is tricky, particularly given that if + * the associated command is a delete, we don't yet know what line that + * will be. So, we clear the VM_RCM_SETNNB flag, and set the first + * nonblank flag (VM_RCM_SETFNB). Note, if the lines are sufficiently + * long, this can cause the cursor to warp out of the screen. It's too + * hard to fix. + * + * XXX + * The G command is always first nonblank, so it's okay to reset it. + */ + if (ISMOTION(vp)) { + F_CLR(vp, VM_RCM_MASK); + F_SET(vp, VM_RCM_SETFNB); + } else + return; + + /* + * If moving backward in the file, delete and yank move to the end + * of the range, unless the line didn't change, in which case yank + * doesn't move. If moving forward in the file, delete and yank + * stay at the start of the range. Ignore others. + */ + if (vp->m_stop.lno < vp->m_start.lno || + vp->m_stop.lno == vp->m_start.lno && + vp->m_stop.cno < vp->m_start.cno) { + if (ISCMD(vp->rkp, 'y') && vp->m_stop.lno == vp->m_start.lno) + vp->m_final = vp->m_start; + } else + vp->m_final = vp->m_start; +} + +/* + * v_up -- [count]^P, [count]k, [count]- + * Move up by lines. + * + * PUBLIC: int v_up __P((SCR *, VICMD *)); + */ +int +v_up(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t lno; + + lno = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + if (vp->m_start.lno <= lno) { + v_sof(sp, &vp->m_start); + return (1); + } + vp->m_stop.lno = vp->m_start.lno - lno; + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_cr -- [count]^M + * In a script window, send the line to the shell. + * In a regular window, move down by lines. + * + * PUBLIC: int v_cr __P((SCR *, VICMD *)); + */ +int +v_cr(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* If it's a colon command-line edit window, it's an ex command. */ + if (F_ISSET(sp, SC_COMEDIT)) + return (v_ecl_exec(sp)); + + /* If it's a script window, exec the line. */ + if (F_ISSET(sp, SC_SCRIPT)) + return (sscr_exec(sp, vp->m_start.lno)); + + /* Otherwise, it's the same as v_down(). */ + return (v_down(sp, vp)); +} + +/* + * v_down -- [count]^J, [count]^N, [count]j, [count]^M, [count]+ + * Move down by lines. + * + * PUBLIC: int v_down __P((SCR *, VICMD *)); + */ +int +v_down(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t lno; + + lno = vp->m_start.lno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1); + if (!db_exist(sp, lno)) { + v_eof(sp, &vp->m_start); + return (1); + } + vp->m_stop.lno = lno; + vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; + return (0); +} + +/* + * v_hpageup -- [count]^U + * Page up half screens. + * + * PUBLIC: int v_hpageup __P((SCR *, VICMD *)); + */ +int +v_hpageup(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* + * Half screens always succeed unless already at SOF. + * + * !!! + * Half screens set the scroll value, even if the command + * ultimately failed, in historic vi. Probably a don't care. + */ + if (F_ISSET(vp, VC_C1SET)) + sp->defscroll = vp->count; + if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_U)) + return (1); + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_hpagedown -- [count]^D + * Page down half screens. + * + * PUBLIC: int v_hpagedown __P((SCR *, VICMD *)); + */ +int +v_hpagedown(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* + * Half screens always succeed unless already at EOF. + * + * !!! + * Half screens set the scroll value, even if the command + * ultimately failed, in historic vi. Probably a don't care. + */ + if (F_ISSET(vp, VC_C1SET)) + sp->defscroll = vp->count; + if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_D)) + return (1); + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_pagedown -- [count]^F + * Page down full screens. + * !!! + * Historic vi did not move to the EOF if the screen couldn't move, i.e. + * if EOF was already displayed on the screen. This implementation does + * move to EOF in that case, making ^F more like the the historic ^D. + * + * PUBLIC: int v_pagedown __P((SCR *, VICMD *)); + */ +int +v_pagedown(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t offset; + + /* + * !!! + * The calculation in IEEE Std 1003.2-1992 (POSIX) is: + * + * top_line = top_line + count * (window - 2); + * + * which was historically wrong. The correct one is: + * + * top_line = top_line + count * window - 2; + * + * i.e. the two line "overlap" was only subtracted once. Which + * makes no sense, but then again, an overlap makes no sense for + * any screen but the "next" one anyway. We do it the historical + * way as there's no good reason to change it. + * + * If the screen has been split, use the smaller of the current + * window size and the window option value. + * + * It possible for this calculation to be less than 1; move at + * least one line. + */ + offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_SPLIT(sp) ? + MIN(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW)); + offset = offset <= 2 ? 1 : offset - 2; + if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_F)) + return (1); + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_pageup -- [count]^B + * Page up full screens. + * + * !!! + * Historic vi did not move to the SOF if the screen couldn't move, i.e. + * if SOF was already displayed on the screen. This implementation does + * move to SOF in that case, making ^B more like the the historic ^U. + * + * PUBLIC: int v_pageup __P((SCR *, VICMD *)); + */ +int +v_pageup(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t offset; + + /* + * !!! + * The calculation in IEEE Std 1003.2-1992 (POSIX) is: + * + * top_line = top_line - count * (window - 2); + * + * which was historically wrong. The correct one is: + * + * top_line = (top_line - count * window) + 2; + * + * A simpler expression is that, as with ^F, we scroll exactly: + * + * count * window - 2 + * + * lines. + * + * Bizarre. As with ^F, an overlap makes no sense for anything + * but the first screen. We do it the historical way as there's + * no good reason to change it. + * + * If the screen has been split, use the smaller of the current + * window size and the window option value. + * + * It possible for this calculation to be less than 1; move at + * least one line. + */ + offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_SPLIT(sp) ? + MIN(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW)); + offset = offset <= 2 ? 1 : offset - 2; + if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_B)) + return (1); + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_lineup -- [count]^Y + * Page up by lines. + * + * PUBLIC: int v_lineup __P((SCR *, VICMD *)); + */ +int +v_lineup(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* + * The cursor moves down, staying with its original line, unless it + * reaches the bottom of the screen. + */ + if (vs_sm_scroll(sp, + &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_Y)) + return (1); + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_linedown -- [count]^E + * Page down by lines. + * + * PUBLIC: int v_linedown __P((SCR *, VICMD *)); + */ +int +v_linedown(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* + * The cursor moves up, staying with its original line, unless it + * reaches the top of the screen. + */ + if (vs_sm_scroll(sp, + &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_E)) + return (1); + vp->m_final = vp->m_stop; + return (0); +} diff --git a/contrib/nvi/vi/v_search.c b/contrib/nvi/vi/v_search.c new file mode 100644 index 0000000..4f7a267 --- /dev/null +++ b/contrib/nvi/vi/v_search.c @@ -0,0 +1,515 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_search.c 10.18 (Berkeley) 9/19/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +static int v_exaddr __P((SCR *, VICMD *, dir_t)); +static int v_search __P((SCR *, VICMD *, char *, size_t, u_int, dir_t)); + +/* + * v_srch -- [count]?RE[? offset] + * Ex address search backward. + * + * PUBLIC: int v_searchb __P((SCR *, VICMD *)); + */ +int +v_searchb(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (v_exaddr(sp, vp, BACKWARD)); +} + +/* + * v_searchf -- [count]/RE[/ offset] + * Ex address search forward. + * + * PUBLIC: int v_searchf __P((SCR *, VICMD *)); + */ +int +v_searchf(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (v_exaddr(sp, vp, FORWARD)); +} + +/* + * v_exaddr -- + * Do a vi search (which is really an ex address). + */ +static int +v_exaddr(sp, vp, dir) + SCR *sp; + VICMD *vp; + dir_t dir; +{ + static EXCMDLIST fake = { "search" }; + EXCMD *cmdp; + GS *gp; + TEXT *tp; + recno_t s_lno; + size_t len, s_cno, tlen; + int err, nb, type; + char *cmd, *t, buf[20]; + + /* + * !!! + * If using the search command as a motion, any addressing components + * are lost, i.e. y/ptrn/+2, when repeated, is the same as y/ptrn/. + */ + if (F_ISSET(vp, VC_ISDOT)) + return (v_search(sp, vp, + NULL, 0, SEARCH_PARSE | SEARCH_MSG | SEARCH_SET, dir)); + + /* Get the search pattern. */ + if (v_tcmd(sp, vp, dir == BACKWARD ? CH_BSEARCH : CH_FSEARCH, + TXT_BS | TXT_CR | TXT_ESCAPE | TXT_PROMPT | + (O_ISSET(sp, O_SEARCHINCR) ? TXT_SEARCHINCR : 0))) + return (1); + + tp = sp->tiq.cqh_first; + + /* If the user backspaced over the prompt, do nothing. */ + if (tp->term == TERM_BS) + return (1); + + /* + * If the user was doing an incremental search, then we've already + * updated the cursor and moved to the right location. Return the + * correct values, we're done. + */ + if (tp->term == TERM_SEARCH) { + vp->m_stop.lno = sp->lno; + vp->m_stop.cno = sp->cno; + if (ISMOTION(vp)) + return (v_correct(sp, vp, 0)); + vp->m_final = vp->m_stop; + return (0); + } + + /* + * If the user entered <escape> or <carriage-return>, the length is + * 1 and the right thing will happen, i.e. the prompt will be used + * as a command character. + * + * Build a fake ex command structure. + */ + gp = sp->gp; + gp->excmd.cp = tp->lb; + gp->excmd.clen = tp->len; + F_INIT(&gp->excmd, E_VISEARCH); + + /* + * XXX + * Warn if the search wraps. This is a pretty special case, but it's + * nice feature that wasn't in the original implementations of ex/vi. + * (It was added at some point to System V's version.) This message + * is only displayed if there are no keys in the queue. The problem is + * the command is going to succeed, and the message is informational, + * not an error. If a macro displays it repeatedly, e.g., the pattern + * only occurs once in the file and wrapscan is set, you lose big. For + * example, if the macro does something like: + * + * :map K /pattern/^MjK + * + * Each search will display the message, but the following "/pattern/" + * will immediately overwrite it, with strange results. The System V + * vi displays the "wrapped" message multiple times, but because it's + * overwritten each time, it's not as noticeable. As we don't discard + * messages, it's a real problem for us. + */ + if (!KEYS_WAITING(sp)) + F_SET(&gp->excmd, E_SEARCH_WMSG); + + /* Save the current line/column. */ + s_lno = sp->lno; + s_cno = sp->cno; + + /* + * !!! + * Historically, vi / and ? commands were full-blown ex addresses, + * including ';' delimiters, trailing <blank>'s, multiple search + * strings (separated by semi-colons) and, finally, full-blown z + * commands after the / and ? search strings. (If the search was + * being used as a motion, the trailing z command was ignored. + * Also, we do some argument checking on the z command, to be sure + * that it's not some other random command.) For multiple search + * strings, leading <blank>'s at the second and subsequent strings + * were eaten as well. This has some (unintended?) side-effects: + * the command /ptrn/;3 is legal and results in moving to line 3. + * I suppose you could use it to optionally move to line 3... + * + * !!! + * Historically, if any part of the search command failed, the cursor + * remained unmodified (even if ; was used). We have to play games + * because the underlying ex parser thinks we're modifying the cursor + * as we go, but I think we're compatible with historic practice. + * + * !!! + * Historically, the command "/STRING/; " failed, apparently it + * confused the parser. We're not that compatible. + */ + cmdp = &gp->excmd; + if (ex_range(sp, cmdp, &err)) + return (1); + + /* + * Remember where any remaining command information is, and clean + * up the fake ex command. + */ + cmd = cmdp->cp; + len = cmdp->clen; + gp->excmd.clen = 0; + + if (err) + goto err2; + + /* Copy out the new cursor position and make sure it's okay. */ + switch (cmdp->addrcnt) { + case 1: + vp->m_stop = cmdp->addr1; + break; + case 2: + vp->m_stop = cmdp->addr2; + break; + } + if (!db_exist(sp, vp->m_stop.lno)) { + ex_badaddr(sp, &fake, + vp->m_stop.lno == 0 ? A_ZERO : A_EOF, NUM_OK); + goto err2; + } + + /* + * !!! + * Historic practice is that a trailing 'z' was ignored if it was a + * motion command. Should probably be an error, but not worth the + * effort. + */ + if (ISMOTION(vp)) + return (v_correct(sp, vp, F_ISSET(cmdp, E_DELTA))); + + /* + * !!! + * Historically, if it wasn't a motion command, a delta in the search + * pattern turns it into a first nonblank movement. + */ + nb = F_ISSET(cmdp, E_DELTA); + + /* Check for the 'z' command. */ + if (len != 0) { + if (*cmd != 'z') + goto err1; + + /* No blanks, just like the z command. */ + for (t = cmd + 1, tlen = len - 1; tlen > 0; ++t, --tlen) + if (!isdigit(*t)) + break; + if (tlen && + (*t == '-' || *t == '.' || *t == '+' || *t == '^')) { + ++t; + --tlen; + type = 1; + } else + type = 0; + if (tlen) + goto err1; + + /* The z command will do the nonblank for us. */ + nb = 0; + + /* Default to z+. */ + if (!type && + v_event_push(sp, NULL, "+", 1, CH_NOMAP | CH_QUOTED)) + return (1); + + /* Push the user's command. */ + if (v_event_push(sp, NULL, cmd, len, CH_NOMAP | CH_QUOTED)) + return (1); + + /* Push line number so get correct z display. */ + tlen = snprintf(buf, + sizeof(buf), "%lu", (u_long)vp->m_stop.lno); + if (v_event_push(sp, NULL, buf, tlen, CH_NOMAP | CH_QUOTED)) + return (1); + + /* Don't refresh until after 'z' happens. */ + F_SET(VIP(sp), VIP_S_REFRESH); + } + + /* Non-motion commands move to the end of the range. */ + vp->m_final = vp->m_stop; + if (nb) { + F_CLR(vp, VM_RCM_MASK); + F_SET(vp, VM_RCM_SETFNB); + } + return (0); + +err1: msgq(sp, M_ERR, + "188|Characters after search string, line offset and/or z command"); +err2: vp->m_final.lno = s_lno; + vp->m_final.cno = s_cno; + return (1); +} + +/* + * v_searchN -- N + * Reverse last search. + * + * PUBLIC: int v_searchN __P((SCR *, VICMD *)); + */ +int +v_searchN(sp, vp) + SCR *sp; + VICMD *vp; +{ + dir_t dir; + + switch (sp->searchdir) { + case BACKWARD: + dir = FORWARD; + break; + case FORWARD: + dir = BACKWARD; + break; + default: + dir = sp->searchdir; + break; + } + return (v_search(sp, vp, NULL, 0, SEARCH_PARSE, dir)); +} + +/* + * v_searchn -- n + * Repeat last search. + * + * PUBLIC: int v_searchn __P((SCR *, VICMD *)); + */ +int +v_searchn(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (v_search(sp, vp, NULL, 0, SEARCH_PARSE, sp->searchdir)); +} + +/* + * v_searchw -- [count]^A + * Search for the word under the cursor. + * + * PUBLIC: int v_searchw __P((SCR *, VICMD *)); + */ +int +v_searchw(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t blen, len; + int rval; + char *bp; + + len = VIP(sp)->klen + sizeof(RE_WSTART) + sizeof(RE_WSTOP); + GET_SPACE_RET(sp, bp, blen, len); + len = snprintf(bp, blen, "%s%s%s", RE_WSTART, VIP(sp)->keyw, RE_WSTOP); + + rval = v_search(sp, vp, bp, len, SEARCH_SET, FORWARD); + + FREE_SPACE(sp, bp, blen); + return (rval); +} + +/* + * v_search -- + * The search commands. + */ +static int +v_search(sp, vp, ptrn, plen, flags, dir) + SCR *sp; + VICMD *vp; + u_int flags; + char *ptrn; + size_t plen; + dir_t dir; +{ + /* Display messages. */ + LF_SET(SEARCH_MSG); + + /* If it's a motion search, offset past end-of-line is okay. */ + if (ISMOTION(vp)) + LF_SET(SEARCH_EOL); + + /* + * XXX + * Warn if the search wraps. See the comment above, in v_exaddr(). + */ + if (!KEYS_WAITING(sp)) + LF_SET(SEARCH_WMSG); + + switch (dir) { + case BACKWARD: + if (b_search(sp, + &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags)) + return (1); + break; + case FORWARD: + if (f_search(sp, + &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags)) + return (1); + break; + case NOTSET: + msgq(sp, M_ERR, "189|No previous search pattern"); + return (1); + default: + abort(); + } + + /* Correct motion commands, otherwise, simply move to the location. */ + if (ISMOTION(vp)) { + if (v_correct(sp, vp, 0)) + return(1); + } else + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_correct -- + * Handle command with a search as the motion. + * + * !!! + * Historically, commands didn't affect the line searched to/from if the + * motion command was a search and the final position was the start/end + * of the line. There were some special cases and vi was not consistent; + * it was fairly easy to confuse it. For example, given the two lines: + * + * abcdefghi + * ABCDEFGHI + * + * placing the cursor on the 'A' and doing y?$ would so confuse it that 'h' + * 'k' and put would no longer work correctly. In any case, we try to do + * the right thing, but it's not going to exactly match historic practice. + * + * PUBLIC: int v_correct __P((SCR *, VICMD *, int)); + */ +int +v_correct(sp, vp, isdelta) + SCR *sp; + VICMD *vp; + int isdelta; +{ + dir_t dir; + MARK m; + size_t len; + + /* + * !!! + * We may have wrapped if wrapscan was set, and we may have returned + * to the position where the cursor started. Historic vi didn't cope + * with this well. Yank wouldn't beep, but the first put after the + * yank would move the cursor right one column (without adding any + * text) and the second would put a copy of the current line. The + * change and delete commands would beep, but would leave the cursor + * on the colon command line. I believe that there are macros that + * depend on delete, at least, failing. For now, commands that use + * search as a motion component fail when the search returns to the + * original cursor position. + */ + if (vp->m_start.lno == vp->m_stop.lno && + vp->m_start.cno == vp->m_stop.cno) { + msgq(sp, M_BERR, "190|Search wrapped to original position"); + return (1); + } + + /* + * !!! + * Searches become line mode operations if there was a delta specified + * to the search pattern. + */ + if (isdelta) + F_SET(vp, VM_LMODE); + + /* + * If the motion is in the reverse direction, switch the start and + * stop MARK's so that it's in a forward direction. (There's no + * reason for this other than to make the tests below easier. The + * code in vi.c:vi() would have done the switch.) Both forward + * and backward motions can happen for any kind of search command + * because of the wrapscan option. + */ + if (vp->m_start.lno > vp->m_stop.lno || + vp->m_start.lno == vp->m_stop.lno && + vp->m_start.cno > vp->m_stop.cno) { + m = vp->m_start; + vp->m_start = vp->m_stop; + vp->m_stop = m; + dir = BACKWARD; + } else + dir = FORWARD; + + /* + * BACKWARD: + * Delete and yank commands move to the end of the range. + * Ignore others. + * + * FORWARD: + * Delete and yank commands don't move. Ignore others. + */ + vp->m_final = vp->m_start; + + /* + * !!! + * Delta'd searches don't correct based on column positions. + */ + if (isdelta) + return (0); + + /* + * !!! + * Backward searches starting at column 0, and forward searches ending + * at column 0 are corrected to the last column of the previous line. + * Otherwise, adjust the starting/ending point to the character before + * the current one (this is safe because we know the search had to move + * to succeed). + * + * Searches become line mode operations if they start at the first + * nonblank and end at column 0 of another line. + */ + if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) { + if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) + return (1); + vp->m_stop.cno = len ? len - 1 : 0; + len = 0; + if (nonblank(sp, vp->m_start.lno, &len)) + return (1); + if (vp->m_start.cno <= len) + F_SET(vp, VM_LMODE); + } else + --vp->m_stop.cno; + + return (0); +} diff --git a/contrib/nvi/vi/v_section.c b/contrib/nvi/vi/v_section.c new file mode 100644 index 0000000..20e8ff2 --- /dev/null +++ b/contrib/nvi/vi/v_section.c @@ -0,0 +1,252 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_section.c 10.7 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * !!! + * In historic vi, the section commands ignored empty lines, unlike the + * paragraph commands, which was probably okay. However, they also moved + * to the start of the last line when there where no more sections instead + * of the end of the last line like the paragraph commands. I've changed + * the latter behavior to match the paragraph commands. + * + * In historic vi, a section was defined as the first character(s) of the + * line matching, which could be followed by anything. This implementation + * follows that historic practice. + * + * !!! + * The historic vi documentation (USD:15-10) claimed: + * The section commands interpret a preceding count as a different + * window size in which to redraw the screen at the new location, + * and this window size is the base size for newly drawn windows + * until another size is specified. This is very useful if you are + * on a slow terminal ... + * + * I can't get the 4BSD vi to do this, it just beeps at me. For now, a + * count to the section commands simply repeats the command. + */ + +/* + * v_sectionf -- [count]]] + * Move forward count sections/functions. + * + * !!! + * Using ]] as a motion command was a bit special, historically. It could + * match } as well as the usual { and section values. If it matched a { or + * a section, it did NOT include the matched line. If it matched a }, it + * did include the line. No clue why. + * + * PUBLIC: int v_sectionf __P((SCR *, VICMD *)); + */ +int +v_sectionf(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t cnt, lno; + size_t len; + char *p, *list, *lp; + + /* Get the macro list. */ + if ((list = O_STR(sp, O_SECTIONS)) == NULL) + return (1); + + /* + * !!! + * If the starting cursor position is at or before any non-blank + * characters in the line, i.e. the movement is cutting all of the + * line's text, the buffer is in line mode. It's a lot easier to + * check here, because we know that the end is going to be the start + * or end of a line. + */ + if (ISMOTION(vp)) + if (vp->m_start.cno == 0) + F_SET(vp, VM_LMODE); + else { + vp->m_stop = vp->m_start; + vp->m_stop.cno = 0; + if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) + return (1); + if (vp->m_start.cno <= vp->m_stop.cno) + F_SET(vp, VM_LMODE); + } + + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + for (lno = vp->m_start.lno; !db_get(sp, ++lno, 0, &p, &len);) { + if (len == 0) + continue; + if (p[0] == '{' || ISMOTION(vp) && p[0] == '}') { + if (!--cnt) { + if (p[0] == '{') + goto adjust1; + goto adjust2; + } + continue; + } + /* + * !!! + * Historic documentation (USD:15-11, 4.2) said that formfeed + * characters (^L) in the first column delimited sections. + * The historic code mentions formfeed characters, but never + * implements them. Seems reasonable, do it. + */ + if (p[0] == '\014') { + if (!--cnt) + goto adjust1; + continue; + } + if (p[0] != '.' || len < 2) + continue; + for (lp = list; *lp != '\0'; lp += 2 * sizeof(*lp)) + if (lp[0] == p[1] && + (lp[1] == ' ' && len == 2 || lp[1] == p[2]) && + !--cnt) { + /* + * !!! + * If not cutting this line, adjust to the end + * of the previous one. Otherwise, position to + * column 0. + */ +adjust1: if (ISMOTION(vp)) + goto ret1; + +adjust2: vp->m_stop.lno = lno; + vp->m_stop.cno = 0; + goto ret2; + } + } + + /* If moving forward, reached EOF, check to see if we started there. */ + if (vp->m_start.lno == lno - 1) { + v_eof(sp, NULL); + return (1); + } + +ret1: if (db_get(sp, --lno, DBG_FATAL, NULL, &len)) + return (1); + vp->m_stop.lno = lno; + vp->m_stop.cno = len ? len - 1 : 0; + + /* + * Non-motion commands go to the end of the range. Delete and + * yank stay at the start of the range. Ignore others. + */ +ret2: if (ISMOTION(vp)) { + vp->m_final = vp->m_start; + if (F_ISSET(vp, VM_LMODE)) + vp->m_final.cno = 0; + } else + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_sectionb -- [count][[ + * Move backward count sections/functions. + * + * PUBLIC: int v_sectionb __P((SCR *, VICMD *)); + */ +int +v_sectionb(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + recno_t cnt, lno; + char *p, *list, *lp; + + /* An empty file or starting from line 1 is always illegal. */ + if (vp->m_start.lno <= 1) { + v_sof(sp, NULL); + return (1); + } + + /* Get the macro list. */ + if ((list = O_STR(sp, O_SECTIONS)) == NULL) + return (1); + + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + for (lno = vp->m_start.lno; !db_get(sp, --lno, 0, &p, &len);) { + if (len == 0) + continue; + if (p[0] == '{') { + if (!--cnt) + goto adjust1; + continue; + } + /* + * !!! + * Historic documentation (USD:15-11, 4.2) said that formfeed + * characters (^L) in the first column delimited sections. + * The historic code mentions formfeed characters, but never + * implements them. Seems reasonable, do it. + */ + if (p[0] == '\014') { + if (!--cnt) + goto adjust1; + continue; + } + if (p[0] != '.' || len < 2) + continue; + for (lp = list; *lp != '\0'; lp += 2 * sizeof(*lp)) + if (lp[0] == p[1] && + (lp[1] == ' ' && len == 2 || lp[1] == p[2]) && + !--cnt) { +adjust1: vp->m_stop.lno = lno; + vp->m_stop.cno = 0; + goto ret1; + } + } + + /* + * If moving backward, reached SOF, which is a movement sink. + * We already checked for starting there. + */ + vp->m_stop.lno = 1; + vp->m_stop.cno = 0; + + /* + * All commands move to the end of the range. + * + * !!! + * Historic practice is the section cut was in line mode if it started + * from column 0 and was in the backward direction. Otherwise, left + * motion commands adjust the starting point to the character before + * the current one. What makes this worse is that if it cut to line + * mode it also went to the first non-<blank>. + */ +ret1: if (vp->m_start.cno == 0) { + F_CLR(vp, VM_RCM_MASK); + F_SET(vp, VM_RCM_SETFNB); + + --vp->m_start.lno; + F_SET(vp, VM_LMODE); + } else + --vp->m_start.cno; + + vp->m_final = vp->m_stop; + return (0); +} diff --git a/contrib/nvi/vi/v_sentence.c b/contrib/nvi/vi/v_sentence.c new file mode 100644 index 0000000..a3d9376 --- /dev/null +++ b/contrib/nvi/vi/v_sentence.c @@ -0,0 +1,359 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_sentence.c 10.7 (Berkeley) 3/6/96"; +#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 <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * !!! + * In historic vi, a sentence was delimited by a '.', '?' or '!' character + * followed by TWO spaces or a newline. One or more empty lines was also + * treated as a separate sentence. The Berkeley documentation for historical + * vi states that any number of ')', ']', '"' and '\'' characters can be + * between the delimiter character and the spaces or end of line, however, + * the historical implementation did not handle additional '"' characters. + * We follow the documentation here, not the implementation. + * + * Once again, historical vi didn't do sentence movements associated with + * counts consistently, mostly in the presence of lines containing only + * white-space characters. + * + * This implementation also permits a single tab to delimit sentences, and + * treats lines containing only white-space characters as empty lines. + * Finally, tabs are eaten (along with spaces) when skipping to the start + * of the text following a "sentence". + */ + +/* + * v_sentencef -- [count]) + * Move forward count sentences. + * + * PUBLIC: int v_sentencef __P((SCR *, VICMD *)); + */ +int +v_sentencef(sp, vp) + SCR *sp; + VICMD *vp; +{ + enum { BLANK, NONE, PERIOD } state; + VCS cs; + size_t len; + u_long cnt; + + cs.cs_lno = vp->m_start.lno; + cs.cs_cno = vp->m_start.cno; + if (cs_init(sp, &cs)) + return (1); + + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + + /* + * !!! + * If in white-space, the next start of sentence counts as one. + * This may not handle " . " correctly, but it's real unclear + * what correctly means in that case. + */ + if (cs.cs_flags == CS_EMP || cs.cs_flags == 0 && isblank(cs.cs_ch)) { + if (cs_fblank(sp, &cs)) + return (1); + if (--cnt == 0) { + if (vp->m_start.lno != cs.cs_lno || + vp->m_start.cno != cs.cs_cno) + goto okret; + return (1); + } + } + + for (state = NONE;;) { + if (cs_next(sp, &cs)) + return (1); + if (cs.cs_flags == CS_EOF) + break; + if (cs.cs_flags == CS_EOL) { + if ((state == PERIOD || state == BLANK) && --cnt == 0) { + if (cs_next(sp, &cs)) + return (1); + if (cs.cs_flags == 0 && + isblank(cs.cs_ch) && cs_fblank(sp, &cs)) + return (1); + goto okret; + } + state = NONE; + continue; + } + if (cs.cs_flags == CS_EMP) { /* An EMP is two sentences. */ + if (--cnt == 0) + goto okret; + if (cs_fblank(sp, &cs)) + return (1); + if (--cnt == 0) + goto okret; + state = NONE; + continue; + } + switch (cs.cs_ch) { + case '.': + case '?': + case '!': + state = PERIOD; + break; + case ')': + case ']': + case '"': + case '\'': + if (state != PERIOD) + state = NONE; + break; + case '\t': + if (state == PERIOD) + state = BLANK; + /* FALLTHROUGH */ + case ' ': + if (state == PERIOD) { + state = BLANK; + break; + } + if (state == BLANK && --cnt == 0) { + if (cs_fblank(sp, &cs)) + return (1); + goto okret; + } + /* FALLTHROUGH */ + default: + state = NONE; + break; + } + } + + /* EOF is a movement sink, but it's an error not to have moved. */ + if (vp->m_start.lno == cs.cs_lno && vp->m_start.cno == cs.cs_cno) { + v_eof(sp, NULL); + return (1); + } + +okret: vp->m_stop.lno = cs.cs_lno; + vp->m_stop.cno = cs.cs_cno; + + /* + * !!! + * Historic, uh, features, yeah, that's right, call 'em features. + * If the starting and ending cursor positions are at the first + * column in their lines, i.e. the movement is cutting entire lines, + * the buffer is in line mode, and the ending position is the last + * character of the previous line. Note check to make sure that + * it's not within a single line. + * + * Non-motion commands move to the end of the range. Delete and + * yank stay at the start. Ignore others. Adjust the end of the + * range for motion commands. + */ + if (ISMOTION(vp)) { + if (vp->m_start.cno == 0 && + (cs.cs_flags != 0 || vp->m_stop.cno == 0)) { + if (vp->m_start.lno < vp->m_stop.lno) { + if (db_get(sp, + --vp->m_stop.lno, DBG_FATAL, NULL, &len)) + return (1); + vp->m_stop.cno = len ? len - 1 : 0; + } + F_SET(vp, VM_LMODE); + } else + --vp->m_stop.cno; + vp->m_final = vp->m_start; + } else + vp->m_final = vp->m_stop; + return (0); +} + +/* + * v_sentenceb -- [count]( + * Move backward count sentences. + * + * PUBLIC: int v_sentenceb __P((SCR *, VICMD *)); + */ +int +v_sentenceb(sp, vp) + SCR *sp; + VICMD *vp; +{ + VCS cs; + recno_t slno; + size_t len, scno; + u_long cnt; + int last; + + /* + * !!! + * Historic vi permitted the user to hit SOF repeatedly. + */ + if (vp->m_start.lno == 1 && vp->m_start.cno == 0) + return (0); + + cs.cs_lno = vp->m_start.lno; + cs.cs_cno = vp->m_start.cno; + if (cs_init(sp, &cs)) + return (1); + + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + + /* + * !!! + * In empty lines, skip to the previous non-white-space character. + * If in text, skip to the prevous white-space character. Believe + * it or not, in the paragraph: + * ab cd. + * AB CD. + * if the cursor is on the 'A' or 'B', ( moves to the 'a'. If it + * is on the ' ', 'C' or 'D', it moves to the 'A'. Yes, Virginia, + * Berkeley was once a major center of drug activity. + */ + if (cs.cs_flags == CS_EMP) { + if (cs_bblank(sp, &cs)) + return (1); + for (;;) { + if (cs_prev(sp, &cs)) + return (1); + if (cs.cs_flags != CS_EOL) + break; + } + } else if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) + for (;;) { + if (cs_prev(sp, &cs)) + return (1); + if (cs.cs_flags != 0 || isblank(cs.cs_ch)) + break; + } + + for (last = 0;;) { + if (cs_prev(sp, &cs)) + return (1); + if (cs.cs_flags == CS_SOF) /* SOF is a movement sink. */ + break; + if (cs.cs_flags == CS_EOL) { + last = 1; + continue; + } + if (cs.cs_flags == CS_EMP) { + if (--cnt == 0) + goto ret; + if (cs_bblank(sp, &cs)) + return (1); + last = 0; + continue; + } + switch (cs.cs_ch) { + case '.': + case '?': + case '!': + if (!last || --cnt != 0) { + last = 0; + continue; + } + +ret: slno = cs.cs_lno; + scno = cs.cs_cno; + + /* + * Move to the start of the sentence, skipping blanks + * and special characters. + */ + do { + if (cs_next(sp, &cs)) + return (1); + } while (!cs.cs_flags && + (cs.cs_ch == ')' || cs.cs_ch == ']' || + cs.cs_ch == '"' || cs.cs_ch == '\'')); + if ((cs.cs_flags || isblank(cs.cs_ch)) && + cs_fblank(sp, &cs)) + return (1); + + /* + * If it was ". xyz", with the cursor on the 'x', or + * "end. ", with the cursor in the spaces, or the + * beginning of a sentence preceded by an empty line, + * we can end up where we started. Fix it. + */ + if (vp->m_start.lno != cs.cs_lno || + vp->m_start.cno != cs.cs_cno) + goto okret; + + /* + * Well, if an empty line preceded possible blanks + * and the sentence, it could be a real sentence. + */ + for (;;) { + if (cs_prev(sp, &cs)) + return (1); + if (cs.cs_flags == CS_EOL) + continue; + if (cs.cs_flags == 0 && isblank(cs.cs_ch)) + continue; + break; + } + if (cs.cs_flags == CS_EMP) + goto okret; + + /* But it wasn't; try again. */ + ++cnt; + cs.cs_lno = slno; + cs.cs_cno = scno; + last = 0; + break; + case '\t': + last = 1; + break; + default: + last = + cs.cs_flags == CS_EOL || isblank(cs.cs_ch) || + cs.cs_ch == ')' || cs.cs_ch == ']' || + cs.cs_ch == '"' || cs.cs_ch == '\'' ? 1 : 0; + } + } + +okret: vp->m_stop.lno = cs.cs_lno; + vp->m_stop.cno = cs.cs_cno; + + /* + * !!! + * If the starting and stopping cursor positions are at the first + * columns in the line, i.e. the movement is cutting an entire line, + * the buffer is in line mode, and the starting position is the last + * character of the previous line. + * + * All commands move to the end of the range. Adjust the start of + * the range for motion commands. + */ + if (ISMOTION(vp)) + if (vp->m_start.cno == 0 && + (cs.cs_flags != 0 || vp->m_stop.cno == 0)) { + if (db_get(sp, + --vp->m_start.lno, DBG_FATAL, NULL, &len)) + return (1); + vp->m_start.cno = len ? len - 1 : 0; + F_SET(vp, VM_LMODE); + } else + --vp->m_start.cno; + vp->m_final = vp->m_stop; + return (0); +} diff --git a/contrib/nvi/vi/v_status.c b/contrib/nvi/vi/v_status.c new file mode 100644 index 0000000..7095d78 --- /dev/null +++ b/contrib/nvi/vi/v_status.c @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_status.c 10.9 (Berkeley) 5/15/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_status -- ^G + * Show the file status. + * + * PUBLIC: int v_status __P((SCR *, VICMD *)); + */ +int +v_status(sp, vp) + SCR *sp; + VICMD *vp; +{ + (void)msgq_status(sp, vp->m_start.lno, MSTAT_SHOWLAST); + return (0); +} diff --git a/contrib/nvi/vi/v_txt.c b/contrib/nvi/vi/v_txt.c new file mode 100644 index 0000000..c47a485 --- /dev/null +++ b/contrib/nvi/vi/v_txt.c @@ -0,0 +1,2950 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_txt.c 10.87 (Berkeley) 10/13/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "vi.h" + +static int txt_abbrev __P((SCR *, TEXT *, CHAR_T *, int, int *, int *)); +static void txt_ai_resolve __P((SCR *, TEXT *, int *)); +static TEXT *txt_backup __P((SCR *, TEXTH *, TEXT *, u_int32_t *)); +static int txt_dent __P((SCR *, TEXT *, int)); +static int txt_emark __P((SCR *, TEXT *, size_t)); +static void txt_err __P((SCR *, TEXTH *)); +static int txt_fc __P((SCR *, TEXT *, int *)); +static int txt_fc_col __P((SCR *, int, ARGS **)); +static int txt_hex __P((SCR *, TEXT *)); +static int txt_insch __P((SCR *, TEXT *, CHAR_T *, u_int)); +static int txt_isrch __P((SCR *, VICMD *, TEXT *, u_int8_t *)); +static int txt_map_end __P((SCR *)); +static int txt_map_init __P((SCR *)); +static int txt_margin __P((SCR *, TEXT *, TEXT *, int *, u_int32_t)); +static void txt_nomorech __P((SCR *)); +static void txt_Rresolve __P((SCR *, TEXTH *, TEXT *, const size_t)); +static int txt_resolve __P((SCR *, TEXTH *, u_int32_t)); +static int txt_showmatch __P((SCR *, TEXT *)); +static void txt_unmap __P((SCR *, TEXT *, u_int32_t *)); + +/* Cursor character (space is hard to track on the screen). */ +#if defined(DEBUG) && 0 +#undef CH_CURSOR +#define CH_CURSOR '+' +#endif + +/* + * v_tcmd -- + * Fill a buffer from the terminal for vi. + * + * PUBLIC: int v_tcmd __P((SCR *, VICMD *, ARG_CHAR_T, u_int)); + */ +int +v_tcmd(sp, vp, prompt, flags) + SCR *sp; + VICMD *vp; + ARG_CHAR_T prompt; + u_int flags; +{ + /* Normally, we end up where we started. */ + vp->m_final.lno = sp->lno; + vp->m_final.cno = sp->cno; + + /* Initialize the map. */ + if (txt_map_init(sp)) + return (1); + + /* Move to the last line. */ + sp->lno = TMAP[0].lno; + sp->cno = 0; + + /* Don't update the modeline for now. */ + F_SET(sp, SC_TINPUT_INFO); + + /* Set the input flags. */ + LF_SET(TXT_APPENDEOL | + TXT_CR | TXT_ESCAPE | TXT_INFOLINE | TXT_MAPINPUT); + if (O_ISSET(sp, O_ALTWERASE)) + LF_SET(TXT_ALTWERASE); + if (O_ISSET(sp, O_TTYWERASE)) + LF_SET(TXT_TTYWERASE); + + /* Do the input thing. */ + if (v_txt(sp, vp, NULL, NULL, 0, prompt, 0, 1, flags)) + return (1); + + /* Reenable the modeline updates. */ + F_CLR(sp, SC_TINPUT_INFO); + + /* Clean up the map. */ + if (txt_map_end(sp)) + return (1); + + if (IS_ONELINE(sp)) + F_SET(sp, SC_SCR_REDRAW); /* XXX */ + + /* Set the cursor to the resulting position. */ + sp->lno = vp->m_final.lno; + sp->cno = vp->m_final.cno; + + return (0); +} + +/* + * txt_map_init + * Initialize the screen map for colon command-line input. + */ +static int +txt_map_init(sp) + SCR *sp; +{ + SMAP *esmp; + VI_PRIVATE *vip; + + vip = VIP(sp); + if (!IS_ONELINE(sp)) { + /* + * 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. + */ + vip->sv_tm_lno = TMAP->lno; + vip->sv_tm_soff = TMAP->soff; + vip->sv_tm_coff = TMAP->coff; + vip->sv_t_maxrows = sp->t_maxrows; + vip->sv_t_minrows = sp->t_minrows; + vip->sv_t_rows = sp->t_rows; + + /* + * If it's a small screen, TMAP may be small for the screen. + * Fix it, filling in fake lines as we go. + */ + if (IS_SMALL(sp)) + for (esmp = + HMAP + (sp->t_maxrows - 1); TMAP < esmp; ++TMAP) { + TMAP[1].lno = TMAP[0].lno + 1; + TMAP[1].coff = HMAP->coff; + TMAP[1].soff = 1; + } + + /* Build the fake entry. */ + TMAP[1].lno = TMAP[0].lno + 1; + TMAP[1].soff = 1; + TMAP[1].coff = 0; + SMAP_FLUSH(&TMAP[1]); + ++TMAP; + + /* Reset the screen information. */ + sp->t_rows = sp->t_minrows = ++sp->t_maxrows; + } + return (0); +} + +/* + * txt_map_end + * Reset the screen map for colon command-line input. + */ +static int +txt_map_end(sp) + SCR *sp; +{ + VI_PRIVATE *vip; + size_t cnt; + + vip = VIP(sp); + if (!IS_ONELINE(sp)) { + /* Restore the screen information. */ + sp->t_rows = vip->sv_t_rows; + sp->t_minrows = vip->sv_t_minrows; + sp->t_maxrows = vip->sv_t_maxrows; + + /* + * If it's a small screen, TMAP may be wrong. Clear any + * lines that might have been overwritten. + */ + if (IS_SMALL(sp)) { + for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) { + (void)sp->gp->scr_move(sp, cnt, 0); + (void)sp->gp->scr_clrtoeol(sp); + } + TMAP = HMAP + (sp->t_rows - 1); + } else + --TMAP; + + /* + * 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 we probably don't care. + */ + if (!O_ISSET(sp, O_LEFTRIGHT)) + while (vip->sv_tm_lno != TMAP->lno || + vip->sv_tm_soff != TMAP->soff) + if (vs_sm_1down(sp)) + return (1); + } + + /* + * 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. + */ + VI_SCR_CFLUSH(vip); + F_SET(vip, VIP_CUR_INVALID); + + return (0); +} + +/* + * If doing input mapping on the colon command line, may need to unmap + * based on the command. + */ +#define UNMAP_TST \ + FL_ISSET(ec_flags, EC_MAPINPUT) && LF_ISSET(TXT_INFOLINE) + +/* + * Internally, we maintain tp->lno and tp->cno, externally, everyone uses + * sp->lno and sp->cno. Make them consistent as necessary. + */ +#define UPDATE_POSITION(sp, tp) { \ + (sp)->lno = (tp)->lno; \ + (sp)->cno = (tp)->cno; \ +} + +/* + * v_txt -- + * Vi text input. + * + * PUBLIC: int v_txt __P((SCR *, VICMD *, MARK *, + * PUBLIC: const char *, size_t, ARG_CHAR_T, recno_t, u_long, u_int32_t)); + */ +int +v_txt(sp, vp, tm, lp, len, prompt, ai_line, rcount, flags) + SCR *sp; + VICMD *vp; + MARK *tm; /* To MARK. */ + const char *lp; /* Input line. */ + size_t len; /* Input line length. */ + ARG_CHAR_T prompt; /* Prompt to display. */ + recno_t ai_line; /* Line number to use for autoindent count. */ + u_long rcount; /* Replay count. */ + u_int32_t flags; /* TXT_* flags. */ +{ + EVENT ev, *evp; /* Current event. */ + EVENT fc; /* File name completion event. */ + GS *gp; + TEXT *ntp, *tp; /* Input text structures. */ + TEXT ait; /* Autoindent text structure. */ + TEXT wmt; /* Wrapmargin text structure. */ + TEXTH *tiqh; + VI_PRIVATE *vip; + abb_t abb; /* State of abbreviation checks. */ + carat_t carat; /* State of the "[^0]^D" sequences. */ + quote_t quote; /* State of quotation. */ + size_t owrite, insert; /* Temporary copies of TEXT fields. */ + size_t margin; /* Wrapmargin value. */ + size_t rcol; /* 0-N: insert offset in the replay buffer. */ + size_t tcol; /* Temporary column. */ + u_int32_t ec_flags; /* Input mapping flags. */ +#define IS_RESTART 0x01 /* Reset the incremental search. */ +#define IS_RUNNING 0x02 /* Incremental search turned on. */ + u_int8_t is_flags; + int abcnt, ab_turnoff; /* Abbreviation character count, switch. */ + int filec_redraw; /* Redraw after the file completion routine. */ + int hexcnt; /* Hex character count. */ + int showmatch; /* Showmatch set on this character. */ + int wm_set, wm_skip; /* Wrapmargin happened, blank skip flags. */ + int max, tmp; + char *p; + + gp = sp->gp; + vip = VIP(sp); + + /* + * Set the input flag, so tabs get displayed correctly + * and everyone knows that the text buffer is in use. + */ + F_SET(sp, SC_TINPUT); + + /* + * Get one TEXT structure with some initial buffer space, reusing + * the last one if it's big enough. (All TEXT bookkeeping fields + * default to 0 -- text_init() handles this.) If changing a line, + * copy it into the TEXT buffer. + */ + tiqh = &sp->tiq; + if (tiqh->cqh_first != (void *)tiqh) { + tp = tiqh->cqh_first; + if (tp->q.cqe_next != (void *)tiqh || tp->lb_len < len + 32) { + text_lfree(tiqh); + goto newtp; + } + tp->ai = tp->insert = tp->offset = tp->owrite = 0; + if (lp != NULL) { + tp->len = len; + memmove(tp->lb, lp, len); + } else + tp->len = 0; + } else { +newtp: if ((tp = text_init(sp, lp, len, len + 32)) == NULL) + return (1); + CIRCLEQ_INSERT_HEAD(tiqh, tp, q); + } + + /* Set default termination condition. */ + tp->term = TERM_OK; + + /* Set the starting line, column. */ + tp->lno = sp->lno; + tp->cno = sp->cno; + + /* + * Set the insert and overwrite counts. If overwriting characters, + * do insertion afterward. If not overwriting characters, assume + * doing insertion. If change is to a mark, emphasize it with an + * CH_ENDMARK character. + */ + if (len) { + if (LF_ISSET(TXT_OVERWRITE)) { + tp->owrite = (tm->cno - tp->cno) + 1; + tp->insert = (len - tm->cno) - 1; + } else + tp->insert = len - tp->cno; + + if (LF_ISSET(TXT_EMARK) && txt_emark(sp, tp, tm->cno)) + return (1); + } + + /* + * Many of the special cases in text input are to handle autoindent + * support. Somebody decided that it would be a good idea if "^^D" + * and "0^D" deleted all of the autoindented characters. In an editor + * that takes single character input from the user, this beggars the + * imagination. Note also, "^^D" resets the next lines' autoindent, + * but "0^D" doesn't. + * + * We assume that autoindent only happens on empty lines, so insert + * and overwrite will be zero. If doing autoindent, figure out how + * much indentation we need and fill it in. Update input column and + * screen cursor as necessary. + */ + if (LF_ISSET(TXT_AUTOINDENT) && ai_line != OOBLNO) { + if (v_txt_auto(sp, ai_line, NULL, 0, tp)) + return (1); + tp->cno = tp->ai; + } else { + /* + * The cc and S commands have a special feature -- leading + * <blank> characters are handled as autoindent characters. + * Beauty! + */ + if (LF_ISSET(TXT_AICHARS)) { + tp->offset = 0; + tp->ai = tp->cno; + } else + tp->offset = tp->cno; + } + + /* If getting a command buffer from the user, there may be a prompt. */ + if (LF_ISSET(TXT_PROMPT)) { + tp->lb[tp->cno++] = prompt; + ++tp->len; + ++tp->offset; + } + + /* + * If appending after the end-of-line, add a space into the buffer + * and move the cursor right. This space is inserted, i.e. pushed + * along, and then deleted when the line is resolved. Assumes that + * the cursor is already positioned at the end of the line. This + * avoids the nastiness of having the cursor reside on a magical + * column, i.e. a column that doesn't really exist. The only down + * side is that we may wrap lines or scroll the screen before it's + * strictly necessary. Not a big deal. + */ + if (LF_ISSET(TXT_APPENDEOL)) { + tp->lb[tp->cno] = CH_CURSOR; + ++tp->len; + ++tp->insert; + (void)vs_change(sp, tp->lno, LINE_RESET); + } + + /* + * Historic practice is that the wrapmargin value was a distance + * from the RIGHT-HAND margin, not the left. It's more useful to + * us as a distance from the left-hand margin, i.e. the same as + * the wraplen value. The wrapmargin option is historic practice. + * Nvi added the wraplen option so that it would be possible to + * edit files with consistent margins without knowing the number of + * columns in the window. + * + * XXX + * Setting margin causes a significant performance hit. Normally + * we don't update the screen if there are keys waiting, but we + * have to if margin is set, otherwise the screen routines don't + * know where the cursor is. + * + * !!! + * Abbreviated keys were affected by the wrapmargin option in the + * historic 4BSD vi. Mapped keys were usually, but sometimes not. + * See the comment in vi/v_text():set_txt_std for more information. + * + * !!! + * One more special case. If an inserted <blank> character causes + * wrapmargin to split the line, the next user entered character is + * discarded if it's a <space> character. + */ + wm_set = wm_skip = 0; + if (LF_ISSET(TXT_WRAPMARGIN)) + if ((margin = O_VAL(sp, O_WRAPMARGIN)) != 0) + margin = sp->cols - margin; + else + margin = O_VAL(sp, O_WRAPLEN); + else + margin = 0; + + /* Initialize abbreviation checks. */ + abcnt = ab_turnoff = 0; + abb = F_ISSET(gp, G_ABBREV) && + LF_ISSET(TXT_MAPINPUT) ? AB_INWORD : AB_NOTSET; + + /* + * Set up the dot command. Dot commands are done by saving the actual + * characters and then reevaluating them so that things like wrapmargin + * can change between the insert and the replay. + * + * !!! + * Historically, vi did not remap or reabbreviate replayed input. (It + * did beep at you if you changed an abbreviation and then replayed the + * input. We're not that compatible.) We don't have to do anything to + * avoid remapping, as we're not getting characters from the terminal + * routines. Turn the abbreviation check off. + * + * XXX + * It would be nice if we could swallow backspaces and such, but it's + * not all that easy to do. What we can do is turn off the common + * error messages during the replay. Otherwise, when the user enters + * an illegal command, e.g., "Ia<erase><erase><erase><erase>b<escape>", + * and then does a '.', they get a list of error messages after command + * completion. + */ + rcol = 0; + if (LF_ISSET(TXT_REPLAY)) { + abb = AB_NOTSET; + LF_CLR(TXT_RECORD); + } + + /* Other text input mode setup. */ + quote = Q_NOTSET; + carat = C_NOTSET; + FL_INIT(is_flags, + LF_ISSET(TXT_SEARCHINCR) ? IS_RESTART | IS_RUNNING : 0); + filec_redraw = hexcnt = showmatch = 0; + + /* Initialize input flags. */ + ec_flags = LF_ISSET(TXT_MAPINPUT) ? EC_MAPINPUT : 0; + + /* Refresh the screen. */ + UPDATE_POSITION(sp, tp); + if (vs_refresh(sp, 1)) + return (1); + + /* If it's dot, just do it now. */ + if (F_ISSET(vp, VC_ISDOT)) + goto replay; + + /* Get an event. */ + evp = &ev; +next: if (v_event_get(sp, evp, 0, ec_flags)) + return (1); + + /* + * If file completion overwrote part of the screen and nothing else has + * been displayed, clean up. We don't do this as part of the normal + * message resolution because we know the user is on the colon command + * line and there's no reason to enter explicit characters to continue. + */ + if (filec_redraw && !F_ISSET(sp, SC_SCR_EXWROTE)) { + filec_redraw = 0; + + fc.e_event = E_REPAINT; + fc.e_flno = vip->totalcount >= + sp->rows ? 1 : sp->rows - vip->totalcount; + fc.e_tlno = sp->rows; + vip->linecount = vip->lcontinue = vip->totalcount = 0; + (void)vs_repaint(sp, &fc); + (void)vs_refresh(sp, 1); + } + + /* Deal with all non-character events. */ + switch (evp->e_event) { + case E_CHARACTER: + break; + case E_ERR: + case E_EOF: + F_SET(sp, SC_EXIT_FORCE); + return (1); + case E_INTERRUPT: + /* + * !!! + * Historically, <interrupt> exited the user from text input + * mode or cancelled a colon command, and returned to command + * mode. It also beeped the terminal, but that seems a bit + * excessive. + */ + goto k_escape; + case E_REPAINT: + if (vs_repaint(sp, &ev)) + return (1); + goto next; + case E_WRESIZE: + /* <resize> interrupts the input mode. */ + v_emsg(sp, NULL, VIM_WRESIZE); + goto k_escape; + default: + v_event_err(sp, evp); + goto k_escape; + } + + /* + * !!! + * If the first character of the input is a nul, replay the previous + * input. (Historically, it's okay to replay non-existent input.) + * This was not documented as far as I know, and is a great test of vi + * clones. + */ + if (rcol == 0 && !LF_ISSET(TXT_REPLAY) && evp->e_c == '\0') { + if (vip->rep == NULL) + goto done; + + abb = AB_NOTSET; + LF_CLR(TXT_RECORD); + LF_SET(TXT_REPLAY); + goto replay; + } + + /* + * File name completion and colon command-line editing. We don't + * have enough meta characters, so we expect people to overload + * them. If the two characters are the same, then we do file name + * completion if the cursor is past the first column, and do colon + * command-line editing if it's not. + */ + if (quote == Q_NOTSET) { + int L__cedit, L__filec; + + L__cedit = L__filec = 0; + if (LF_ISSET(TXT_CEDIT) && O_STR(sp, O_CEDIT) != NULL && + O_STR(sp, O_CEDIT)[0] == evp->e_c) + L__cedit = 1; + if (LF_ISSET(TXT_FILEC) && O_STR(sp, O_FILEC) != NULL && + O_STR(sp, O_FILEC)[0] == evp->e_c) + L__filec = 1; + if (L__cedit == 1 && (L__filec == 0 || tp->cno == tp->offset)) { + tp->term = TERM_CEDIT; + goto k_escape; + } + if (L__filec == 1) { + if (txt_fc(sp, tp, &filec_redraw)) + goto err; + goto resolve; + } + } + + /* Abbreviation overflow check. See comment in txt_abbrev(). */ +#define MAX_ABBREVIATION_EXPANSION 256 + if (F_ISSET(&evp->e_ch, CH_ABBREVIATED)) { + if (++abcnt > MAX_ABBREVIATION_EXPANSION) { + if (v_event_flush(sp, CH_ABBREVIATED)) + msgq(sp, M_ERR, +"191|Abbreviation exceeded expansion limit: characters discarded"); + abcnt = 0; + if (LF_ISSET(TXT_REPLAY)) + goto done; + goto resolve; + } + } else + abcnt = 0; + + /* Check to see if the character fits into the replay buffers. */ + if (LF_ISSET(TXT_RECORD)) { + BINC_GOTO(sp, vip->rep, + vip->rep_len, (rcol + 1) * sizeof(EVENT)); + vip->rep[rcol++] = *evp; + } + +replay: if (LF_ISSET(TXT_REPLAY)) + evp = vip->rep + rcol++; + + /* Wrapmargin check for leading space. */ + if (wm_skip) { + wm_skip = 0; + if (evp->e_c == ' ') + goto resolve; + } + + /* If quoted by someone else, simply insert the character. */ + if (F_ISSET(&evp->e_ch, CH_QUOTED)) + goto insq_ch; + + /* + * !!! + * If this character was quoted by a K_VLNEXT or a backslash, replace + * the placeholder (a carat or a backslash) with the new character. + * If it was quoted by a K_VLNEXT, we've already adjusted the cursor + * because it has to appear on top of the placeholder character. If + * it was quoted by a backslash, adjust the cursor now, the cursor + * doesn't appear on top of it. Historic practice in both cases. + * + * Skip tests for abbreviations; ":ab xa XA" followed by "ixa^V<space>" + * doesn't perform an abbreviation. Special case, ^V^J (not ^V^M) is + * the same as ^J, historically. + */ + if (quote == Q_BTHIS || quote == Q_VTHIS) { + FL_CLR(ec_flags, EC_QUOTED); + if (LF_ISSET(TXT_MAPINPUT)) + FL_SET(ec_flags, EC_MAPINPUT); + + if (quote == Q_BTHIS && + (evp->e_value == K_VERASE || evp->e_value == K_VKILL)) { + quote = Q_NOTSET; + --tp->cno; + ++tp->owrite; + goto insl_ch; + } + if (quote == Q_VTHIS && evp->e_value != K_NL) { + quote = Q_NOTSET; + goto insl_ch; + } + quote = Q_NOTSET; + } + + /* + * !!! + * Translate "<CH_HEX>[isxdigit()]*" to a character with a hex value: + * this test delimits the value by any non-hex character. Offset by + * one, we use 0 to mean that we've found <CH_HEX>. + */ + if (hexcnt > 1 && !isxdigit(evp->e_c)) { + hexcnt = 0; + if (txt_hex(sp, tp)) + goto err; + } + + switch (evp->e_value) { + case K_CR: /* Carriage return. */ + case K_NL: /* New line. */ + /* Return in script windows and the command line. */ +k_cr: if (LF_ISSET(TXT_CR)) { + /* + * If this was a map, we may have not displayed + * the line. Display it, just in case. + * + * If a script window and not the colon line, + * push a <cr> so it gets executed. + */ + if (LF_ISSET(TXT_INFOLINE)) { + if (vs_change(sp, tp->lno, LINE_RESET)) + goto err; + } else if (F_ISSET(sp, SC_SCRIPT)) + (void)v_event_push(sp, NULL, "\r", 1, CH_NOMAP); + + /* Set term condition: if empty. */ + if (tp->cno <= tp->offset) + tp->term = TERM_CR; + /* + * Set term condition: if searching incrementally and + * the user entered a pattern, return a completed + * search, regardless if the entire pattern was found. + */ + if (FL_ISSET(is_flags, IS_RUNNING) && + tp->cno >= tp->offset + 1) + tp->term = TERM_SEARCH; + + goto k_escape; + } + +#define LINE_RESOLVE { \ + /* \ + * Handle abbreviations. If there was one, discard the \ + * replay characters. \ + */ \ + if (abb == AB_INWORD && \ + !LF_ISSET(TXT_REPLAY) && F_ISSET(gp, G_ABBREV)) { \ + if (txt_abbrev(sp, tp, &evp->e_c, \ + LF_ISSET(TXT_INFOLINE), &tmp, \ + &ab_turnoff)) \ + goto err; \ + if (tmp) { \ + if (LF_ISSET(TXT_RECORD)) \ + rcol -= tmp + 1; \ + goto resolve; \ + } \ + } \ + if (abb != AB_NOTSET) \ + abb = AB_NOTWORD; \ + if (UNMAP_TST) \ + txt_unmap(sp, tp, &ec_flags); \ + /* \ + * Delete any appended cursor. It's possible to get in \ + * situations where TXT_APPENDEOL is set but tp->insert \ + * is 0 when using the R command and all the characters \ + * are tp->owrite characters. \ + */ \ + if (LF_ISSET(TXT_APPENDEOL) && tp->insert > 0) { \ + --tp->len; \ + --tp->insert; \ + } \ +} + LINE_RESOLVE; + + /* + * Save the current line information for restoration in + * txt_backup(), and set the line final length. + */ + tp->sv_len = tp->len; + tp->sv_cno = tp->cno; + tp->len = tp->cno; + + /* Update the old line. */ + if (vs_change(sp, tp->lno, LINE_RESET)) + goto err; + + /* + * Historic practice, when the autoindent edit option was set, + * was to delete <blank> characters following the inserted + * newline. This affected the 'R', 'c', and 's' commands; 'c' + * and 's' retained the insert characters only, 'R' moved the + * overwrite and insert characters into the next TEXT structure. + * We keep track of the number of characters erased for the 'R' + * command so that the final resolution of the line is correct. + */ + tp->R_erase = 0; + owrite = tp->owrite; + insert = tp->insert; + if (LF_ISSET(TXT_REPLACE) && owrite != 0) { + for (p = tp->lb + tp->cno; owrite > 0 && isblank(*p); + ++p, --owrite, ++tp->R_erase); + if (owrite == 0) + for (; insert > 0 && isblank(*p); + ++p, ++tp->R_erase, --insert); + } else { + p = tp->lb + tp->cno + owrite; + if (O_ISSET(sp, O_AUTOINDENT)) + for (; insert > 0 && + isblank(*p); ++p, --insert); + owrite = 0; + } + + /* + * !!! + * Create a new line and insert the new TEXT into the queue. + * DON'T insert until the old line has been updated, or the + * inserted line count in line.c:db_get() will be wrong. + */ + if ((ntp = text_init(sp, p, + insert + owrite, insert + owrite + 32)) == NULL) + goto err; + CIRCLEQ_INSERT_TAIL(&sp->tiq, ntp, q); + + /* Set up bookkeeping for the new line. */ + ntp->insert = insert; + ntp->owrite = owrite; + ntp->lno = tp->lno + 1; + + /* + * Reset the autoindent line value. 0^D keeps the autoindent + * line from changing, ^D changes the level, even if there were + * no characters in the old line. Note, if using the current + * tp structure, use the cursor as the length, the autoindent + * characters may have been erased. + */ + if (LF_ISSET(TXT_AUTOINDENT)) { + if (carat == C_NOCHANGE) { + if (v_txt_auto(sp, OOBLNO, &ait, ait.ai, ntp)) + goto err; + FREE_SPACE(sp, ait.lb, ait.lb_len); + } else + if (v_txt_auto(sp, OOBLNO, tp, tp->cno, ntp)) + goto err; + carat = C_NOTSET; + } + + /* Reset the cursor. */ + ntp->cno = ntp->ai; + + /* + * If we're here because wrapmargin was set and we've broken a + * line, there may be additional information (i.e. the start of + * a line) in the wmt structure. + */ + if (wm_set) { + if (wmt.offset != 0 || + wmt.owrite != 0 || wmt.insert != 0) { +#define WMTSPACE wmt.offset + wmt.owrite + wmt.insert + BINC_GOTO(sp, ntp->lb, + ntp->lb_len, ntp->len + WMTSPACE + 32); + memmove(ntp->lb + ntp->cno, wmt.lb, WMTSPACE); + ntp->len += WMTSPACE; + ntp->cno += wmt.offset; + ntp->owrite = wmt.owrite; + ntp->insert = wmt.insert; + } + wm_set = 0; + } + + /* New lines are TXT_APPENDEOL. */ + if (ntp->owrite == 0 && ntp->insert == 0) { + BINC_GOTO(sp, ntp->lb, ntp->lb_len, ntp->len + 1); + LF_SET(TXT_APPENDEOL); + ntp->lb[ntp->cno] = CH_CURSOR; + ++ntp->insert; + ++ntp->len; + } + + /* Swap old and new TEXT's, and update the new line. */ + tp = ntp; + if (vs_change(sp, tp->lno, LINE_INSERT)) + goto err; + + goto resolve; + case K_ESCAPE: /* Escape. */ + if (!LF_ISSET(TXT_ESCAPE)) + goto ins_ch; + + /* If we have a count, start replaying the input. */ + if (rcount > 1) { + --rcount; + + rcol = 0; + abb = AB_NOTSET; + LF_CLR(TXT_RECORD); + LF_SET(TXT_REPLAY); + + /* + * Some commands (e.g. 'o') need a <newline> for each + * repetition. + */ + if (LF_ISSET(TXT_ADDNEWLINE)) + goto k_cr; + + /* + * The R command turns into the 'a' command after the + * first repetition. + */ + if (LF_ISSET(TXT_REPLACE)) { + tp->insert = tp->owrite; + tp->owrite = 0; + LF_CLR(TXT_REPLACE); + } + goto replay; + } + + /* Set term condition: if empty. */ + if (tp->cno <= tp->offset) + tp->term = TERM_ESC; + /* + * Set term condition: if searching incrementally and the user + * entered a pattern, return a completed search, regardless if + * the entire pattern was found. + */ + if (FL_ISSET(is_flags, IS_RUNNING) && tp->cno >= tp->offset + 1) + tp->term = TERM_SEARCH; + +k_escape: LINE_RESOLVE; + + /* + * Clean up for the 'R' command, restoring overwrite + * characters, and making them into insert characters. + */ + if (LF_ISSET(TXT_REPLACE)) + txt_Rresolve(sp, &sp->tiq, tp, len); + + /* + * If there are any overwrite characters, copy down + * any insert characters, and decrement the length. + */ + if (tp->owrite) { + if (tp->insert) + memmove(tp->lb + tp->cno, + tp->lb + tp->cno + tp->owrite, tp->insert); + tp->len -= tp->owrite; + } + + /* + * Optionally resolve the lines into the file. If not + * resolving the lines into the file, end the line with + * a nul. If the line is empty, then set the length to + * 0, the termination condition has already been set. + * + * XXX + * This is wrong, should pass back a length. + */ + if (LF_ISSET(TXT_RESOLVE)) { + if (txt_resolve(sp, &sp->tiq, flags)) + goto err; + } else { + BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1); + tp->lb[tp->len] = '\0'; + } + + /* + * Set the return cursor position to rest on the last + * inserted character. + */ + if (tp->cno != 0) + --tp->cno; + + /* Update the last line. */ + if (vs_change(sp, tp->lno, LINE_RESET)) + return (1); + goto done; + case K_CARAT: /* Delete autoindent chars. */ + if (tp->cno <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) + carat = C_CARATSET; + goto ins_ch; + case K_ZERO: /* Delete autoindent chars. */ + if (tp->cno <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) + carat = C_ZEROSET; + goto ins_ch; + case K_CNTRLD: /* Delete autoindent char. */ + /* + * If in the first column or no characters to erase, ignore + * the ^D (this matches historic practice). If not doing + * autoindent or already inserted non-ai characters, it's a + * literal. The latter test is done in the switch, as the + * CARAT forms are N + 1, not N. + */ + if (!LF_ISSET(TXT_AUTOINDENT)) + goto ins_ch; + if (tp->cno == 0) + goto resolve; + + switch (carat) { + case C_CARATSET: /* ^^D */ + if (tp->ai == 0 || tp->cno > tp->ai + tp->offset + 1) + goto ins_ch; + + /* Save the ai string for later. */ + ait.lb = NULL; + ait.lb_len = 0; + BINC_GOTO(sp, ait.lb, ait.lb_len, tp->ai); + memmove(ait.lb, tp->lb, tp->ai); + ait.ai = ait.len = tp->ai; + + carat = C_NOCHANGE; + goto leftmargin; + case C_ZEROSET: /* 0^D */ + if (tp->ai == 0 || tp->cno > tp->ai + tp->offset + 1) + goto ins_ch; + + carat = C_NOTSET; +leftmargin: tp->lb[tp->cno - 1] = ' '; + tp->owrite += tp->cno - tp->offset; + tp->ai = 0; + tp->cno = tp->offset; + break; + case C_NOTSET: /* ^D */ + if (tp->ai == 0 || tp->cno > tp->ai + tp->offset) + goto ins_ch; + + (void)txt_dent(sp, tp, 0); + break; + default: + abort(); + } + break; + case K_VERASE: /* Erase the last character. */ + /* If can erase over the prompt, return. */ + if (tp->cno <= tp->offset && LF_ISSET(TXT_BS)) { + tp->term = TERM_BS; + goto done; + } + + /* + * If at the beginning of the line, try and drop back to a + * previously inserted line. + */ + if (tp->cno == 0) { + if ((ntp = + txt_backup(sp, &sp->tiq, tp, &flags)) == NULL) + goto err; + tp = ntp; + break; + } + + /* If nothing to erase, bell the user. */ + if (tp->cno <= tp->offset) { + if (!LF_ISSET(TXT_REPLAY)) + txt_nomorech(sp); + break; + } + + /* Drop back one character. */ + --tp->cno; + + /* + * Historically, vi didn't replace the erased characters with + * <blank>s, presumably because it's easier to fix a minor + * typing mistake and continue on if the previous letters are + * already there. This is a problem for incremental searching, + * because the user can no longer tell where they are in the + * colon command line because the cursor is at the last search + * point in the screen. So, if incrementally searching, erase + * the erased characters from the screen. + */ + if (FL_ISSET(is_flags, IS_RUNNING)) + tp->lb[tp->cno] = ' '; + + /* + * Increment overwrite, decrement ai if deleted. + * + * !!! + * Historic vi did not permit users to use erase characters + * to delete autoindent characters. We do. Eat hot death, + * POSIX. + */ + ++tp->owrite; + if (tp->cno < tp->ai) + --tp->ai; + + /* Reset if we deleted an incremental search character. */ + if (FL_ISSET(is_flags, IS_RUNNING)) + FL_SET(is_flags, IS_RESTART); + break; + case K_VWERASE: /* Skip back one word. */ + /* + * If at the beginning of the line, try and drop back to a + * previously inserted line. + */ + if (tp->cno == 0) { + if ((ntp = + txt_backup(sp, &sp->tiq, tp, &flags)) == NULL) + goto err; + tp = ntp; + } + + /* + * If at offset, nothing to erase so bell the user. + */ + if (tp->cno <= tp->offset) { + if (!LF_ISSET(TXT_REPLAY)) + txt_nomorech(sp); + break; + } + + /* + * The first werase goes back to any autoindent column and the + * second werase goes back to the offset. + * + * !!! + * Historic vi did not permit users to use erase characters to + * delete autoindent characters. + */ + if (tp->ai && tp->cno > tp->ai) + max = tp->ai; + else { + tp->ai = 0; + max = tp->offset; + } + + /* Skip over trailing space characters. */ + while (tp->cno > max && isblank(tp->lb[tp->cno - 1])) { + --tp->cno; + ++tp->owrite; + } + if (tp->cno == max) + break; + /* + * There are three types of word erase found on UNIX systems. + * They can be identified by how the string /a/b/c is treated + * -- as 1, 3, or 6 words. Historic vi had two classes of + * characters, and strings were delimited by them and + * <blank>'s, so, 6 words. The historic tty interface used + * <blank>'s to delimit strings, so, 1 word. The algorithm + * offered in the 4.4BSD tty interface (as stty altwerase) + * treats it as 3 words -- there are two classes of + * characters, and strings are delimited by them and + * <blank>'s. The difference is that the type of the first + * erased character erased is ignored, which is exactly right + * when erasing pathname components. The edit options + * TXT_ALTWERASE and TXT_TTYWERASE specify the 4.4BSD tty + * interface and the historic tty driver behavior, + * respectively, and the default is the same as the historic + * vi behavior. + * + * Overwrite erased characters if doing incremental search; + * see comment above. + */ + if (LF_ISSET(TXT_TTYWERASE)) + while (tp->cno > max) { + --tp->cno; + ++tp->owrite; + if (FL_ISSET(is_flags, IS_RUNNING)) + tp->lb[tp->cno] = ' '; + if (isblank(tp->lb[tp->cno - 1])) + break; + } + else { + if (LF_ISSET(TXT_ALTWERASE)) { + --tp->cno; + ++tp->owrite; + if (FL_ISSET(is_flags, IS_RUNNING)) + tp->lb[tp->cno] = ' '; + if (isblank(tp->lb[tp->cno - 1])) + break; + } + if (tp->cno > max) + tmp = inword(tp->lb[tp->cno - 1]); + while (tp->cno > max) { + --tp->cno; + ++tp->owrite; + if (FL_ISSET(is_flags, IS_RUNNING)) + tp->lb[tp->cno] = ' '; + if (tmp != inword(tp->lb[tp->cno - 1]) + || isblank(tp->lb[tp->cno - 1])) + break; + } + } + + /* Reset if we deleted an incremental search character. */ + if (FL_ISSET(is_flags, IS_RUNNING)) + FL_SET(is_flags, IS_RESTART); + break; + case K_VKILL: /* Restart this line. */ + /* + * !!! + * If at the beginning of the line, try and drop back to a + * previously inserted line. Historic vi did not permit + * users to go back to previous lines. + */ + if (tp->cno == 0) { + if ((ntp = + txt_backup(sp, &sp->tiq, tp, &flags)) == NULL) + goto err; + tp = ntp; + } + + /* If at offset, nothing to erase so bell the user. */ + if (tp->cno <= tp->offset) { + if (!LF_ISSET(TXT_REPLAY)) + txt_nomorech(sp); + break; + } + + /* + * First kill goes back to any autoindent and second kill goes + * back to the offset. + * + * !!! + * Historic vi did not permit users to use erase characters to + * delete autoindent characters. + */ + if (tp->ai && tp->cno > tp->ai) + max = tp->ai; + else { + tp->ai = 0; + max = tp->offset; + } + tp->owrite += tp->cno - max; + + /* + * Overwrite erased characters if doing incremental search; + * see comment above. + */ + if (FL_ISSET(is_flags, IS_RUNNING)) + do { + tp->lb[--tp->cno] = ' '; + } while (tp->cno > max); + else + tp->cno = max; + + /* Reset if we deleted an incremental search character. */ + if (FL_ISSET(is_flags, IS_RUNNING)) + FL_SET(is_flags, IS_RESTART); + break; + case K_CNTRLT: /* Add autoindent characters. */ + if (!LF_ISSET(TXT_CNTRLT)) + goto ins_ch; + if (txt_dent(sp, tp, 1)) + goto err; + goto ebuf_chk; + case K_RIGHTBRACE: + case K_RIGHTPAREN: + if (LF_ISSET(TXT_SHOWMATCH)) + showmatch = 1; + goto ins_ch; + case K_BACKSLASH: /* Quote next erase/kill. */ + /* + * !!! + * Historic vi tried to make abbreviations after a backslash + * escape work. If you did ":ab x y", and inserted "x\^H", + * (assuming the erase character was ^H) you got "x^H", and + * no abbreviation was done. If you inserted "x\z", however, + * it tried to back up and do the abbreviation, i.e. replace + * 'x' with 'y'. The problem was it got it wrong, and you + * ended up with "zy\". + * + * This is really hard to do (you have to remember the + * word/non-word state, for example), and doesn't make any + * sense to me. Both backslash and the characters it + * (usually) escapes will individually trigger the + * abbreviation, so I don't see why the combination of them + * wouldn't. I don't expect to get caught on this one, + * particularly since it never worked right, but I've been + * wrong before. + * + * Do the tests for abbreviations, so ":ab xa XA", + * "ixa\<K_VERASE>" performs the abbreviation. + */ + quote = Q_BNEXT; + goto insq_ch; + case K_VLNEXT: /* Quote next character. */ + evp->e_c = '^'; + quote = Q_VNEXT; + /* + * Turn on the quote flag so that the underlying routines + * quote the next character where it's possible. Turn off + * the input mapbiting flag so that we don't remap the next + * character. + */ + FL_SET(ec_flags, EC_QUOTED); + FL_CLR(ec_flags, EC_MAPINPUT); + + /* + * !!! + * Skip the tests for abbreviations, so ":ab xa XA", + * "ixa^V<space>" doesn't perform the abbreviation. + */ + goto insl_ch; + case K_HEXCHAR: + hexcnt = 1; + goto insq_ch; + default: /* Insert the character. */ +ins_ch: /* + * Historically, vi eliminated nul's out of hand. If the + * beautify option was set, it also deleted any unknown + * ASCII value less than space (040) and the del character + * (0177), except for tabs. Unknown is a key word here. + * Most vi documentation claims that it deleted everything + * but <tab>, <nl> and <ff>, as that's what the original + * 4BSD documentation said. This is obviously wrong, + * however, as <esc> would be included in that list. What + * we do is eliminate any unquoted, iscntrl() character that + * wasn't a replay and wasn't handled specially, except + * <tab> or <ff>. + */ + if (LF_ISSET(TXT_BEAUTIFY) && iscntrl(evp->e_c) && + evp->e_value != K_FORMFEED && evp->e_value != K_TAB) { + msgq(sp, M_BERR, + "192|Illegal character; quote to enter"); + if (LF_ISSET(TXT_REPLAY)) + goto done; + break; + } + +insq_ch: /* + * If entering a non-word character after a word, check for + * abbreviations. If there was one, discard replay characters. + * If entering a blank character, check for unmap commands, + * as well. + */ + if (!inword(evp->e_c)) { + if (abb == AB_INWORD && + !LF_ISSET(TXT_REPLAY) && F_ISSET(gp, G_ABBREV)) { + if (txt_abbrev(sp, tp, &evp->e_c, + LF_ISSET(TXT_INFOLINE), &tmp, &ab_turnoff)) + goto err; + if (tmp) { + if (LF_ISSET(TXT_RECORD)) + rcol -= tmp + 1; + goto resolve; + } + } + if (isblank(evp->e_c) && UNMAP_TST) + txt_unmap(sp, tp, &ec_flags); + } + if (abb != AB_NOTSET) + abb = inword(evp->e_c) ? AB_INWORD : AB_NOTWORD; + +insl_ch: if (txt_insch(sp, tp, &evp->e_c, flags)) + goto err; + + /* + * If we're using K_VLNEXT to quote the next character, then + * we want the cursor to position itself on the ^ placeholder + * we're displaying, to match historic practice. + */ + if (quote == Q_VNEXT) { + --tp->cno; + ++tp->owrite; + } + + /* + * !!! + * Translate "<CH_HEX>[isxdigit()]*" to a character with + * a hex value: this test delimits the value by the max + * number of hex bytes. Offset by one, we use 0 to mean + * that we've found <CH_HEX>. + */ + if (hexcnt != 0 && hexcnt++ == sizeof(CHAR_T) * 2 + 1) { + hexcnt = 0; + if (txt_hex(sp, tp)) + goto err; + } + + /* + * Check to see if we've crossed the margin. + * + * !!! + * In the historic vi, the wrapmargin value was figured out + * using the display widths of the characters, i.e. <tab> + * characters were counted as two characters if the list edit + * option is set, but as the tabstop edit option number of + * characters otherwise. That's what the vs_column() function + * gives us, so we use it. + */ + if (margin != 0) { + if (vs_column(sp, &tcol)) + goto err; + if (tcol >= margin) { + if (txt_margin(sp, tp, &wmt, &tmp, flags)) + goto err; + if (tmp) { + if (isblank(evp->e_c)) + wm_skip = 1; + wm_set = 1; + goto k_cr; + } + } + } + + /* + * If we've reached the end of the buffer, then we need to + * switch into insert mode. This happens when there's a + * change to a mark and the user puts in more characters than + * the length of the motion. + */ +ebuf_chk: if (tp->cno >= tp->len) { + BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1); + LF_SET(TXT_APPENDEOL); + + tp->lb[tp->cno] = CH_CURSOR; + ++tp->insert; + ++tp->len; + } + + /* Step the quote state forward. */ + if (quote != Q_NOTSET) { + if (quote == Q_BNEXT) + quote = Q_BTHIS; + if (quote == Q_VNEXT) + quote = Q_VTHIS; + } + break; + } + +#ifdef DEBUG + if (tp->cno + tp->insert + tp->owrite != tp->len) { + msgq(sp, M_ERR, + "len %u != cno: %u ai: %u insert %u overwrite %u", + tp->len, tp->cno, tp->ai, tp->insert, tp->owrite); + if (LF_ISSET(TXT_REPLAY)) + goto done; + tp->len = tp->cno + tp->insert + tp->owrite; + } +#endif + +resolve:/* + * 1: If we don't need to know where the cursor really is and we're + * replaying text, keep going. + */ + if (margin == 0 && LF_ISSET(TXT_REPLAY)) + goto replay; + + /* + * 2: Reset the line. Don't bother unless we're about to wait on + * a character or we need to know where the cursor really is. + * We have to do this before showing matching characters so the + * user can see what they're matching. + */ + if ((margin != 0 || !KEYS_WAITING(sp)) && + vs_change(sp, tp->lno, LINE_RESET)) + return (1); + + /* + * 3: If there aren't keys waiting, display the matching character. + * We have to do this before resolving any messages, otherwise + * the error message from a missing match won't appear correctly. + */ + if (showmatch) { + if (!KEYS_WAITING(sp) && txt_showmatch(sp, tp)) + return (1); + showmatch = 0; + } + + /* + * 4: If there have been messages and we're not editing on the colon + * command line or doing file name completion, resolve them. + */ + if ((vip->totalcount != 0 || F_ISSET(gp, G_BELLSCHED)) && + !F_ISSET(sp, SC_TINPUT_INFO) && !filec_redraw && + vs_resolve(sp, NULL, 0)) + return (1); + + /* + * 5: Refresh the screen if we're about to wait on a character or we + * need to know where the cursor really is. + */ + if (margin != 0 || !KEYS_WAITING(sp)) { + UPDATE_POSITION(sp, tp); + if (vs_refresh(sp, margin != 0)) + return (1); + } + + /* 6: Proceed with the incremental search. */ + if (FL_ISSET(is_flags, IS_RUNNING) && txt_isrch(sp, vp, tp, &is_flags)) + return (1); + + /* 7: Next character... */ + if (LF_ISSET(TXT_REPLAY)) + goto replay; + goto next; + +done: /* Leave input mode. */ + F_CLR(sp, SC_TINPUT); + + /* If recording for playback, save it. */ + if (LF_ISSET(TXT_RECORD)) + vip->rep_cnt = rcol; + + /* + * If not working on the colon command line, set the final cursor + * position. + */ + if (!F_ISSET(sp, SC_TINPUT_INFO)) { + vp->m_final.lno = tp->lno; + vp->m_final.cno = tp->cno; + } + return (0); + +err: +alloc_err: + txt_err(sp, &sp->tiq); + return (1); +} + +/* + * txt_abbrev -- + * Handle abbreviations. + */ +static int +txt_abbrev(sp, tp, pushcp, isinfoline, didsubp, turnoffp) + SCR *sp; + TEXT *tp; + CHAR_T *pushcp; + int isinfoline, *didsubp, *turnoffp; +{ + VI_PRIVATE *vip; + CHAR_T ch, *p; + SEQ *qp; + size_t len, off; + + /* Check to make sure we're not at the start of an append. */ + *didsubp = 0; + if (tp->cno == tp->offset) + return (0); + + vip = VIP(sp); + + /* + * Find the start of the "word". + * + * !!! + * We match historic practice, which, as far as I can tell, had an + * off-by-one error. The way this worked was that when the inserted + * text switched from a "word" character to a non-word character, + * vi would check for possible abbreviations. It would then take the + * type (i.e. word/non-word) of the character entered TWO characters + * ago, and move backward in the text until reaching a character that + * was not that type, or the beginning of the insert, the line, or + * the file. For example, in the string "abc<space>", when the <space> + * character triggered the abbreviation check, the type of the 'b' + * character was used for moving through the string. Maybe there's a + * reason for not using the first (i.e. 'c') character, but I can't + * think of one. + * + * Terminate at the beginning of the insert or the character after the + * offset character -- both can be tested for using tp->offset. + */ + off = tp->cno - 1; /* Previous character. */ + p = tp->lb + off; + len = 1; /* One character test. */ + if (off == tp->offset || isblank(p[-1])) + goto search; + if (inword(p[-1])) /* Move backward to change. */ + for (;;) { + --off; --p; ++len; + if (off == tp->offset || !inword(p[-1])) + break; + } + else + for (;;) { + --off; --p; ++len; + if (off == tp->offset || + inword(p[-1]) || isblank(p[-1])) + break; + } + + /* + * !!! + * Historic vi exploded abbreviations on the command line. This has + * obvious problems in that unabbreviating the string can be extremely + * tricky, particularly if the string has, say, an embedded escape + * character. Personally, I think it's a stunningly bad idea. Other + * examples of problems this caused in historic vi are: + * :ab foo bar + * :ab foo baz + * results in "bar" being abbreviated to "baz", which wasn't what the + * user had in mind at all. Also, the commands: + * :ab foo bar + * :unab foo<space> + * resulted in an error message that "bar" wasn't mapped. Finally, + * since the string was already exploded by the time the unabbreviate + * command got it, all it knew was that an abbreviation had occurred. + * Cleverly, it checked the replacement string for its unabbreviation + * match, which meant that the commands: + * :ab foo1 bar + * :ab foo2 bar + * :unab foo2 + * unabbreviate "foo1", and the commands: + * :ab foo bar + * :ab bar baz + * unabbreviate "foo"! + * + * Anyway, people neglected to first ask my opinion before they wrote + * macros that depend on this stuff, so, we make this work as follows. + * When checking for an abbreviation on the command line, if we get a + * string which is <blank> terminated and which starts at the beginning + * of the line, we check to see it is the abbreviate or unabbreviate + * commands. If it is, turn abbreviations off and return as if no + * abbreviation was found. Note also, minor trickiness, so that if + * the user erases the line and starts another command, we turn the + * abbreviations back on. + * + * This makes the layering look like a Nachos Supreme. + */ +search: if (isinfoline) + if (off == tp->ai || off == tp->offset) + if (ex_is_abbrev(p, len)) { + *turnoffp = 1; + return (0); + } else + *turnoffp = 0; + else + if (*turnoffp) + return (0); + + /* Check for any abbreviations. */ + if ((qp = seq_find(sp, NULL, NULL, p, len, SEQ_ABBREV, NULL)) == NULL) + return (0); + + /* + * Push the abbreviation onto the tty stack. Historically, characters + * resulting from an abbreviation expansion were themselves subject to + * map expansions, O_SHOWMATCH matching etc. This means the expanded + * characters will be re-tested for abbreviations. It's difficult to + * know what historic practice in this case was, since abbreviations + * were applied to :colon command lines, so entering abbreviations that + * looped was tricky, although possible. In addition, obvious loops + * didn't work as expected. (The command ':ab a b|ab b c|ab c a' will + * silently only implement and/or display the last abbreviation.) + * + * This implementation doesn't recover well from such abbreviations. + * The main input loop counts abbreviated characters, and, when it + * reaches a limit, discards any abbreviated characters on the queue. + * It's difficult to back up to the original position, as the replay + * queue would have to be adjusted, and the line state when an initial + * abbreviated character was received would have to be saved. + */ + ch = *pushcp; + if (v_event_push(sp, NULL, &ch, 1, CH_ABBREVIATED)) + return (1); + if (v_event_push(sp, NULL, qp->output, qp->olen, CH_ABBREVIATED)) + return (1); + + /* + * If the size of the abbreviation is larger than or equal to the size + * of the original text, move to the start of the replaced characters, + * and add their length to the overwrite count. + * + * If the abbreviation is smaller than the original text, we have to + * delete the additional overwrite characters and copy down any insert + * characters. + */ + tp->cno -= len; + if (qp->olen >= len) + tp->owrite += len; + else { + if (tp->insert) + memmove(tp->lb + tp->cno + qp->olen, + tp->lb + tp->cno + tp->owrite + len, tp->insert); + tp->owrite += qp->olen; + tp->len -= len - qp->olen; + } + + /* + * We return the length of the abbreviated characters. This is so + * the calling routine can replace the replay characters with the + * abbreviation. This means that subsequent '.' commands will produce + * the same text, regardless of intervening :[un]abbreviate commands. + * This is historic practice. + */ + *didsubp = len; + return (0); +} + +/* + * txt_unmap -- + * Handle the unmap command. + */ +static void +txt_unmap(sp, tp, ec_flagsp) + SCR *sp; + TEXT *tp; + u_int32_t *ec_flagsp; +{ + size_t len, off; + char *p; + + /* Find the beginning of this "word". */ + for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --p, --off) { + if (isblank(*p)) { + ++p; + break; + } + ++len; + if (off == tp->ai || off == tp->offset) + break; + } + + /* + * !!! + * Historic vi exploded input mappings on the command line. See the + * txt_abbrev() routine for an explanation of the problems inherent + * in this. + * + * We make this work as follows. If we get a string which is <blank> + * terminated and which starts at the beginning of the line, we check + * to see it is the unmap command. If it is, we return that the input + * mapping should be turned off. Note also, minor trickiness, so that + * if the user erases the line and starts another command, we go ahead + * an turn mapping back on. + */ + if ((off == tp->ai || off == tp->offset) && ex_is_unmap(p, len)) + FL_CLR(*ec_flagsp, EC_MAPINPUT); + else + FL_SET(*ec_flagsp, EC_MAPINPUT); +} + +/* + * txt_ai_resolve -- + * When a line is resolved by <esc>, review autoindent characters. + */ +static void +txt_ai_resolve(sp, tp, changedp) + SCR *sp; + TEXT *tp; + int *changedp; +{ + u_long ts; + int del; + size_t cno, len, new, old, scno, spaces, tab_after_sp, tabs; + char *p; + + *changedp = 0; + + /* + * If the line is empty, has an offset, or no autoindent + * characters, we're done. + */ + if (!tp->len || tp->offset || !tp->ai) + return; + + /* + * If the length is less than or equal to the autoindent + * characters, delete them. + */ + if (tp->len <= tp->ai) { + tp->ai = tp->cno = tp->len = 0; + return; + } + + /* + * The autoindent characters plus any leading <blank> characters + * in the line are resolved into the minimum number of characters. + * Historic practice. + */ + ts = O_VAL(sp, O_TABSTOP); + + /* Figure out the last <blank> screen column. */ + for (p = tp->lb, scno = 0, len = tp->len, + spaces = tab_after_sp = 0; len-- && isblank(*p); ++p) + if (*p == '\t') { + if (spaces) + tab_after_sp = 1; + scno += COL_OFF(scno, ts); + } else { + ++spaces; + ++scno; + } + + /* + * If there are no spaces, or no tabs after spaces and less than + * ts spaces, it's already minimal. + */ + if (!spaces || !tab_after_sp && spaces < ts) + return; + + /* Count up spaces/tabs needed to get to the target. */ + for (cno = 0, tabs = 0; cno + COL_OFF(cno, ts) <= scno; ++tabs) + cno += COL_OFF(cno, ts); + spaces = scno - cno; + + /* + * Figure out how many characters we're dropping -- if we're not + * dropping any, it's already minimal, we're done. + */ + old = p - tp->lb; + new = spaces + tabs; + if (old == new) + return; + + /* Shift the rest of the characters down, adjust the counts. */ + del = old - new; + memmove(p - del, p, tp->len - old); + tp->len -= del; + tp->cno -= del; + + /* Fill in space/tab characters. */ + for (p = tp->lb; tabs--;) + *p++ = '\t'; + while (spaces--) + *p++ = ' '; + *changedp = 1; +} + +/* + * v_txt_auto -- + * Handle autoindent. If aitp isn't NULL, use it, otherwise, + * retrieve the line. + * + * PUBLIC: int v_txt_auto __P((SCR *, recno_t, TEXT *, size_t, TEXT *)); + */ +int +v_txt_auto(sp, lno, aitp, len, tp) + SCR *sp; + recno_t lno; + TEXT *aitp, *tp; + size_t len; +{ + size_t nlen; + char *p, *t; + + if (aitp == NULL) { + /* + * If the ex append command is executed with an address of 0, + * it's possible to get here with a line number of 0. Return + * an indent of 0. + */ + if (lno == 0) { + tp->ai = 0; + return (0); + } + if (db_get(sp, lno, DBG_FATAL, &t, &len)) + return (1); + } else + t = aitp->lb; + + /* Count whitespace characters. */ + for (p = t; len > 0; ++p, --len) + if (!isblank(*p)) + break; + + /* Set count, check for no indentation. */ + if ((nlen = (p - t)) == 0) + return (0); + + /* Make sure the buffer's big enough. */ + BINC_RET(sp, tp->lb, tp->lb_len, tp->len + nlen); + + /* Copy the buffer's current contents up. */ + if (tp->len != 0) + memmove(tp->lb + nlen, tp->lb, tp->len); + tp->len += nlen; + + /* Copy the indentation into the new buffer. */ + memmove(tp->lb, t, nlen); + + /* Set the autoindent count. */ + tp->ai = nlen; + return (0); +} + +/* + * txt_backup -- + * Back up to the previously edited line. + */ +static TEXT * +txt_backup(sp, tiqh, tp, flagsp) + SCR *sp; + TEXTH *tiqh; + TEXT *tp; + u_int32_t *flagsp; +{ + VI_PRIVATE *vip; + TEXT *ntp; + + /* Get a handle on the previous TEXT structure. */ + if ((ntp = tp->q.cqe_prev) == (void *)tiqh) { + if (!FL_ISSET(*flagsp, TXT_REPLAY)) + msgq(sp, M_BERR, + "193|Already at the beginning of the insert"); + return (tp); + } + + /* Bookkeeping. */ + ntp->len = ntp->sv_len; + + /* Handle appending to the line. */ + vip = VIP(sp); + if (ntp->owrite == 0 && ntp->insert == 0) { + ntp->lb[ntp->len] = CH_CURSOR; + ++ntp->insert; + ++ntp->len; + FL_SET(*flagsp, TXT_APPENDEOL); + } else + FL_CLR(*flagsp, TXT_APPENDEOL); + + /* Release the current TEXT. */ + CIRCLEQ_REMOVE(tiqh, tp, q); + text_free(tp); + + /* Update the old line on the screen. */ + if (vs_change(sp, ntp->lno + 1, LINE_DELETE)) + return (NULL); + + /* Return the new/current TEXT. */ + return (ntp); +} + +/* + * Text indentation is truly strange. ^T and ^D do movements to the next or + * previous shiftwidth value, i.e. for a 1-based numbering, with shiftwidth=3, + * ^T moves a cursor on the 7th, 8th or 9th column to the 10th column, and ^D + * moves it back. + * + * !!! + * The ^T and ^D characters in historical vi had special meaning only when they + * were the first characters entered after entering text input mode. As normal + * erase characters couldn't erase autoindent characters (^T in this case), it + * meant that inserting text into previously existing text was strange -- ^T + * only worked if it was the first keystroke(s), and then could only be erased + * using ^D. This implementation treats ^T specially anywhere it occurs in the + * input, and permits the standard erase characters to erase the characters it + * inserts. + * + * !!! + * A fun test is to try: + * :se sw=4 ai list + * i<CR>^Tx<CR>^Tx<CR>^Tx<CR>^Dx<CR>^Dx<CR>^Dx<esc> + * Historic vi loses some of the '$' marks on the line ends, but otherwise gets + * it right. + * + * XXX + * Technically, txt_dent should be part of the screen interface, as it requires + * knowledge of character sizes, including <space>s, on the screen. It's here + * because it's a complicated little beast, and I didn't want to shove it down + * into the screen. It's probable that KEY_LEN will call into the screen once + * there are screens with different character representations. + * + * txt_dent -- + * Handle ^T indents, ^D outdents. + * + * If anything changes here, check the ex version to see if it needs similar + * changes. + */ +static int +txt_dent(sp, tp, isindent) + SCR *sp; + TEXT *tp; + int isindent; +{ + CHAR_T ch; + u_long sw, ts; + size_t cno, current, spaces, target, tabs, off; + int ai_reset; + + ts = O_VAL(sp, O_TABSTOP); + sw = O_VAL(sp, O_SHIFTWIDTH); + + /* + * Since we don't know what precedes the character(s) being inserted + * (or deleted), the preceding whitespace characters must be resolved. + * An example is a <tab>, which doesn't need a full shiftwidth number + * of columns because it's preceded by <space>s. This is easy to get + * if the user sets shiftwidth to a value less than tabstop (or worse, + * something for which tabstop isn't a multiple) and then uses ^T to + * indent, and ^D to outdent. + * + * Figure out the current and target screen columns. In the historic + * vi, the autoindent column was NOT determined using display widths + * of characters as was the wrapmargin column. For that reason, we + * can't use the vs_column() function, but have to calculate it here. + * This is slow, but it's normally only on the first few characters of + * a line. + */ + for (current = cno = 0; cno < tp->cno; ++cno) + current += tp->lb[cno] == '\t' ? + COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]); + + target = current; + if (isindent) + target += COL_OFF(target, sw); + else + target -= --target % sw; + + /* + * The AI characters will be turned into overwrite characters if the + * cursor immediately follows them. We test both the cursor position + * and the indent flag because there's no single test. (^T can only + * be detected by the cursor position, and while we know that the test + * is always true for ^D, the cursor can be in more than one place, as + * "0^D" and "^D" are different.) + */ + ai_reset = !isindent || tp->cno == tp->ai + tp->offset; + + /* + * Back up over any previous <blank> characters, changing them into + * overwrite characters (including any ai characters). Then figure + * out the current screen column. + */ + for (; tp->cno > tp->offset && + (tp->lb[tp->cno - 1] == ' ' || tp->lb[tp->cno - 1] == '\t'); + --tp->cno, ++tp->owrite); + for (current = cno = 0; cno < tp->cno; ++cno) + current += tp->lb[cno] == '\t' ? + COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]); + + /* + * If we didn't move up to or past the target, it's because there + * weren't enough characters to delete, e.g. the first character + * of the line was a tp->offset character, and the user entered + * ^D to move to the beginning of a line. An example of this is: + * + * :set ai sw=4<cr>i<space>a<esc>i^T^D + * + * Otherwise, count up the total spaces/tabs needed to get from the + * beginning of the line (or the last non-<blank> character) to the + * target. + */ + if (current >= target) + spaces = tabs = 0; + else { + for (cno = current, + tabs = 0; cno + COL_OFF(cno, ts) <= target; ++tabs) + cno += COL_OFF(cno, ts); + spaces = target - cno; + } + + /* If we overwrote ai characters, reset the ai count. */ + if (ai_reset) + tp->ai = tabs + spaces; + + /* + * Call txt_insch() to insert each character, so that we get the + * correct effect when we add a <tab> to replace N <spaces>. + */ + for (ch = '\t'; tabs > 0; --tabs) + (void)txt_insch(sp, tp, &ch, 0); + for (ch = ' '; spaces > 0; --spaces) + (void)txt_insch(sp, tp, &ch, 0); + return (0); +} + +/* + * txt_fc -- + * File name completion. + */ +static int +txt_fc(sp, tp, redrawp) + SCR *sp; + TEXT *tp; + int *redrawp; +{ + struct stat sb; + ARGS **argv; + CHAR_T s_ch; + EXCMD cmd; + size_t indx, len, nlen, off; + int argc, trydir; + char *p, *t; + + trydir = 0; + *redrawp = 0; + + /* + * Find the beginning of this "word" -- if we're at the beginning + * of the line, it's a special case. + */ + if (tp->cno == 1) { + len = 0; + p = tp->lb; + } else +retry: for (len = 0, + off = tp->cno - 1, p = tp->lb + off;; --off, --p) { + if (isblank(*p)) { + ++p; + break; + } + ++len; + if (off == tp->ai || off == tp->offset) + break; + } + + /* + * Get enough space for a wildcard character. + * + * XXX + * This won't work for "foo\", since the \ will escape the expansion + * character. I'm not sure if that's a bug or not... + */ + off = p - tp->lb; + BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1); + p = tp->lb + off; + + s_ch = p[len]; + p[len] = '*'; + + /* Build an ex command, and call the ex expansion routines. */ + ex_cinit(&cmd, 0, 0, OOBLNO, OOBLNO, 0, NULL); + if (argv_init(sp, &cmd)) + return (1); + if (argv_exp2(sp, &cmd, p, len + 1)) { + p[len] = s_ch; + return (0); + } + argc = cmd.argc; + argv = cmd.argv; + + p[len] = s_ch; + + switch (argc) { + case 0: /* No matches. */ + if (!trydir) + (void)sp->gp->scr_bell(sp); + return (0); + case 1: /* One match. */ + /* If something changed, do the exchange. */ + nlen = strlen(cmd.argv[0]->bp); + if (len != nlen || memcmp(cmd.argv[0]->bp, p, len)) + break; + + /* If haven't done a directory test, do it now. */ + if (!trydir && + !stat(cmd.argv[0]->bp, &sb) && S_ISDIR(sb.st_mode)) { + p += len; + goto isdir; + } + + /* If nothing changed, period, ring the bell. */ + if (!trydir) + (void)sp->gp->scr_bell(sp); + return (0); + default: /* Multiple matches. */ + *redrawp = 1; + if (txt_fc_col(sp, argc, argv)) + return (1); + + /* Find the length of the shortest match. */ + for (nlen = cmd.argv[0]->len; --argc > 0;) { + if (cmd.argv[argc]->len < nlen) + nlen = cmd.argv[argc]->len; + for (indx = 0; indx < nlen && + cmd.argv[argc]->bp[indx] == cmd.argv[0]->bp[indx]; + ++indx); + nlen = indx; + } + break; + } + + /* Overwrite the expanded text first. */ + for (t = cmd.argv[0]->bp; len > 0 && nlen > 0; --len, --nlen) + *p++ = *t++; + + /* If lost text, make the remaining old text overwrite characters. */ + if (len) { + tp->cno -= len; + tp->owrite += len; + } + + /* Overwrite any overwrite characters next. */ + for (; nlen > 0 && tp->owrite > 0; --nlen, --tp->owrite, ++tp->cno) + *p++ = *t++; + + /* Shift remaining text up, and move the cursor to the end. */ + if (nlen) { + off = p - tp->lb; + BINC_RET(sp, tp->lb, tp->lb_len, tp->len + nlen); + p = tp->lb + off; + + tp->cno += nlen; + tp->len += nlen; + + if (tp->insert != 0) + (void)memmove(p + nlen, p, tp->insert); + while (nlen--) + *p++ = *t++; + } + + /* If a single match and it's a directory, retry it. */ + if (argc == 1 && !stat(cmd.argv[0]->bp, &sb) && S_ISDIR(sb.st_mode)) { +isdir: if (tp->owrite == 0) { + off = p - tp->lb; + BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1); + p = tp->lb + off; + if (tp->insert != 0) + (void)memmove(p + 1, p, tp->insert); + ++tp->len; + } else + --tp->owrite; + + ++tp->cno; + *p++ = '/'; + + trydir = 1; + goto retry; + } + return (0); +} + +/* + * txt_fc_col -- + * Display file names for file name completion. + */ +static int +txt_fc_col(sp, argc, argv) + SCR *sp; + int argc; + ARGS **argv; +{ + ARGS **av; + CHAR_T *p; + GS *gp; + size_t base, cnt, col, colwidth, numrows, numcols, prefix, row; + int ac, nf, reset; + + gp = sp->gp; + + /* Trim any directory prefix common to all of the files. */ + if ((p = strrchr(argv[0]->bp, '/')) == NULL) + prefix = 0; + else { + prefix = (p - argv[0]->bp) + 1; + for (ac = argc - 1, av = argv + 1; ac > 0; --ac, ++av) + if (av[0]->len < prefix || + memcmp(av[0]->bp, argv[0]->bp, prefix)) { + prefix = 0; + break; + } + } + + /* + * Figure out the column width for the longest name. Output is done on + * 6 character "tab" boundaries for no particular reason. (Since we + * don't output tab characters, we ignore the terminal's tab settings.) + * Ignore the user's tab setting because we have no idea how reasonable + * it is. + */ + for (ac = argc, av = argv, colwidth = 0; ac > 0; --ac, ++av) { + for (col = 0, p = av[0]->bp + prefix; *p != '\0'; ++p) + col += KEY_LEN(sp, *p); + if (col > colwidth) + colwidth = col; + } + colwidth += COL_OFF(colwidth, 6); + + /* + * Writing to the bottom line of the screen is always turned off when + * SC_TINPUT_INFO is set. Turn it back on, we know what we're doing. + */ + if (F_ISSET(sp, SC_TINPUT_INFO)) { + reset = 1; + F_CLR(sp, SC_TINPUT_INFO); + } else + reset = 0; + +#define CHK_INTR \ + if (F_ISSET(gp, G_INTERRUPTED)) \ + goto intr; + + /* If the largest file name is too large, just print them. */ + if (colwidth > sp->cols) { + p = msg_print(sp, av[0]->bp + prefix, &nf); + for (ac = argc, av = argv; ac > 0; --ac, ++av) { + (void)ex_printf(sp, "%s\n", p); + if (F_ISSET(gp, G_INTERRUPTED)) + break; + } + if (nf) + FREE_SPACE(sp, p, 0); + CHK_INTR; + } else { + /* Figure out the number of columns. */ + numcols = (sp->cols - 1) / colwidth; + if (argc > numcols) { + numrows = argc / numcols; + if (argc % numcols) + ++numrows; + } else + numrows = 1; + + /* Display the files in sorted order. */ + for (row = 0; row < numrows; ++row) { + for (base = row, col = 0; col < numcols; ++col) { + p = msg_print(sp, argv[base]->bp + prefix, &nf); + cnt = ex_printf(sp, "%s", p); + if (nf) + FREE_SPACE(sp, p, 0); + CHK_INTR; + if ((base += numrows) >= argc) + break; + (void)ex_printf(sp, + "%*s", (int)(colwidth - cnt), ""); + CHK_INTR; + } + (void)ex_puts(sp, "\n"); + CHK_INTR; + } + (void)ex_puts(sp, "\n"); + CHK_INTR; + } + (void)ex_fflush(sp); + + if (0) { +intr: F_CLR(gp, G_INTERRUPTED); + } + if (reset) + F_SET(sp, SC_TINPUT_INFO); + + return (0); +} + +/* + * txt_emark -- + * Set the end mark on the line. + */ +static int +txt_emark(sp, tp, cno) + SCR *sp; + TEXT *tp; + size_t cno; +{ + CHAR_T ch, *kp; + size_t chlen, nlen, olen; + char *p; + + ch = CH_ENDMARK; + + /* + * The end mark may not be the same size as the current character. + * Don't let the line shift. + */ + nlen = KEY_LEN(sp, ch); + if (tp->lb[cno] == '\t') + (void)vs_columns(sp, tp->lb, tp->lno, &cno, &olen); + else + olen = KEY_LEN(sp, tp->lb[cno]); + + /* + * If the line got longer, well, it's weird, but it's easy. If + * it's the same length, it's easy. If it got shorter, we have + * to fix it up. + */ + if (olen > nlen) { + BINC_RET(sp, tp->lb, tp->lb_len, tp->len + olen); + chlen = olen - nlen; + if (tp->insert != 0) + memmove(tp->lb + cno + 1 + chlen, + tp->lb + cno + 1, tp->insert); + + tp->len += chlen; + tp->owrite += chlen; + p = tp->lb + cno; + if (tp->lb[cno] == '\t') + for (cno += chlen; chlen--;) + *p++ = ' '; + else + for (kp = KEY_NAME(sp, tp->lb[cno]), + cno += chlen; chlen--;) + *p++ = *kp++; + } + tp->lb[cno] = ch; + return (vs_change(sp, tp->lno, LINE_RESET)); +} + +/* + * txt_err -- + * Handle an error during input processing. + */ +static void +txt_err(sp, tiqh) + SCR *sp; + TEXTH *tiqh; +{ + recno_t lno; + + /* + * The problem with input processing is that the cursor is at an + * indeterminate position since some input may have been lost due + * to a malloc error. So, try to go back to the place from which + * the cursor started, knowing that it may no longer be available. + * + * We depend on at least one line number being set in the text + * chain. + */ + for (lno = tiqh->cqh_first->lno; + !db_exist(sp, lno) && lno > 0; --lno); + + sp->lno = lno == 0 ? 1 : lno; + sp->cno = 0; + + /* Redraw the screen, just in case. */ + F_SET(sp, SC_SCR_REDRAW); +} + +/* + * txt_hex -- + * Let the user insert any character value they want. + * + * !!! + * This is an extension. The pattern "^X[0-9a-fA-F]*" is a way + * for the user to specify a character value which their keyboard + * may not be able to enter. + */ +static int +txt_hex(sp, tp) + SCR *sp; + TEXT *tp; +{ + CHAR_T savec; + size_t len, off; + u_long value; + char *p, *wp; + + /* + * Null-terminate the string. Since nul isn't a legal hex value, + * this should be okay, and lets us use a local routine, which + * presumably understands the character set, to convert the value. + */ + savec = tp->lb[tp->cno]; + tp->lb[tp->cno] = 0; + + /* Find the previous CH_HEX character. */ + for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --p, --off, ++len) { + if (*p == CH_HEX) { + wp = p + 1; + break; + } + /* Not on this line? Shouldn't happen. */ + if (off == tp->ai || off == tp->offset) + goto nothex; + } + + /* If length of 0, then it wasn't a hex value. */ + if (len == 0) + goto nothex; + + /* Get the value. */ + errno = 0; + value = strtol(wp, NULL, 16); + if (errno || value > MAX_CHAR_T) { +nothex: tp->lb[tp->cno] = savec; + return (0); + } + + /* Restore the original character. */ + tp->lb[tp->cno] = savec; + + /* Adjust the bookkeeping. */ + tp->cno -= len; + tp->len -= len; + tp->lb[tp->cno - 1] = value; + + /* Copy down any overwrite characters. */ + if (tp->owrite) + memmove(tp->lb + tp->cno, tp->lb + tp->cno + len, tp->owrite); + + /* Copy down any insert characters. */ + if (tp->insert) + memmove(tp->lb + tp->cno + tp->owrite, + tp->lb + tp->cno + tp->owrite + len, tp->insert); + + return (0); +} + +/* + * txt_insch -- + * + * !!! + * Historic vi did a special screen optimization for tab characters. As an + * example, for the keystrokes "iabcd<esc>0C<tab>", the tab overwrote the + * rest of the string when it was displayed. + * + * Because early versions of this implementation redisplayed the entire line + * on each keystroke, the "bcd" was pushed to the right as it ignored that + * the user had "promised" to change the rest of the characters. However, + * the historic vi implementation had an even worse bug: given the keystrokes + * "iabcd<esc>0R<tab><esc>", the "bcd" disappears, and magically reappears + * on the second <esc> key. + * + * POSIX 1003.2 requires (will require) that this be fixed, specifying that + * vi overwrite characters the user has committed to changing, on the basis + * of the screen space they require, but that it not overwrite other characters. + */ +static int +txt_insch(sp, tp, chp, flags) + SCR *sp; + TEXT *tp; + CHAR_T *chp; + u_int flags; +{ + CHAR_T *kp, savech; + size_t chlen, cno, copydown, olen, nlen; + char *p; + + /* + * The 'R' command does one-for-one replacement, because there's + * no way to know how many characters the user intends to replace. + */ + if (LF_ISSET(TXT_REPLACE)) { + if (tp->owrite) { + --tp->owrite; + tp->lb[tp->cno++] = *chp; + return (0); + } + } else if (tp->owrite) { /* Overwrite a character. */ + cno = tp->cno; + + /* + * If the old or new characters are tabs, then the length of the + * display depends on the character position in the display. We + * don't even try to handle this here, just ask the screen. + */ + if (*chp == '\t') { + savech = tp->lb[cno]; + tp->lb[cno] = '\t'; + (void)vs_columns(sp, tp->lb, tp->lno, &cno, &nlen); + tp->lb[cno] = savech; + } else + nlen = KEY_LEN(sp, *chp); + + /* + * Eat overwrite characters until we run out of them or we've + * handled the length of the new character. If we only eat + * part of an overwrite character, break it into its component + * elements and display the remaining components. + */ + for (copydown = 0; nlen != 0 && tp->owrite != 0;) { + --tp->owrite; + + if (tp->lb[cno] == '\t') + (void)vs_columns(sp, + tp->lb, tp->lno, &cno, &olen); + else + olen = KEY_LEN(sp, tp->lb[cno]); + + if (olen == nlen) { + nlen = 0; + break; + } + if (olen < nlen) { + ++copydown; + nlen -= olen; + } else { + BINC_RET(sp, + tp->lb, tp->lb_len, tp->len + olen); + chlen = olen - nlen; + memmove(tp->lb + cno + 1 + chlen, + tp->lb + cno + 1, tp->owrite + tp->insert); + + tp->len += chlen; + tp->owrite += chlen; + if (tp->lb[cno] == '\t') + for (p = tp->lb + cno + 1; chlen--;) + *p++ = ' '; + else + for (kp = + KEY_NAME(sp, tp->lb[cno]) + nlen, + p = tp->lb + cno + 1; chlen--;) + *p++ = *kp++; + nlen = 0; + break; + } + } + + /* + * If had to erase several characters, we adjust the total + * count, and if there are any characters left, shift them + * into position. + */ + if (copydown != 0 && (tp->len -= copydown) != 0) + memmove(tp->lb + cno, tp->lb + cno + copydown, + tp->owrite + tp->insert + copydown); + + /* If we had enough overwrite characters, we're done. */ + if (nlen == 0) { + tp->lb[tp->cno++] = *chp; + return (0); + } + } + + /* Check to see if the character fits into the input buffer. */ + BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1); + + ++tp->len; + if (tp->insert) { /* Insert a character. */ + if (tp->insert == 1) + tp->lb[tp->cno + 1] = tp->lb[tp->cno]; + else + memmove(tp->lb + tp->cno + 1, + tp->lb + tp->cno, tp->owrite + tp->insert); + } + tp->lb[tp->cno++] = *chp; + return (0); +} + +/* + * txt_isrch -- + * Do an incremental search. + */ +static int +txt_isrch(sp, vp, tp, is_flagsp) + SCR *sp; + VICMD *vp; + TEXT *tp; + u_int8_t *is_flagsp; +{ + MARK start; + recno_t lno; + u_int sf; + + /* If it's a one-line screen, we don't do incrementals. */ + if (IS_ONELINE(sp)) { + FL_CLR(*is_flagsp, IS_RUNNING); + return (0); + } + + /* + * If the user erases back to the beginning of the buffer, there's + * nothing to search for. Reset the cursor to the starting point. + */ + if (tp->cno <= 1) { + vp->m_final = vp->m_start; + return (0); + } + + /* + * If it's an RE quote character, and not quoted, ignore it until + * we get another character. + */ + if (tp->lb[tp->cno - 1] == '\\' && + (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\')) + return (0); + + /* + * If it's a magic shell character, and not quoted, reset the cursor + * to the starting point. + */ + if (strchr(O_STR(sp, O_SHELLMETA), tp->lb[tp->cno - 1]) != NULL && + (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\')) + vp->m_final = vp->m_start; + + /* + * If we see the search pattern termination character, then quit doing + * an incremental search. There may be more, e.g., ":/foo/;/bar/", + * and we can't handle that incrementally. Also, reset the cursor to + * the original location, the ex search routines don't know anything + * about incremental searches. + */ + if (tp->lb[0] == tp->lb[tp->cno - 1] && + (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\')) { + vp->m_final = vp->m_start; + FL_CLR(*is_flagsp, IS_RUNNING); + return (0); + } + + /* + * Remember the input line and discard the special input map, + * but don't overwrite the input line on the screen. + */ + lno = tp->lno; + F_SET(VIP(sp), VIP_S_MODELINE); + F_CLR(sp, SC_TINPUT | SC_TINPUT_INFO); + if (txt_map_end(sp)) + return (1); + + /* + * Specify a starting point and search. If we find a match, move to + * it and refresh the screen. If we didn't find the match, then we + * beep the screen. When searching from the original cursor position, + * we have to move the cursor, otherwise, we don't want to move the + * cursor in case the text at the current position continues to match. + */ + if (FL_ISSET(*is_flagsp, IS_RESTART)) { + start = vp->m_start; + sf = SEARCH_SET; + } else { + start = vp->m_final; + sf = SEARCH_INCR | SEARCH_SET; + } + + if (tp->lb[0] == '/' ? + !f_search(sp, + &start, &vp->m_final, tp->lb + 1, tp->cno - 1, NULL, sf) : + !b_search(sp, + &start, &vp->m_final, tp->lb + 1, tp->cno - 1, NULL, sf)) { + sp->lno = vp->m_final.lno; + sp->cno = vp->m_final.cno; + FL_CLR(*is_flagsp, IS_RESTART); + + if (!KEYS_WAITING(sp) && vs_refresh(sp, 0)) + return (1); + } else + FL_SET(*is_flagsp, IS_RESTART); + + /* Reinstantiate the special input map. */ + if (txt_map_init(sp)) + return (1); + F_CLR(VIP(sp), VIP_S_MODELINE); + F_SET(sp, SC_TINPUT | SC_TINPUT_INFO); + + /* Reset the line number of the input line. */ + tp->lno = TMAP[0].lno; + + /* + * If the colon command-line moved, i.e. the screen scrolled, + * refresh the input line. + * + * XXX + * We shouldn't be calling vs_line, here -- we need dirty bits + * on entries in the SMAP array. + */ + if (lno != TMAP[0].lno) { + if (vs_line(sp, &TMAP[0], NULL, NULL)) + return (1); + (void)sp->gp->scr_refresh(sp, 0); + } + return (0); +} + +/* + * txt_resolve -- + * Resolve the input text chain into the file. + */ +static int +txt_resolve(sp, tiqh, flags) + SCR *sp; + TEXTH *tiqh; + u_int32_t flags; +{ + VI_PRIVATE *vip; + TEXT *tp; + recno_t lno; + int changed; + + /* + * The first line replaces a current line, and all subsequent lines + * are appended into the file. Resolve autoindented characters for + * each line before committing it. If the latter causes the line to + * change, we have to redisplay it, otherwise the information cached + * about the line will be wrong. + */ + vip = VIP(sp); + tp = tiqh->cqh_first; + + if (LF_ISSET(TXT_AUTOINDENT)) + txt_ai_resolve(sp, tp, &changed); + else + changed = 0; + if (db_set(sp, tp->lno, tp->lb, tp->len) || + changed && vs_change(sp, tp->lno, LINE_RESET)) + return (1); + + for (lno = tp->lno; (tp = tp->q.cqe_next) != (void *)&sp->tiq; ++lno) { + if (LF_ISSET(TXT_AUTOINDENT)) + txt_ai_resolve(sp, tp, &changed); + else + changed = 0; + if (db_append(sp, 0, lno, tp->lb, tp->len) || + changed && vs_change(sp, tp->lno, LINE_RESET)) + return (1); + } + + /* + * Clear the input flag, the look-aside buffer is no longer valid. + * Has to be done as part of text resolution, or upon return we'll + * be looking at incorrect data. + */ + F_CLR(sp, SC_TINPUT); + + return (0); +} + +/* + * txt_showmatch -- + * Show a character match. + * + * !!! + * Historic vi tried to display matches even in the :colon command line. + * I think not. + */ +static int +txt_showmatch(sp, tp) + SCR *sp; + TEXT *tp; +{ + GS *gp; + VCS cs; + MARK m; + int cnt, endc, startc; + + gp = sp->gp; + + /* + * Do a refresh first, in case we haven't done one in awhile, + * so the user can see what we're complaining about. + */ + UPDATE_POSITION(sp, tp); + if (vs_refresh(sp, 1)) + return (1); + + /* + * We don't display the match if it's not on the screen. Find + * out what the first character on the screen is. + */ + if (vs_sm_position(sp, &m, 0, P_TOP)) + return (1); + + /* Initialize the getc() interface. */ + cs.cs_lno = tp->lno; + cs.cs_cno = tp->cno - 1; + if (cs_init(sp, &cs)) + return (1); + startc = (endc = cs.cs_ch) == ')' ? '(' : '{'; + + /* Search for the match. */ + for (cnt = 1;;) { + if (cs_prev(sp, &cs)) + return (1); + if (cs.cs_flags != 0) { + if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) { + msgq(sp, M_BERR, + "Unmatched %s", KEY_NAME(sp, endc)); + return (0); + } + continue; + } + if (cs.cs_ch == endc) + ++cnt; + else if (cs.cs_ch == startc && --cnt == 0) + break; + } + + /* If the match is on the screen, move to it. */ + if (cs.cs_lno < m.lno || cs.cs_lno == m.lno && cs.cs_cno < m.cno) + return (0); + sp->lno = cs.cs_lno; + sp->cno = cs.cs_cno; + if (vs_refresh(sp, 1)) + return (1); + + /* Wait for timeout or character arrival. */ + return (v_event_get(sp, + NULL, O_VAL(sp, O_MATCHTIME) * 100, EC_TIMEOUT)); +} + +/* + * txt_margin -- + * Handle margin wrap. + */ +static int +txt_margin(sp, tp, wmtp, didbreak, flags) + SCR *sp; + TEXT *tp, *wmtp; + int *didbreak; + u_int32_t flags; +{ + VI_PRIVATE *vip; + size_t len, off; + char *p, *wp; + + /* Find the nearest previous blank. */ + for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --off, --p, ++len) { + if (isblank(*p)) { + wp = p + 1; + break; + } + + /* + * If reach the start of the line, there's nowhere to break. + * + * !!! + * Historic vi belled each time a character was entered after + * crossing the margin until a space was entered which could + * be used to break the line. I don't as it tends to wake the + * cats. + */ + if (off == tp->ai || off == tp->offset) { + *didbreak = 0; + return (0); + } + } + + /* + * Store saved information about the rest of the line in the + * wrapmargin TEXT structure. + * + * !!! + * The offset field holds the length of the current characters + * that the user entered, but which are getting split to the new + * line -- it's going to be used to set the cursor value when we + * move to the new line. + */ + vip = VIP(sp); + wmtp->lb = p + 1; + wmtp->offset = len; + wmtp->insert = LF_ISSET(TXT_APPENDEOL) ? tp->insert - 1 : tp->insert; + wmtp->owrite = tp->owrite; + + /* Correct current bookkeeping information. */ + tp->cno -= len; + if (LF_ISSET(TXT_APPENDEOL)) { + tp->len -= len + tp->owrite + (tp->insert - 1); + tp->insert = 1; + } else { + tp->len -= len + tp->owrite + tp->insert; + tp->insert = 0; + } + tp->owrite = 0; + + /* + * !!! + * Delete any trailing whitespace from the current line. + */ + for (;; --p, --off) { + if (!isblank(*p)) + break; + --tp->cno; + --tp->len; + if (off == tp->ai || off == tp->offset) + break; + } + *didbreak = 1; + return (0); +} + +/* + * txt_Rresolve -- + * Resolve the input line for the 'R' command. + */ +static void +txt_Rresolve(sp, tiqh, tp, orig_len) + SCR *sp; + TEXTH *tiqh; + TEXT *tp; + const size_t orig_len; +{ + TEXT *ttp; + size_t input_len, retain; + char *p; + + /* + * Check to make sure that the cursor hasn't moved beyond + * the end of the line. + */ + if (tp->owrite == 0) + return; + + /* + * Calculate how many characters the user has entered, + * plus the blanks erased by <carriage-return>/<newline>s. + */ + for (ttp = tiqh->cqh_first, input_len = 0;;) { + input_len += ttp == tp ? tp->cno : ttp->len + ttp->R_erase; + if ((ttp = ttp->q.cqe_next) == (void *)&sp->tiq) + break; + } + + /* + * If the user has entered less characters than the original line + * was long, restore any overwriteable characters to the original + * characters. These characters are entered as "insert characters", + * because they're after the cursor and we don't want to lose them. + * (This is okay because the R command has no insert characters.) + * We set owrite to 0 so that the insert characters don't get copied + * to somewhere else, which means that the line and the length have + * to be adjusted here as well. + * + * We have to retrieve the original line because the original pinned + * page has long since been discarded. If it doesn't exist, that's + * okay, the user just extended the file. + */ + if (input_len < orig_len) { + retain = MIN(tp->owrite, orig_len - input_len); + if (db_get(sp, + tiqh->cqh_first->lno, DBG_FATAL | DBG_NOCACHE, &p, NULL)) + return; + memcpy(tp->lb + tp->cno, p + input_len, retain); + tp->len -= tp->owrite - retain; + tp->owrite = 0; + tp->insert += retain; + } +} + +/* + * txt_nomorech -- + * No more characters message. + */ +static void +txt_nomorech(sp) + SCR *sp; +{ + msgq(sp, M_BERR, "194|No more characters to erase"); +} diff --git a/contrib/nvi/vi/v_ulcase.c b/contrib/nvi/vi/v_ulcase.c new file mode 100644 index 0000000..179beba --- /dev/null +++ b/contrib/nvi/vi/v_ulcase.c @@ -0,0 +1,179 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_ulcase.c 10.7 (Berkeley) 3/6/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +static int ulcase __P((SCR *, recno_t, CHAR_T *, size_t, size_t, size_t)); + +/* + * v_ulcase -- [count]~ + * Toggle upper & lower case letters. + * + * !!! + * Historic vi didn't permit ~ to cross newline boundaries. I can + * think of no reason why it shouldn't, which at least lets the user + * auto-repeat through a paragraph. + * + * !!! + * In historic vi, the count was ignored. It would have been better + * if there had been an associated motion, but it's too late to make + * that the default now. + * + * PUBLIC: int v_ulcase __P((SCR *, VICMD *)); + */ +int +v_ulcase(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t lno; + size_t cno, lcnt, len; + u_long cnt; + char *p; + + lno = vp->m_start.lno; + cno = vp->m_start.cno; + + for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt > 0; cno = 0) { + /* SOF is an error, EOF is an infinite count sink. */ + if (db_get(sp, lno, 0, &p, &len)) { + if (lno == 1) { + v_emsg(sp, NULL, VIM_EMPTY); + return (1); + } + --lno; + break; + } + + /* Empty lines decrement the count by one. */ + if (len == 0) { + --cnt; + vp->m_final.cno = 0; + continue; + } + + if (cno + cnt >= len) { + lcnt = len - 1; + cnt -= len - cno; + + vp->m_final.cno = len - 1; + } else { + lcnt = cno + cnt - 1; + cnt = 0; + + vp->m_final.cno = lcnt + 1; + } + + if (ulcase(sp, lno, p, len, cno, lcnt)) + return (1); + + if (cnt > 0) + ++lno; + } + + vp->m_final.lno = lno; + return (0); +} + +/* + * v_mulcase -- [count]~[count]motion + * Toggle upper & lower case letters over a range. + * + * PUBLIC: int v_mulcase __P((SCR *, VICMD *)); + */ +int +v_mulcase(sp, vp) + SCR *sp; + VICMD *vp; +{ + CHAR_T *p; + size_t len; + recno_t lno; + + for (lno = vp->m_start.lno;;) { + if (db_get(sp, lno, DBG_FATAL, &p, &len)) + return (1); + if (len != 0 && ulcase(sp, lno, p, len, + lno == vp->m_start.lno ? vp->m_start.cno : 0, + !F_ISSET(vp, VM_LMODE) && + lno == vp->m_stop.lno ? vp->m_stop.cno : len)) + return (1); + + if (++lno > vp->m_stop.lno) + break; + } + + /* + * XXX + * I didn't create a new motion command when I added motion semantics + * for ~. While that's the correct way to do it, that choice would + * have required changes all over the vi directory for little gain. + * Instead, we pretend it's a yank command. Note, this means that we + * follow the cursor motion rules for yank commands, but that seems + * reasonable to me. + */ + return (0); +} + +/* + * ulcase -- + * Change part of a line's case. + */ +static int +ulcase(sp, lno, lp, len, scno, ecno) + SCR *sp; + recno_t lno; + CHAR_T *lp; + size_t len, scno, ecno; +{ + size_t blen; + int change, rval; + CHAR_T ch, *p, *t; + char *bp; + + GET_SPACE_RET(sp, bp, blen, len); + memmove(bp, lp, len); + + change = rval = 0; + for (p = bp + scno, t = bp + ecno + 1; p < t; ++p) { + ch = *(u_char *)p; + if (islower(ch)) { + *p = toupper(ch); + change = 1; + } else if (isupper(ch)) { + *p = tolower(ch); + change = 1; + } + } + + if (change && db_set(sp, lno, bp, len)) + rval = 1; + + FREE_SPACE(sp, bp, blen); + return (rval); +} diff --git a/contrib/nvi/vi/v_undo.c b/contrib/nvi/vi/v_undo.c new file mode 100644 index 0000000..d04a8a1 --- /dev/null +++ b/contrib/nvi/vi/v_undo.c @@ -0,0 +1,139 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_undo.c 10.5 (Berkeley) 3/6/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_Undo -- U + * Undo changes to this line. + * + * PUBLIC: int v_Undo __P((SCR *, VICMD *)); + */ +int +v_Undo(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* + * Historically, U reset the cursor to the first column in the line + * (not the first non-blank). This seems a bit non-intuitive, but, + * considering that we may have undone multiple changes, anything + * else (including the cursor position stored in the logging records) + * is going to appear random. + */ + vp->m_final.cno = 0; + + /* + * !!! + * Set up the flags so that an immediately subsequent 'u' will roll + * forward, instead of backward. In historic vi, a 'u' following a + * 'U' redid all of the changes to the line. Given that the user has + * explicitly discarded those changes by entering 'U', it seems likely + * that the user wants something between the original and end forms of + * the line, so starting to replay the changes seems the best way to + * get to there. + */ + F_SET(sp->ep, F_UNDO); + sp->ep->lundo = BACKWARD; + + return (log_setline(sp)); +} + +/* + * v_undo -- u + * Undo the last change. + * + * PUBLIC: int v_undo __P((SCR *, VICMD *)); + */ +int +v_undo(sp, vp) + SCR *sp; + VICMD *vp; +{ + EXF *ep; + + /* Set the command count. */ + VIP(sp)->u_ccnt = sp->ccnt; + + /* + * !!! + * In historic vi, 'u' toggled between "undo" and "redo", i.e. 'u' + * undid the last undo. However, if there has been a change since + * the last undo/redo, we always do an undo. To make this work when + * the user can undo multiple operations, we leave the old semantic + * unchanged, but make '.' after a 'u' do another undo/redo operation. + * This has two problems. + * + * The first is that 'u' didn't set '.' in historic vi. So, if a + * user made a change, realized it was in the wrong place, does a + * 'u' to undo it, moves to the right place and then does '.', the + * change was reapplied. To make this work, we only apply the '.' + * to the undo command if it's the command immediately following an + * undo command. See vi/vi.c:getcmd() for the details. + * + * The second is that the traditional way to view the numbered cut + * buffers in vi was to enter the commands "1pu.u.u.u. which will + * no longer work because the '.' immediately follows the 'u' command. + * Since we provide a much better method of viewing buffers, and + * nobody can think of a better way of adding in multiple undo, this + * remains broken. + * + * !!! + * There is change to historic practice for the final cursor position + * in this implementation. In historic vi, if an undo was isolated to + * a single line, the cursor moved to the start of the change, and + * then, subsequent 'u' commands would not move it again. (It has been + * pointed out that users used multiple undo commands to get the cursor + * to the start of the changed text.) Nvi toggles between the cursor + * position before and after the change was made. One final issue is + * that historic vi only did this if the user had not moved off of the + * line before entering the undo command; otherwise, vi would move the + * cursor to the most attractive position on the changed line. + * + * It would be difficult to match historic practice in this area. You + * not only have to know that the changes were isolated to one line, + * but whether it was the first or second undo command as well. And, + * to completely match historic practice, we'd have to track users line + * changes, too. This isn't worth the effort. + */ + ep = sp->ep; + if (!F_ISSET(ep, F_UNDO)) { + F_SET(ep, F_UNDO); + ep->lundo = BACKWARD; + } else if (!F_ISSET(vp, VC_ISDOT)) + ep->lundo = ep->lundo == BACKWARD ? FORWARD : BACKWARD; + + switch (ep->lundo) { + case BACKWARD: + return (log_backward(sp, &vp->m_final)); + case FORWARD: + return (log_forward(sp, &vp->m_final)); + default: + abort(); + } + /* NOTREACHED */ +} diff --git a/contrib/nvi/vi/v_util.c b/contrib/nvi/vi/v_util.c new file mode 100644 index 0000000..c4c0daa --- /dev/null +++ b/contrib/nvi/vi/v_util.c @@ -0,0 +1,180 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_util.c 10.11 (Berkeley) 6/30/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_eof -- + * Vi end-of-file error. + * + * PUBLIC: void v_eof __P((SCR *, MARK *)); + */ +void +v_eof(sp, mp) + SCR *sp; + MARK *mp; +{ + recno_t lno; + + if (mp == NULL) + v_emsg(sp, NULL, VIM_EOF); + else { + if (db_last(sp, &lno)) + return; + if (mp->lno >= lno) + v_emsg(sp, NULL, VIM_EOF); + else + msgq(sp, M_BERR, "195|Movement past the end-of-file"); + } +} + +/* + * v_eol -- + * Vi end-of-line error. + * + * PUBLIC: void v_eol __P((SCR *, MARK *)); + */ +void +v_eol(sp, mp) + SCR *sp; + MARK *mp; +{ + size_t len; + + if (mp == NULL) + v_emsg(sp, NULL, VIM_EOL); + else { + if (db_get(sp, mp->lno, DBG_FATAL, NULL, &len)) + return; + if (mp->cno == len - 1) + v_emsg(sp, NULL, VIM_EOL); + else + msgq(sp, M_BERR, "196|Movement past the end-of-line"); + } +} + +/* + * v_nomove -- + * Vi no cursor movement error. + * + * PUBLIC: void v_nomove __P((SCR *)); + */ +void +v_nomove(sp) + SCR *sp; +{ + msgq(sp, M_BERR, "197|No cursor movement made"); +} + +/* + * v_sof -- + * Vi start-of-file error. + * + * PUBLIC: void v_sof __P((SCR *, MARK *)); + */ +void +v_sof(sp, mp) + SCR *sp; + MARK *mp; +{ + if (mp == NULL || mp->lno == 1) + msgq(sp, M_BERR, "198|Already at the beginning of the file"); + else + msgq(sp, M_BERR, "199|Movement past the beginning of the file"); +} + +/* + * v_sol -- + * Vi start-of-line error. + * + * PUBLIC: void v_sol __P((SCR *)); + */ +void +v_sol(sp) + SCR *sp; +{ + msgq(sp, M_BERR, "200|Already in the first column"); +} + +/* + * v_isempty -- + * Return if the line contains nothing but white-space characters. + * + * PUBLIC: int v_isempty __P((char *, size_t)); + */ +int +v_isempty(p, len) + char *p; + size_t len; +{ + for (; len--; ++p) + if (!isblank(*p)) + return (0); + return (1); +} + +/* + * v_emsg -- + * Display a few common vi messages. + * + * PUBLIC: void v_emsg __P((SCR *, char *, vim_t)); + */ +void +v_emsg(sp, p, which) + SCR *sp; + char *p; + vim_t which; +{ + switch (which) { + case VIM_COMBUF: + msgq(sp, M_ERR, + "201|Buffers should be specified before the command"); + break; + case VIM_EMPTY: + msgq(sp, M_BERR, "209|The file is empty"); + break; + case VIM_EOF: + msgq(sp, M_BERR, "202|Already at end-of-file"); + break; + case VIM_EOL: + msgq(sp, M_BERR, "203|Already at end-of-line"); + break; + case VIM_NOCOM: + case VIM_NOCOM_B: + msgq(sp, + which == VIM_NOCOM_B ? M_BERR : M_ERR, + "204|%s isn't a vi command", p); + break; + case VIM_WRESIZE: + msgq(sp, M_ERR, "Window resize interrupted text input mode"); + break; + case VIM_USAGE: + msgq(sp, M_ERR, "205|Usage: %s", p); + break; + } +} diff --git a/contrib/nvi/vi/v_word.c b/contrib/nvi/vi/v_word.c new file mode 100644 index 0000000..e6e4a63 --- /dev/null +++ b/contrib/nvi/vi/v_word.c @@ -0,0 +1,547 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_word.c 10.5 (Berkeley) 3/6/96"; +#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 <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * There are two types of "words". Bigwords are easy -- groups of anything + * delimited by whitespace. Normal words are trickier. They are either a + * group of characters, numbers and underscores, or a group of anything but, + * delimited by whitespace. When for a word, if you're in whitespace, it's + * easy, just remove the whitespace and go to the beginning or end of the + * word. Otherwise, figure out if the next character is in a different group. + * If it is, go to the beginning or end of that group, otherwise, go to the + * beginning or end of the current group. The historic version of vi didn't + * get this right, so, for example, there were cases where "4e" was not the + * same as "eeee" -- in particular, single character words, and commands that + * began in whitespace were almost always handled incorrectly. To get it right + * you have to resolve the cursor after each search so that the look-ahead to + * figure out what type of "word" the cursor is in will be correct. + * + * Empty lines, and lines that consist of only white-space characters count + * as a single word, and the beginning and end of the file counts as an + * infinite number of words. + * + * Movements associated with commands are different than movement commands. + * For example, in "abc def", with the cursor on the 'a', "cw" is from + * 'a' to 'c', while "w" is from 'a' to 'd'. In general, trailing white + * space is discarded from the change movement. Another example is that, + * in the same string, a "cw" on any white space character replaces that + * single character, and nothing else. Ain't nothin' in here that's easy. + * + * One historic note -- in the original vi, the 'w', 'W' and 'B' commands + * would treat groups of empty lines as individual words, i.e. the command + * would move the cursor to each new empty line. The 'e' and 'E' commands + * would treat groups of empty lines as a single word, i.e. the first use + * would move past the group of lines. The 'b' command would just beep at + * you, or, if you did it from the start of the line as part of a motion + * command, go absolutely nuts. If the lines contained only white-space + * characters, the 'w' and 'W' commands would just beep at you, and the 'B', + * 'b', 'E' and 'e' commands would treat the group as a single word, and + * the 'B' and 'b' commands will treat the lines as individual words. This + * implementation treats all of these cases as a single white-space word. + */ + +enum which {BIGWORD, LITTLEWORD}; + +static int bword __P((SCR *, VICMD *, enum which)); +static int eword __P((SCR *, VICMD *, enum which)); +static int fword __P((SCR *, VICMD *, enum which)); + +/* + * v_wordW -- [count]W + * Move forward a bigword at a time. + * + * PUBLIC: int v_wordW __P((SCR *, VICMD *)); + */ +int +v_wordW(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (fword(sp, vp, BIGWORD)); +} + +/* + * v_wordw -- [count]w + * Move forward a word at a time. + * + * PUBLIC: int v_wordw __P((SCR *, VICMD *)); + */ +int +v_wordw(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (fword(sp, vp, LITTLEWORD)); +} + +/* + * fword -- + * Move forward by words. + */ +static int +fword(sp, vp, type) + SCR *sp; + VICMD *vp; + enum which type; +{ + enum { INWORD, NOTWORD } state; + VCS cs; + u_long cnt; + + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + cs.cs_lno = vp->m_start.lno; + cs.cs_cno = vp->m_start.cno; + if (cs_init(sp, &cs)) + return (1); + + /* + * If in white-space: + * If the count is 1, and it's a change command, we're done. + * Else, move to the first non-white-space character, which + * counts as a single word move. If it's a motion command, + * don't move off the end of the line. + */ + if (cs.cs_flags == CS_EMP || cs.cs_flags == 0 && isblank(cs.cs_ch)) { + if (ISMOTION(vp) && cs.cs_flags != CS_EMP && cnt == 1) { + if (ISCMD(vp->rkp, 'c')) + return (0); + if (ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) { + if (cs_fspace(sp, &cs)) + return (1); + goto ret; + } + } + if (cs_fblank(sp, &cs)) + return (1); + --cnt; + } + + /* + * Cyclically move to the next word -- this involves skipping + * over word characters and then any trailing non-word characters. + * Note, for the 'w' command, the definition of a word keeps + * switching. + */ + if (type == BIGWORD) + while (cnt--) { + for (;;) { + if (cs_next(sp, &cs)) + return (1); + if (cs.cs_flags == CS_EOF) + goto ret; + if (cs.cs_flags != 0 || isblank(cs.cs_ch)) + break; + } + /* + * If a motion command and we're at the end of the + * last word, we're done. Delete and yank eat any + * trailing blanks, but we don't move off the end + * of the line regardless. + */ + if (cnt == 0 && ISMOTION(vp)) { + if ((ISCMD(vp->rkp, 'd') || + ISCMD(vp->rkp, 'y')) && + cs_fspace(sp, &cs)) + return (1); + break; + } + + /* Eat whitespace characters. */ + if (cs_fblank(sp, &cs)) + return (1); + if (cs.cs_flags == CS_EOF) + goto ret; + } + else + while (cnt--) { + state = cs.cs_flags == 0 && + inword(cs.cs_ch) ? INWORD : NOTWORD; + for (;;) { + if (cs_next(sp, &cs)) + return (1); + if (cs.cs_flags == CS_EOF) + goto ret; + if (cs.cs_flags != 0 || isblank(cs.cs_ch)) + break; + if (state == INWORD) { + if (!inword(cs.cs_ch)) + break; + } else + if (inword(cs.cs_ch)) + break; + } + /* See comment above. */ + if (cnt == 0 && ISMOTION(vp)) { + if ((ISCMD(vp->rkp, 'd') || + ISCMD(vp->rkp, 'y')) && + cs_fspace(sp, &cs)) + return (1); + break; + } + + /* Eat whitespace characters. */ + if (cs.cs_flags != 0 || isblank(cs.cs_ch)) + if (cs_fblank(sp, &cs)) + return (1); + if (cs.cs_flags == CS_EOF) + goto ret; + } + + /* + * If we didn't move, we must be at EOF. + * + * !!! + * That's okay for motion commands, however. + */ +ret: if (!ISMOTION(vp) && + cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { + v_eof(sp, &vp->m_start); + return (1); + } + + /* Adjust the end of the range for motion commands. */ + vp->m_stop.lno = cs.cs_lno; + vp->m_stop.cno = cs.cs_cno; + if (ISMOTION(vp) && cs.cs_flags == 0) + --vp->m_stop.cno; + + /* + * Non-motion commands move to the end of the range. Delete + * and yank stay at the start, ignore others. + */ + vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; + return (0); +} + +/* + * v_wordE -- [count]E + * Move forward to the end of the bigword. + * + * PUBLIC: int v_wordE __P((SCR *, VICMD *)); + */ +int +v_wordE(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (eword(sp, vp, BIGWORD)); +} + +/* + * v_worde -- [count]e + * Move forward to the end of the word. + * + * PUBLIC: int v_worde __P((SCR *, VICMD *)); + */ +int +v_worde(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (eword(sp, vp, LITTLEWORD)); +} + +/* + * eword -- + * Move forward to the end of the word. + */ +static int +eword(sp, vp, type) + SCR *sp; + VICMD *vp; + enum which type; +{ + enum { INWORD, NOTWORD } state; + VCS cs; + u_long cnt; + + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + cs.cs_lno = vp->m_start.lno; + cs.cs_cno = vp->m_start.cno; + if (cs_init(sp, &cs)) + return (1); + + /* + * !!! + * If in whitespace, or the next character is whitespace, move past + * it. (This doesn't count as a word move.) Stay at the character + * past the current one, it sets word "state" for the 'e' command. + */ + if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) { + if (cs_next(sp, &cs)) + return (1); + if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) + goto start; + } + if (cs_fblank(sp, &cs)) + return (1); + + /* + * Cyclically move to the next word -- this involves skipping + * over word characters and then any trailing non-word characters. + * Note, for the 'e' command, the definition of a word keeps + * switching. + */ +start: if (type == BIGWORD) + while (cnt--) { + for (;;) { + if (cs_next(sp, &cs)) + return (1); + if (cs.cs_flags == CS_EOF) + goto ret; + if (cs.cs_flags != 0 || isblank(cs.cs_ch)) + break; + } + /* + * When we reach the start of the word after the last + * word, we're done. If we changed state, back up one + * to the end of the previous word. + */ + if (cnt == 0) { + if (cs.cs_flags == 0 && cs_prev(sp, &cs)) + return (1); + break; + } + + /* Eat whitespace characters. */ + if (cs_fblank(sp, &cs)) + return (1); + if (cs.cs_flags == CS_EOF) + goto ret; + } + else + while (cnt--) { + state = cs.cs_flags == 0 && + inword(cs.cs_ch) ? INWORD : NOTWORD; + for (;;) { + if (cs_next(sp, &cs)) + return (1); + if (cs.cs_flags == CS_EOF) + goto ret; + if (cs.cs_flags != 0 || isblank(cs.cs_ch)) + break; + if (state == INWORD) { + if (!inword(cs.cs_ch)) + break; + } else + if (inword(cs.cs_ch)) + break; + } + /* See comment above. */ + if (cnt == 0) { + if (cs.cs_flags == 0 && cs_prev(sp, &cs)) + return (1); + break; + } + + /* Eat whitespace characters. */ + if (cs.cs_flags != 0 || isblank(cs.cs_ch)) + if (cs_fblank(sp, &cs)) + return (1); + if (cs.cs_flags == CS_EOF) + goto ret; + } + + /* + * If we didn't move, we must be at EOF. + * + * !!! + * That's okay for motion commands, however. + */ +ret: if (!ISMOTION(vp) && + cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { + v_eof(sp, &vp->m_start); + return (1); + } + + /* Set the end of the range for motion commands. */ + vp->m_stop.lno = cs.cs_lno; + vp->m_stop.cno = cs.cs_cno; + + /* + * Non-motion commands move to the end of the range. + * Delete and yank stay at the start, ignore others. + */ + vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; + return (0); +} + +/* + * v_WordB -- [count]B + * Move backward a bigword at a time. + * + * PUBLIC: int v_wordB __P((SCR *, VICMD *)); + */ +int +v_wordB(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (bword(sp, vp, BIGWORD)); +} + +/* + * v_wordb -- [count]b + * Move backward a word at a time. + * + * PUBLIC: int v_wordb __P((SCR *, VICMD *)); + */ +int +v_wordb(sp, vp) + SCR *sp; + VICMD *vp; +{ + return (bword(sp, vp, LITTLEWORD)); +} + +/* + * bword -- + * Move backward by words. + */ +static int +bword(sp, vp, type) + SCR *sp; + VICMD *vp; + enum which type; +{ + enum { INWORD, NOTWORD } state; + VCS cs; + u_long cnt; + + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + cs.cs_lno = vp->m_start.lno; + cs.cs_cno = vp->m_start.cno; + if (cs_init(sp, &cs)) + return (1); + + /* + * !!! + * If in whitespace, or the previous character is whitespace, move + * past it. (This doesn't count as a word move.) Stay at the + * character before the current one, it sets word "state" for the + * 'b' command. + */ + if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) { + if (cs_prev(sp, &cs)) + return (1); + if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) + goto start; + } + if (cs_bblank(sp, &cs)) + return (1); + + /* + * Cyclically move to the beginning of the previous word -- this + * involves skipping over word characters and then any trailing + * non-word characters. Note, for the 'b' command, the definition + * of a word keeps switching. + */ +start: if (type == BIGWORD) + while (cnt--) { + for (;;) { + if (cs_prev(sp, &cs)) + return (1); + if (cs.cs_flags == CS_SOF) + goto ret; + if (cs.cs_flags != 0 || isblank(cs.cs_ch)) + break; + } + /* + * When we reach the end of the word before the last + * word, we're done. If we changed state, move forward + * one to the end of the next word. + */ + if (cnt == 0) { + if (cs.cs_flags == 0 && cs_next(sp, &cs)) + return (1); + break; + } + + /* Eat whitespace characters. */ + if (cs_bblank(sp, &cs)) + return (1); + if (cs.cs_flags == CS_SOF) + goto ret; + } + else + while (cnt--) { + state = cs.cs_flags == 0 && + inword(cs.cs_ch) ? INWORD : NOTWORD; + for (;;) { + if (cs_prev(sp, &cs)) + return (1); + if (cs.cs_flags == CS_SOF) + goto ret; + if (cs.cs_flags != 0 || isblank(cs.cs_ch)) + break; + if (state == INWORD) { + if (!inword(cs.cs_ch)) + break; + } else + if (inword(cs.cs_ch)) + break; + } + /* See comment above. */ + if (cnt == 0) { + if (cs.cs_flags == 0 && cs_next(sp, &cs)) + return (1); + break; + } + + /* Eat whitespace characters. */ + if (cs.cs_flags != 0 || isblank(cs.cs_ch)) + if (cs_bblank(sp, &cs)) + return (1); + if (cs.cs_flags == CS_SOF) + goto ret; + } + + /* If we didn't move, we must be at SOF. */ +ret: if (cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { + v_sof(sp, &vp->m_start); + return (1); + } + + /* Set the end of the range for motion commands. */ + vp->m_stop.lno = cs.cs_lno; + vp->m_stop.cno = cs.cs_cno; + + /* + * All commands move to the end of the range. Motion commands + * adjust the starting point to the character before the current + * one. + * + * !!! + * The historic vi didn't get this right -- the `yb' command yanked + * the right stuff and even updated the cursor value, but the cursor + * was not actually updated on the screen. + */ + vp->m_final = vp->m_stop; + if (ISMOTION(vp)) + --vp->m_start.cno; + return (0); +} diff --git a/contrib/nvi/vi/v_xchar.c b/contrib/nvi/vi/v_xchar.c new file mode 100644 index 0000000..15f155f --- /dev/null +++ b/contrib/nvi/vi/v_xchar.c @@ -0,0 +1,107 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_xchar.c 10.9 (Berkeley) 10/23/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_xchar -- [buffer] [count]x + * Deletes the character(s) on which the cursor sits. + * + * PUBLIC: int v_xchar __P((SCR *, VICMD *)); + */ +int +v_xchar(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + int isempty; + + if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) { + if (isempty) + goto nodel; + return (1); + } + if (len == 0) { +nodel: msgq(sp, M_BERR, "206|No characters to delete"); + return (1); + } + + /* + * Delete from the cursor toward the end of line, w/o moving the + * cursor. + * + * !!! + * Note, "2x" at EOL isn't the same as "xx" because the left movement + * of the cursor as part of the 'x' command isn't taken into account. + * Historically correct. + */ + if (F_ISSET(vp, VC_C1SET)) + vp->m_stop.cno += vp->count - 1; + if (vp->m_stop.cno >= len - 1) { + vp->m_stop.cno = len - 1; + vp->m_final.cno = vp->m_start.cno ? vp->m_start.cno - 1 : 0; + } else + vp->m_final.cno = vp->m_start.cno; + + if (cut(sp, + F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, + &vp->m_start, &vp->m_stop, 0)) + return (1); + return (del(sp, &vp->m_start, &vp->m_stop, 0)); +} + +/* + * v_Xchar -- [buffer] [count]X + * Deletes the character(s) immediately before the current cursor + * position. + * + * PUBLIC: int v_Xchar __P((SCR *, VICMD *)); + */ +int +v_Xchar(sp, vp) + SCR *sp; + VICMD *vp; +{ + u_long cnt; + + if (vp->m_start.cno == 0) { + v_sol(sp); + return (1); + } + + cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; + if (cnt >= vp->m_start.cno) + vp->m_start.cno = 0; + else + vp->m_start.cno -= cnt; + --vp->m_stop.cno; + vp->m_final.cno = vp->m_start.cno; + + if (cut(sp, + F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, + &vp->m_start, &vp->m_stop, 0)) + return (1); + return (del(sp, &vp->m_start, &vp->m_stop, 0)); +} diff --git a/contrib/nvi/vi/v_yank.c b/contrib/nvi/vi/v_yank.c new file mode 100644 index 0000000..4708f19 --- /dev/null +++ b/contrib/nvi/vi/v_yank.c @@ -0,0 +1,82 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_yank.c 10.9 (Berkeley) 5/19/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_yank -- [buffer][count]y[count][motion] + * [buffer][count]Y + * Yank text (or lines of text) into a cut buffer. + * + * !!! + * Historic vi moved the cursor to the from MARK if it was before the current + * cursor and on a different line, e.g., "yk" moves the cursor but "yj" and + * "yl" do not. Unfortunately, it's too late to change this now. Matching + * the historic semantics isn't easy. The line number was always changed and + * column movement was usually relative. However, "y'a" moved the cursor to + * the first non-blank of the line marked by a, while "y`a" moved the cursor + * to the line and column marked by a. Hopefully, the motion component code + * got it right... Unlike delete, we make no adjustments here. + * + * PUBLIC: int v_yank __P((SCR *, VICMD *)); + */ +int +v_yank(sp, vp) + SCR *sp; + VICMD *vp; +{ + size_t len; + + if (cut(sp, + F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, + &vp->m_stop, F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0)) + return (1); + sp->rptlines[L_YANKED] += (vp->m_stop.lno - vp->m_start.lno) + 1; + + /* + * One special correction, in case we've deleted the current line or + * character. We check it here instead of checking in every command + * that can be a motion component. + */ + if (db_get(sp, vp->m_final.lno, DBG_FATAL, NULL, &len)) + return (1); + + /* + * !!! + * Cursor movements, other than those caused by a line mode command + * moving to another line, historically reset the relative position. + * + * This currently matches the check made in v_delete(), I'm hoping + * that they should be consistent... + */ + if (!F_ISSET(vp, VM_LMODE)) { + F_CLR(vp, VM_RCM_MASK); + F_SET(vp, VM_RCM_SET); + + /* Make sure the set cursor position exists. */ + if (vp->m_final.cno >= len) + vp->m_final.cno = len ? len - 1 : 0; + } + return (0); +} diff --git a/contrib/nvi/vi/v_z.c b/contrib/nvi/vi/v_z.c new file mode 100644 index 0000000..5f02ddf --- /dev/null +++ b/contrib/nvi/vi/v_z.c @@ -0,0 +1,149 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_z.c 10.10 (Berkeley) 5/16/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_z -- [count]z[count][-.+^<CR>] + * Move the screen. + * + * PUBLIC: int v_z __P((SCR *, VICMD *)); + */ +int +v_z(sp, vp) + SCR *sp; + VICMD *vp; +{ + recno_t lno; + u_int value; + + /* + * The first count is the line to use. If the value doesn't + * exist, use the last line. + */ + if (F_ISSET(vp, VC_C1SET)) { + lno = vp->count; + if (!db_exist(sp, lno) && db_last(sp, &lno)) + return (1); + } else + lno = vp->m_start.lno; + + /* Set default return cursor line. */ + vp->m_final.lno = lno; + vp->m_final.cno = vp->m_start.cno; + + /* + * The second count is the displayed window size, i.e. the 'z' command + * is another way to get artificially small windows. Note, you can't + * grow beyond the size of the window. + * + * !!! + * A window size of 0 was historically allowed, and simply ignored. + * This could be much more simply done by modifying the value of the + * O_WINDOW option, but that's not how it worked historically. + */ + if (F_ISSET(vp, VC_C2SET) && vp->count2 != 0) { + if (vp->count2 > O_VAL(sp, O_WINDOW)) + vp->count2 = O_VAL(sp, O_WINDOW); + if (vs_crel(sp, vp->count2)) + return (1); + } + + switch (vp->character) { + case '-': /* Put the line at the bottom. */ + if (vs_sm_fill(sp, lno, P_BOTTOM)) + return (1); + break; + case '.': /* Put the line in the middle. */ + if (vs_sm_fill(sp, lno, P_MIDDLE)) + return (1); + break; + case '+': + /* + * If the user specified a line number, put that line at the + * top and move the cursor to it. Otherwise, scroll forward + * a screen from the current screen. + */ + if (F_ISSET(vp, VC_C1SET)) { + if (vs_sm_fill(sp, lno, P_TOP)) + return (1); + if (vs_sm_position(sp, &vp->m_final, 0, P_TOP)) + return (1); + } else + if (vs_sm_scroll(sp, &vp->m_final, sp->t_rows, Z_PLUS)) + return (1); + break; + case '^': + /* + * If the user specified a line number, put that line at the + * bottom, move the cursor to it, and then display the screen + * before that one. Otherwise, scroll backward a screen from + * the current screen. + * + * !!! + * Note, we match the off-by-one characteristics of historic + * vi, here. + */ + if (F_ISSET(vp, VC_C1SET)) { + if (vs_sm_fill(sp, lno, P_BOTTOM)) + return (1); + if (vs_sm_position(sp, &vp->m_final, 0, P_TOP)) + return (1); + if (vs_sm_fill(sp, vp->m_final.lno, P_BOTTOM)) + return (1); + } else + if (vs_sm_scroll(sp, &vp->m_final, sp->t_rows, Z_CARAT)) + return (1); + break; + default: /* Put the line at the top for <cr>. */ + value = KEY_VAL(sp, vp->character); + if (value != K_CR && value != K_NL) { + v_emsg(sp, vp->kp->usage, VIM_USAGE); + return (1); + } + if (vs_sm_fill(sp, lno, P_TOP)) + return (1); + break; + } + return (0); +} + +/* + * vs_crel -- + * Change the relative size of the current screen. + * + * PUBLIC: int vs_crel __P((SCR *, long)); + */ +int +vs_crel(sp, count) + SCR *sp; + long count; +{ + 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, SC_SCR_REDRAW); + return (0); +} diff --git a/contrib/nvi/vi/v_zexit.c b/contrib/nvi/vi/v_zexit.c new file mode 100644 index 0000000..3e454ca --- /dev/null +++ b/contrib/nvi/vi/v_zexit.c @@ -0,0 +1,54 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)v_zexit.c 10.6 (Berkeley) 4/27/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * v_zexit -- ZZ + * Save the file and exit. + * + * PUBLIC: int v_zexit __P((SCR *, VICMD *)); + */ +int +v_zexit(sp, vp) + SCR *sp; + VICMD *vp; +{ + /* Write back any modifications. */ + if (F_ISSET(sp->ep, F_MODIFIED) && + file_write(sp, NULL, NULL, NULL, FS_ALL)) + return (1); + + /* Check to make sure it's not a temporary file. */ + if (file_m3(sp, 0)) + return (1); + + /* Check for more files to edit. */ + if (ex_ncheck(sp, 0)) + return (1); + + F_SET(sp, SC_EXIT); + return (0); +} diff --git a/contrib/nvi/vi/vi.c b/contrib/nvi/vi/vi.c new file mode 100644 index 0000000..d20f7f2 --- /dev/null +++ b/contrib/nvi/vi/vi.c @@ -0,0 +1,1251 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)vi.c 10.57 (Berkeley) 10/13/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "vi.h" + +typedef enum { + GC_ERR, GC_ERR_NOFLUSH, GC_EVENT, GC_FATAL, GC_INTERRUPT, GC_OK +} gcret_t; + +static VIKEYS const + *v_alias __P((SCR *, VICMD *, VIKEYS const *)); +static gcret_t v_cmd __P((SCR *, VICMD *, VICMD *, VICMD *, int *, int *)); +static int v_count __P((SCR *, ARG_CHAR_T, u_long *)); +static void v_dtoh __P((SCR *)); +static int v_init __P((SCR *)); +static gcret_t v_key __P((SCR *, int, EVENT *, u_int32_t)); +static int v_keyword __P((SCR *)); +static int v_motion __P((SCR *, VICMD *, VICMD *, int *)); + +#if defined(DEBUG) && defined(COMLOG) +static void v_comlog __P((SCR *, VICMD *)); +#endif + +/* + * Side-effect: + * The dot structure can be set by the underlying vi functions, + * see v_Put() and v_put(). + */ +#define DOT (&VIP(sp)->sdot) +#define DOTMOTION (&VIP(sp)->sdotmotion) + +/* + * vi -- + * Main vi command loop. + * + * PUBLIC: int vi __P((SCR **)); + */ +int +vi(spp) + SCR **spp; +{ + GS *gp; + MARK abs; + SCR *next, *sp; + VICMD cmd, *vp; + VI_PRIVATE *vip; + int comcount, mapped, rval; + + /* Get the first screen. */ + sp = *spp; + gp = sp->gp; + + /* Initialize the command structure. */ + vp = &cmd; + memset(vp, 0, sizeof(VICMD)); + + /* Reset strange attraction. */ + F_SET(vp, VM_RCM_SET); + + /* Initialize the vi screen. */ + if (v_init(sp)) + return (1); + + /* Set the focus. */ + (void)sp->gp->scr_rename(sp, sp->frp->name, 1); + + for (vip = VIP(sp), rval = 0;;) { + /* Resolve messages. */ + if (!MAPPED_KEYS_WAITING(sp) && vs_resolve(sp, NULL, 0)) + goto ret; + + /* + * If not skipping a refresh, return to command mode and + * refresh the screen. + */ + if (F_ISSET(vip, VIP_S_REFRESH)) + F_CLR(vip, VIP_S_REFRESH); + else { + sp->showmode = SM_COMMAND; + if (vs_refresh(sp, 0)) + goto ret; + } + + /* Set the new favorite position. */ + if (F_ISSET(vp, VM_RCM_SET | VM_RCM_SETFNB | VM_RCM_SETNNB)) { + F_CLR(vip, VIP_RCM_LAST); + (void)vs_column(sp, &sp->rcm); + } + + /* + * If not currently in a map, log the cursor position, + * and set a flag so that this command can become the + * DOT command. + */ + if (MAPPED_KEYS_WAITING(sp)) + mapped = 1; + else { + if (log_cursor(sp)) + goto err; + mapped = 0; + } + + /* + * There may be an ex command waiting, and we returned here + * only because we exited a screen or file. In this case, + * we simply go back into the ex parser. + */ + if (EXCMD_RUNNING(gp)) { + vp->kp = &vikeys[':']; + goto ex_continue; + } + + /* Refresh the command structure. */ + memset(vp, 0, sizeof(VICMD)); + + /* + * We get a command, which may or may not have an associated + * motion. If it does, we get it too, calling its underlying + * function to get the resulting mark. We then call the + * command setting the cursor to the resulting mark. + * + * !!! + * Vi historically flushed mapped characters on error, but + * entering extra <escape> characters at the beginning of + * a map wasn't considered an error -- in fact, users would + * put leading <escape> characters in maps to clean up vi + * state before the map was interpreted. Beauty! + */ + switch (v_cmd(sp, DOT, vp, NULL, &comcount, &mapped)) { + case GC_ERR: + goto err; + case GC_ERR_NOFLUSH: + goto gc_err_noflush; + case GC_EVENT: + if (v_event_exec(sp, vp)) + goto err; + goto gc_event; + case GC_FATAL: + goto ret; + case GC_INTERRUPT: + goto intr; + case GC_OK: + break; + } + + /* Check for security setting. */ + if (F_ISSET(vp->kp, V_SECURE) && O_ISSET(sp, O_SECURE)) { + ex_emsg(sp, KEY_NAME(sp, vp->key), EXM_SECURE); + goto err; + } + + /* + * Historical practice: if a dot command gets a new count, + * any motion component goes away, i.e. "d3w2." deletes a + * total of 5 words. + */ + if (F_ISSET(vp, VC_ISDOT) && comcount) + DOTMOTION->count = 1; + + /* Copy the key flags into the local structure. */ + F_SET(vp, vp->kp->flags); + + /* Prepare to set the previous context. */ + if (F_ISSET(vp, V_ABS | V_ABS_C | V_ABS_L)) { + abs.lno = sp->lno; + abs.cno = sp->cno; + } + + /* + * Set the three cursor locations to the current cursor. The + * underlying routines don't bother if the cursor doesn't move. + * This also handles line commands (e.g. Y) defaulting to the + * current line. + */ + vp->m_start.lno = vp->m_stop.lno = vp->m_final.lno = sp->lno; + vp->m_start.cno = vp->m_stop.cno = vp->m_final.cno = sp->cno; + + /* + * Do any required motion; v_motion sets the from MARK and the + * line mode flag, as well as the VM_RCM flags. + */ + if (F_ISSET(vp, V_MOTION) && + v_motion(sp, DOTMOTION, vp, &mapped)) { + if (INTERRUPTED(sp)) + goto intr; + goto err; + } + + /* + * If a count is set and the command is line oriented, set the + * to MARK here relative to the cursor/from MARK. This is for + * commands that take both counts and motions, i.e. "4yy" and + * "y%". As there's no way the command can know which the user + * did, we have to do it here. (There are commands that are + * line oriented and that take counts ("#G", "#H"), for which + * this calculation is either completely meaningless or wrong. + * Each command must validate the value for itself. + */ + if (F_ISSET(vp, VC_C1SET) && F_ISSET(vp, VM_LMODE)) + vp->m_stop.lno += vp->count - 1; + + /* Increment the command count. */ + ++sp->ccnt; + +#if defined(DEBUG) && defined(COMLOG) + v_comlog(sp, vp); +#endif + /* Call the function. */ +ex_continue: if (vp->kp->func(sp, vp)) + goto err; +gc_event: +#ifdef DEBUG + /* Make sure no function left the temporary space locked. */ + if (F_ISSET(gp, G_TMP_INUSE)) { + F_CLR(gp, G_TMP_INUSE); + msgq(sp, M_ERR, + "232|vi: temporary buffer not released"); + } +#endif + /* + * If we're exiting this screen, move to the next one, or, if + * there aren't any more, return to the main editor loop. The + * ordering is careful, don't discard the contents of sp until + * the end. + */ + if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { + if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE))) + goto ret; + if (vs_discard(sp, &next)) + goto ret; + if (next == NULL && vs_swap(sp, &next, NULL)) + goto ret; + *spp = next; + if (screen_end(sp)) + goto ret; + if (next == NULL) + break; + + /* Switch screens, change focus. */ + sp = next; + vip = VIP(sp); + (void)sp->gp->scr_rename(sp, sp->frp->name, 1); + + /* Don't trust the cursor. */ + F_SET(vip, VIP_CUR_INVALID); + + continue; + } + + /* + * Set the dot command structure. + * + * !!! + * Historically, commands which used mapped keys did not + * set the dot command, with the exception of the text + * input commands. + */ + if (F_ISSET(vp, V_DOT) && !mapped) { + *DOT = cmd; + F_SET(DOT, VC_ISDOT); + + /* + * If a count was supplied for both the command and + * its motion, the count was used only for the motion. + * Turn the count back on for the dot structure. + */ + if (F_ISSET(vp, VC_C1RESET)) + F_SET(DOT, VC_C1SET); + + /* VM flags aren't retained. */ + F_CLR(DOT, VM_COMMASK | VM_RCM_MASK); + } + + /* + * Some vi row movements are "attracted" to the last position + * set, i.e. the VM_RCM commands are moths to the VM_RCM_SET + * commands' candle. If the movement is to the EOL the vi + * command handles it. If it's to the beginning, we handle it + * here. + * + * Note, some commands (e.g. _, ^) don't set the VM_RCM_SETFNB + * flag, but do the work themselves. The reason is that they + * have to modify the column in case they're being used as a + * motion component. Other similar commands (e.g. +, -) don't + * have to modify the column because they are always line mode + * operations when used as motions, so the column number isn't + * of any interest. + * + * Does this totally violate the screen and editor layering? + * You betcha. As they say, if you think you understand it, + * you don't. + */ + switch (F_ISSET(vp, VM_RCM_MASK)) { + case 0: + case VM_RCM_SET: + break; + case VM_RCM: + vp->m_final.cno = vs_rcm(sp, + vp->m_final.lno, F_ISSET(vip, VIP_RCM_LAST)); + break; + case VM_RCM_SETLAST: + F_SET(vip, VIP_RCM_LAST); + break; + case VM_RCM_SETFNB: + vp->m_final.cno = 0; + /* FALLTHROUGH */ + case VM_RCM_SETNNB: + if (nonblank(sp, vp->m_final.lno, &vp->m_final.cno)) + goto err; + break; + default: + abort(); + } + + /* Update the cursor. */ + sp->lno = vp->m_final.lno; + sp->cno = vp->m_final.cno; + + /* + * Set the absolute mark -- set even if a tags or similar + * command, since the tag may be moving to the same file. + */ + if ((F_ISSET(vp, V_ABS) || + F_ISSET(vp, V_ABS_L) && sp->lno != abs.lno || + F_ISSET(vp, V_ABS_C) && + (sp->lno != abs.lno || sp->cno != abs.cno)) && + mark_set(sp, ABSMARK1, &abs, 1)) + goto err; + + if (0) { +err: if (v_event_flush(sp, CH_MAPPED)) + msgq(sp, M_BERR, + "110|Vi command failed: mapped keys discarded"); + } + + /* + * Check and clear interrupts. There's an obvious race, but + * it's not worth fixing. + */ +gc_err_noflush: if (INTERRUPTED(sp)) { +intr: CLR_INTERRUPT(sp); + if (v_event_flush(sp, CH_MAPPED)) + msgq(sp, M_ERR, + "231|Interrupted: mapped keys discarded"); + else + msgq(sp, M_ERR, "236|Interrupted"); + } + + /* If the last command switched screens, update. */ + if (F_ISSET(sp, SC_SSWITCH)) { + F_CLR(sp, SC_SSWITCH); + + /* + * If the current screen is still displayed, it will + * need a new status line. + */ + F_SET(sp, SC_STATUS); + + /* Switch screens, change focus. */ + sp = sp->nextdisp; + vip = VIP(sp); + (void)sp->gp->scr_rename(sp, sp->frp->name, 1); + + /* Don't trust the cursor. */ + F_SET(vip, VIP_CUR_INVALID); + + /* Refresh so we can display messages. */ + if (vs_refresh(sp, 1)) + return (1); + } + + /* If the last command switched files, change focus. */ + if (F_ISSET(sp, SC_FSWITCH)) { + F_CLR(sp, SC_FSWITCH); + (void)sp->gp->scr_rename(sp, sp->frp->name, 1); + } + + /* If leaving vi, return to the main editor loop. */ + if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_EX)) { + *spp = sp; + v_dtoh(sp); + break; + } + } + if (0) +ret: rval = 1; + return (rval); +} + +#define KEY(key, ec_flags) { \ + if ((gcret = v_key(sp, 0, &ev, ec_flags)) != GC_OK) \ + return (gcret); \ + if (ev.e_value == K_ESCAPE) \ + goto esc; \ + if (F_ISSET(&ev.e_ch, CH_MAPPED)) \ + *mappedp = 1; \ + key = ev.e_c; \ +} + +/* + * The O_TILDEOP option makes the ~ command take a motion instead + * of a straight count. This is the replacement structure we use + * instead of the one currently in the VIKEYS table. + * + * XXX + * This should probably be deleted -- it's not all that useful, and + * we get help messages wrong. + */ +VIKEYS const tmotion = { + v_mulcase, V_CNT|V_DOT|V_MOTION|VM_RCM_SET, + "[count]~[count]motion", + " ~ change case to motion" +}; + +/* + * v_cmd -- + * + * The command structure for vi is less complex than ex (and don't think + * I'm not grateful!) The command syntax is: + * + * [count] [buffer] [count] key [[motion] | [buffer] [character]] + * + * and there are several special cases. The motion value is itself a vi + * command, with the syntax: + * + * [count] key [character] + */ +static gcret_t +v_cmd(sp, dp, vp, ismotion, comcountp, mappedp) + SCR *sp; + VICMD *dp, *vp; + VICMD *ismotion; /* Previous key if getting motion component. */ + int *comcountp, *mappedp; +{ + enum { COMMANDMODE, ISPARTIAL, NOTPARTIAL } cpart; + EVENT ev; + VIKEYS const *kp; + gcret_t gcret; + u_int flags; + CHAR_T key; + char *s; + + /* + * Get a key. + * + * <escape> cancels partial commands, i.e. a command where at least + * one non-numeric character has been entered. Otherwise, it beeps + * the terminal. + * + * !!! + * POSIX 1003.2-1992 explicitly disallows cancelling commands where + * all that's been entered is a number, requiring that the terminal + * be alerted. + */ + cpart = ismotion == NULL ? COMMANDMODE : ISPARTIAL; + if ((gcret = + v_key(sp, ismotion == NULL, &ev, EC_MAPCOMMAND)) != GC_OK) { + if (gcret == GC_EVENT) + vp->ev = ev; + return (gcret); + } + if (ev.e_value == K_ESCAPE) + goto esc; + if (F_ISSET(&ev.e_ch, CH_MAPPED)) + *mappedp = 1; + key = ev.e_c; + + if (ismotion == NULL) + cpart = NOTPARTIAL; + + /* Pick up optional buffer. */ + if (key == '"') { + cpart = ISPARTIAL; + if (ismotion != NULL) { + v_emsg(sp, NULL, VIM_COMBUF); + return (GC_ERR); + } + KEY(vp->buffer, 0); + F_SET(vp, VC_BUFFER); + + KEY(key, EC_MAPCOMMAND); + } + + /* + * Pick up optional count, where a leading 0 is not a count, + * it's a command. + */ + if (isdigit(key) && key != '0') { + if (v_count(sp, key, &vp->count)) + return (GC_ERR); + F_SET(vp, VC_C1SET); + *comcountp = 1; + + KEY(key, EC_MAPCOMMAND); + } else + *comcountp = 0; + + /* Pick up optional buffer. */ + if (key == '"') { + cpart = ISPARTIAL; + if (F_ISSET(vp, VC_BUFFER)) { + msgq(sp, M_ERR, "234|Only one buffer may be specified"); + return (GC_ERR); + } + if (ismotion != NULL) { + v_emsg(sp, NULL, VIM_COMBUF); + return (GC_ERR); + } + KEY(vp->buffer, 0); + F_SET(vp, VC_BUFFER); + + KEY(key, EC_MAPCOMMAND); + } + + /* Check for an OOB command key. */ + cpart = ISPARTIAL; + if (key > MAXVIKEY) { + v_emsg(sp, KEY_NAME(sp, key), VIM_NOCOM); + return (GC_ERR); + } + kp = &vikeys[vp->key = key]; + + /* + * !!! + * Historically, D accepted and then ignored a count. Match it. + */ + if (vp->key == 'D' && F_ISSET(vp, VC_C1SET)) { + *comcountp = 0; + vp->count = 0; + F_CLR(vp, VC_C1SET); + } + + /* Check for command aliases. */ + if (kp->func == NULL && (kp = v_alias(sp, vp, kp)) == NULL) + return (GC_ERR); + + /* The tildeop option makes the ~ command take a motion. */ + if (key == '~' && O_ISSET(sp, O_TILDEOP)) + kp = &tmotion; + + vp->kp = kp; + + /* + * Find the command. The only legal command with no underlying + * function is dot. It's historic practice that <escape> doesn't + * just erase the preceding number, it beeps the terminal as well. + * It's a common problem, so just beep the terminal unless verbose + * was set. + */ + if (kp->func == NULL) { + if (key != '.') { + v_emsg(sp, KEY_NAME(sp, key), + ev.e_value == K_ESCAPE ? VIM_NOCOM_B : VIM_NOCOM); + return (GC_ERR); + } + + /* If called for a motion command, stop now. */ + if (dp == NULL) + goto usage; + + /* + * !!! + * If a '.' is immediately entered after an undo command, we + * replay the log instead of redoing the last command. This + * is necessary because 'u' can't set the dot command -- see + * vi/v_undo.c:v_undo for details. + */ + if (VIP(sp)->u_ccnt == sp->ccnt) { + vp->kp = &vikeys['u']; + F_SET(vp, VC_ISDOT); + return (GC_OK); + } + + /* Otherwise, a repeatable command must have been executed. */ + if (!F_ISSET(dp, VC_ISDOT)) { + msgq(sp, M_ERR, "208|No command to repeat"); + return (GC_ERR); + } + + /* Set new count/buffer, if any, and return. */ + if (F_ISSET(vp, VC_C1SET)) { + F_SET(dp, VC_C1SET); + dp->count = vp->count; + } + if (F_ISSET(vp, VC_BUFFER)) + dp->buffer = vp->buffer; + + *vp = *dp; + return (GC_OK); + } + + /* Set the flags based on the command flags. */ + flags = kp->flags; + + /* Check for illegal count. */ + if (F_ISSET(vp, VC_C1SET) && !LF_ISSET(V_CNT)) + goto usage; + + /* Illegal motion command. */ + if (ismotion == NULL) { + /* Illegal buffer. */ + if (!LF_ISSET(V_OBUF) && F_ISSET(vp, VC_BUFFER)) + goto usage; + + /* Required buffer. */ + if (LF_ISSET(V_RBUF)) { + KEY(vp->buffer, 0); + F_SET(vp, VC_BUFFER); + } + } + + /* + * Special case: '[', ']' and 'Z' commands. Doesn't the fact that + * the *single* characters don't mean anything but the *doubled* + * characters do, just frost your shorts? + */ + if (vp->key == '[' || vp->key == ']' || vp->key == 'Z') { + /* + * Historically, half entered [[, ]] or Z commands weren't + * cancelled by <escape>, the terminal was beeped instead. + * POSIX.2-1992 probably didn't notice, and requires that + * they be cancelled instead of beeping. Seems fine to me. + * + * Don't set the EC_MAPCOMMAND flag, apparently ] is a popular + * vi meta-character, and we don't want the user to wait while + * we time out a possible mapping. This *appears* to match + * historic vi practice, but with mapping characters, you Just + * Never Know. + */ + KEY(key, 0); + + if (vp->key != key) { +usage: if (ismotion == NULL) + s = kp->usage; + else if (ismotion->key == '~' && O_ISSET(sp, O_TILDEOP)) + s = tmotion.usage; + else + s = vikeys[ismotion->key].usage; + v_emsg(sp, s, VIM_USAGE); + return (GC_ERR); + } + } + /* Special case: 'z' command. */ + if (vp->key == 'z') { + KEY(vp->character, 0); + if (isdigit(vp->character)) { + if (v_count(sp, vp->character, &vp->count2)) + return (GC_ERR); + F_SET(vp, VC_C2SET); + KEY(vp->character, 0); + } + } + + /* + * Commands that have motion components can be doubled to + * imply the current line. + */ + if (ismotion != NULL && ismotion->key != key && !LF_ISSET(V_MOVE)) { + msgq(sp, M_ERR, "210|%s may not be used as a motion command", + KEY_NAME(sp, key)); + return (GC_ERR); + } + + /* Required character. */ + if (LF_ISSET(V_CHAR)) + KEY(vp->character, 0); + + /* Get any associated cursor word. */ + if (F_ISSET(kp, V_KEYW) && v_keyword(sp)) + return (GC_ERR); + + return (GC_OK); + +esc: switch (cpart) { + case COMMANDMODE: + msgq(sp, M_BERR, "211|Already in command mode"); + return (GC_ERR_NOFLUSH); + case ISPARTIAL: + break; + case NOTPARTIAL: + (void)sp->gp->scr_bell(sp); + break; + } + return (GC_ERR); +} + +/* + * v_motion -- + * + * Get resulting motion mark. + */ +static int +v_motion(sp, dm, vp, mappedp) + SCR *sp; + VICMD *dm, *vp; + int *mappedp; +{ + VICMD motion; + size_t len; + u_long cnt; + u_int flags; + int tilde_reset, notused; + + /* + * If '.' command, use the dot motion, else get the motion command. + * Clear any line motion flags, the subsequent motion isn't always + * the same, i.e. "/aaa" may or may not be a line motion. + */ + if (F_ISSET(vp, VC_ISDOT)) { + motion = *dm; + F_SET(&motion, VC_ISDOT); + F_CLR(&motion, VM_COMMASK); + } else { + memset(&motion, 0, sizeof(VICMD)); + if (v_cmd(sp, NULL, &motion, vp, ¬used, mappedp) != GC_OK) + return (1); + } + + /* + * A count may be provided both to the command and to the motion, in + * which case the count is multiplicative. For example, "3y4y" is the + * same as "12yy". This count is provided to the motion command and + * not to the regular function. + */ + cnt = motion.count = F_ISSET(&motion, VC_C1SET) ? motion.count : 1; + if (F_ISSET(vp, VC_C1SET)) { + motion.count *= vp->count; + F_SET(&motion, VC_C1SET); + + /* + * Set flags to restore the original values of the command + * structure so dot commands can change the count values, + * e.g. "2dw" "3." deletes a total of five words. + */ + F_CLR(vp, VC_C1SET); + F_SET(vp, VC_C1RESET); + } + + /* + * Some commands can be repeated to indicate the current line. In + * this case, or if the command is a "line command", set the flags + * appropriately. If not a doubled command, run the function to get + * the resulting mark. + */ + if (vp->key == motion.key) { + F_SET(vp, VM_LDOUBLE | VM_LMODE); + + /* Set the origin of the command. */ + vp->m_start.lno = sp->lno; + vp->m_start.cno = 0; + + /* + * Set the end of the command. + * + * If the current line is missing, i.e. the file is empty, + * historic vi permitted a "cc" or "!!" command to insert + * text. + */ + vp->m_stop.lno = sp->lno + motion.count - 1; + if (db_get(sp, vp->m_stop.lno, 0, NULL, &len)) { + if (vp->m_stop.lno != 1 || + vp->key != 'c' && vp->key != '!') { + v_emsg(sp, NULL, VIM_EMPTY); + return (1); + } + vp->m_stop.cno = 0; + } else + vp->m_stop.cno = len ? len - 1 : 0; + } else { + /* + * Motion commands change the underlying movement (*snarl*). + * For example, "l" is illegal at the end of a line, but "dl" + * is not. Set flags so the function knows the situation. + */ + motion.rkp = vp->kp; + + /* + * XXX + * Use yank instead of creating a new motion command, it's a + * lot easier for now. + */ + if (vp->kp == &tmotion) { + tilde_reset = 1; + vp->kp = &vikeys['y']; + } else + tilde_reset = 0; + + /* + * Copy the key flags into the local structure, except for the + * RCM flags -- the motion command will set the RCM flags in + * the vp structure if necessary. This means that the motion + * command is expected to determine where the cursor ends up! + * However, we save off the current RCM mask and restore it if + * it no RCM flags are set by the motion command, with a small + * modification. + * + * We replace the VM_RCM_SET flag with the VM_RCM flag. This + * is so that cursor movement doesn't set the relative position + * unless the motion command explicitly specified it. This + * appears to match historic practice, but I've never been able + * to develop a hard-and-fast rule. + */ + flags = F_ISSET(vp, VM_RCM_MASK); + if (LF_ISSET(VM_RCM_SET)) { + LF_SET(VM_RCM); + LF_CLR(VM_RCM_SET); + } + F_CLR(vp, VM_RCM_MASK); + F_SET(&motion, motion.kp->flags & ~VM_RCM_MASK); + + /* + * Set the three cursor locations to the current cursor. This + * permits commands like 'j' and 'k', that are line oriented + * motions and have special cursor suck semantics when they are + * used as standalone commands, to ignore column positioning. + */ + motion.m_final.lno = + motion.m_stop.lno = motion.m_start.lno = sp->lno; + motion.m_final.cno = + motion.m_stop.cno = motion.m_start.cno = sp->cno; + + /* Run the function. */ + if ((motion.kp->func)(sp, &motion)) + return (1); + + /* + * If the current line is missing, i.e. the file is empty, + * historic vi allowed "c<motion>" or "!<motion>" to insert + * text. Otherwise fail -- most motion commands will have + * already failed, but some, e.g. G, succeed in empty files. + */ + if (!db_exist(sp, vp->m_stop.lno)) { + if (vp->m_stop.lno != 1 || + vp->key != 'c' && vp->key != '!') { + v_emsg(sp, NULL, VIM_EMPTY); + return (1); + } + vp->m_stop.cno = 0; + } + + /* + * XXX + * See above. + */ + if (tilde_reset) + vp->kp = &tmotion; + + /* + * Copy cut buffer, line mode and cursor position information + * from the motion command structure, i.e. anything that the + * motion command can set for us. The commands can flag the + * movement as a line motion (see v_sentence) as well as set + * the VM_RCM_* flags explicitly. + */ + F_SET(vp, F_ISSET(&motion, VM_COMMASK | VM_RCM_MASK)); + + /* + * If the motion command set no relative motion flags, use + * the (slightly) modified previous values. + */ + if (!F_ISSET(vp, VM_RCM_MASK)) + F_SET(vp, flags); + + /* + * Commands can change behaviors based on the motion command + * used, for example, the ! command repeated the last bang + * command if N or n was used as the motion. + */ + vp->rkp = motion.kp; + + /* + * Motion commands can reset all of the cursor information. + * If the motion is in the reverse direction, switch the + * from and to MARK's so that it's in a forward direction. + * Motions are from the from MARK to the to MARK (inclusive). + */ + if (motion.m_start.lno > motion.m_stop.lno || + motion.m_start.lno == motion.m_stop.lno && + motion.m_start.cno > motion.m_stop.cno) { + vp->m_start = motion.m_stop; + vp->m_stop = motion.m_start; + } else { + vp->m_start = motion.m_start; + vp->m_stop = motion.m_stop; + } + vp->m_final = motion.m_final; + } + + /* + * If the command sets dot, save the motion structure. The motion + * count was changed above and needs to be reset, that's why this + * is done here, and not in the calling routine. + */ + if (F_ISSET(vp->kp, V_DOT)) { + *dm = motion; + dm->count = cnt; + } + return (0); +} + +/* + * v_init -- + * Initialize the vi screen. + */ +static int +v_init(sp) + SCR *sp; +{ + GS *gp; + VI_PRIVATE *vip; + + gp = sp->gp; + vip = VIP(sp); + + /* Switch into vi. */ + if (gp->scr_screen(sp, SC_VI)) + return (1); + (void)gp->scr_attr(sp, SA_ALTERNATE, 1); + + F_CLR(sp, SC_EX | SC_SCR_EX); + F_SET(sp, SC_VI); + + /* + * Initialize screen values. + * + * Small windows: see vs_refresh(), section 6a. + * + * 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 = vip->srows = O_VAL(sp, O_LINES); + sp->cols = O_VAL(sp, O_COLUMNS); + sp->t_rows = sp->t_minrows = O_VAL(sp, O_WINDOW); + if (sp->rows != 1) { + if (sp->t_rows > sp->rows - 1) { + sp->t_minrows = sp->t_rows = sp->rows - 1; + msgq(sp, M_INFO, + "214|Windows option value is too large, max is %u", + sp->t_rows); + } + sp->t_maxrows = sp->rows - 1; + } else + sp->t_maxrows = 1; + sp->woff = 0; + + /* Create a screen map. */ + CALLOC_RET(sp, HMAP, SMAP *, SIZE_HMAP(sp), sizeof(SMAP)); + TMAP = HMAP + (sp->t_rows - 1); + HMAP->lno = sp->lno; + HMAP->coff = 0; + HMAP->soff = 1; + + /* + * Fill the screen map from scratch -- try and center the line. That + * way if we're starting with a file we've seen before, we'll put the + * line in the middle, otherwise, it won't work and we'll end up with + * the line at the top. + */ + F_SET(sp, SC_SCR_REFORMAT | SC_SCR_CENTER); + + /* Invalidate the cursor. */ + F_SET(vip, VIP_CUR_INVALID); + + /* Paint the screen image from scratch. */ + F_SET(vip, VIP_N_EX_PAINT); + + return (0); +} + +/* + * v_dtoh -- + * Move all but the current screen to the hidden queue. + */ +static void +v_dtoh(sp) + SCR *sp; +{ + GS *gp; + SCR *tsp; + int hidden; + + /* Move all screens to the hidden queue, tossing screen maps. */ + for (hidden = 0, gp = sp->gp; + (tsp = gp->dq.cqh_first) != (void *)&gp->dq; ++hidden) { + if (_HMAP(tsp) != NULL) { + free(_HMAP(tsp)); + _HMAP(tsp) = NULL; + } + CIRCLEQ_REMOVE(&gp->dq, tsp, q); + CIRCLEQ_INSERT_TAIL(&gp->hq, tsp, q); + } + + /* Move current screen back to the display queue. */ + CIRCLEQ_REMOVE(&gp->hq, sp, q); + CIRCLEQ_INSERT_TAIL(&gp->dq, sp, q); + + /* + * XXX + * Don't bother internationalizing this message, it's going to + * go away as soon as we have one-line screens. --TK + */ + if (hidden > 1) + msgq(sp, M_INFO, + "%d screens backgrounded; use :display to list them", + hidden - 1); +} + +/* + * v_keyword -- + * Get the word (or non-word) the cursor is on. + */ +static int +v_keyword(sp) + SCR *sp; +{ + VI_PRIVATE *vip; + size_t beg, end, len; + int moved, state; + char *p; + + if (db_get(sp, sp->lno, DBG_FATAL, &p, &len)) + return (1); + + /* + * !!! + * Historically, tag commands skipped over any leading whitespace + * characters. Make this true in general when using cursor words. + * If movement, getting a cursor word implies moving the cursor to + * its beginning. Refresh now. + * + * !!! + * Find the beginning/end of the keyword. Keywords are currently + * used for cursor-word searching and for tags. Historical vi + * only used the word in a tag search from the cursor to the end + * of the word, i.e. if the cursor was on the 'b' in " abc ", the + * tag was "bc". For consistency, we make cursor word searches + * follow the same rule. + */ + for (moved = 0, + beg = sp->cno; beg < len && isspace(p[beg]); moved = 1, ++beg); + if (beg >= len) { + msgq(sp, M_BERR, "212|Cursor not in a word"); + return (1); + } + if (moved) { + sp->cno = beg; + (void)vs_refresh(sp, 0); + } + + /* Find the end of the word. */ + for (state = inword(p[beg]), + end = beg; ++end < len && state == inword(p[end]);); + + vip = VIP(sp); + len = (end - beg); + BINC_RET(sp, vip->keyw, vip->klen, len); + memmove(vip->keyw, p + beg, len); + vip->keyw[len] = '\0'; /* XXX */ + return (0); +} + +/* + * v_alias -- + * Check for a command alias. + */ +static VIKEYS const * +v_alias(sp, vp, kp) + SCR *sp; + VICMD *vp; + VIKEYS const *kp; +{ + CHAR_T push; + + switch (vp->key) { + case 'C': /* C -> c$ */ + push = '$'; + vp->key = 'c'; + break; + case 'D': /* D -> d$ */ + push = '$'; + vp->key = 'd'; + break; + case 'S': /* S -> c_ */ + push = '_'; + vp->key = 'c'; + break; + case 'Y': /* Y -> y_ */ + push = '_'; + vp->key = 'y'; + break; + default: + return (kp); + } + return (v_event_push(sp, + NULL, &push, 1, CH_NOMAP | CH_QUOTED) ? NULL : &vikeys[vp->key]); +} + +/* + * v_count -- + * Return the next count. + */ +static int +v_count(sp, fkey, countp) + SCR *sp; + ARG_CHAR_T fkey; + u_long *countp; +{ + EVENT ev; + u_long count, tc; + + ev.e_c = fkey; + count = tc = 0; + do { + /* + * XXX + * Assume that overflow results in a smaller number. + */ + tc = count * 10 + ev.e_c - '0'; + if (count > tc) { + /* Toss to the next non-digit. */ + do { + if (v_key(sp, 0, &ev, + EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK) + return (1); + } while (isdigit(ev.e_c)); + msgq(sp, M_ERR, + "235|Number larger than %lu", ULONG_MAX); + return (1); + } + count = tc; + if (v_key(sp, 0, &ev, EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK) + return (1); + } while (isdigit(ev.e_c)); + *countp = count; + return (0); +} + +/* + * v_key -- + * Return the next event. + */ +static gcret_t +v_key(sp, command_events, evp, ec_flags) + SCR *sp; + int command_events; + EVENT *evp; + u_int32_t ec_flags; +{ + u_int32_t quote; + + for (quote = 0;;) { + if (v_event_get(sp, evp, 0, ec_flags | quote)) + return (GC_FATAL); + quote = 0; + + switch (evp->e_event) { + case E_CHARACTER: + /* + * !!! + * Historically, ^V was ignored in the command stream, + * although it had a useful side-effect of interrupting + * mappings. Adding a quoting bit to the call probably + * extends historic practice, but it feels right. + */ + if (evp->e_value == K_VLNEXT) { + quote = EC_QUOTED; + break; + } + return (GC_OK); + case E_ERR: + case E_EOF: + return (GC_FATAL); + case E_INTERRUPT: + /* + * !!! + * Historically, vi beeped on command level interrupts. + * + * Historically, vi exited to ex mode if no file was + * named on the command line, and two interrupts were + * generated in a row. (Just figured you might want + * to know that.) + */ + (void)sp->gp->scr_bell(sp); + return (GC_INTERRUPT); + case E_REPAINT: + if (vs_repaint(sp, evp)) + return (GC_FATAL); + break; + case E_WRESIZE: + return (GC_ERR); + case E_QUIT: + case E_WRITE: + if (command_events) + return (GC_EVENT); + /* FALLTHROUGH */ + default: + v_event_err(sp, evp); + return (GC_ERR); + } + } + /* NOTREACHED */ +} + +#if defined(DEBUG) && defined(COMLOG) +/* + * v_comlog -- + * Log the contents of the command structure. + */ +static void +v_comlog(sp, vp) + SCR *sp; + VICMD *vp; +{ + TRACE(sp, "vcmd: %c", vp->key); + if (F_ISSET(vp, VC_BUFFER)) + TRACE(sp, " buffer: %c", vp->buffer); + if (F_ISSET(vp, VC_C1SET)) + TRACE(sp, " c1: %lu", vp->count); + if (F_ISSET(vp, VC_C2SET)) + TRACE(sp, " c2: %lu", vp->count2); + TRACE(sp, " flags: 0x%x\n", vp->flags); +} +#endif diff --git a/contrib/nvi/vi/vi.h b/contrib/nvi/vi/vi.h new file mode 100644 index 0000000..bede3a6 --- /dev/null +++ b/contrib/nvi/vi/vi.h @@ -0,0 +1,377 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + * + * @(#)vi.h 10.19 (Berkeley) 6/30/96 + */ + +/* Definition of a vi "word". */ +#define inword(ch) (isalnum(ch) || (ch) == '_') + +typedef struct _vikeys VIKEYS; + +/* Structure passed around to functions implementing vi commands. */ +typedef struct _vicmd { + CHAR_T key; /* Command key. */ + CHAR_T buffer; /* Buffer. */ + CHAR_T character; /* Character. */ + u_long count; /* Count. */ + u_long count2; /* Second count (only used by z). */ + EVENT ev; /* Associated event. */ + +#define ISCMD(p, key) ((p) == &vikeys[key]) + VIKEYS const *kp; /* Command/Motion VIKEYS entry. */ +#define ISMOTION(vp) (vp->rkp != NULL && F_ISSET(vp->rkp, V_MOTION)) + VIKEYS const *rkp; /* Related C/M VIKEYS entry. */ + + /* + * Historic vi allowed "dl" when the cursor was on the last column, + * deleting the last character, and similarly allowed "dw" when + * the cursor was on the last column of the file. It didn't allow + * "dh" when the cursor was on column 1, although these cases are + * not strictly analogous. The point is that some movements would + * succeed if they were associated with a motion command, and fail + * otherwise. This is part of the off-by-1 schizophrenia that + * plagued vi. Other examples are that "dfb" deleted everything + * up to and including the next 'b' character, while "d/b" deleted + * everything up to the next 'b' character. While this implementation + * regularizes the interface to the extent possible, there are many + * special cases that can't be fixed. The special cases are handled + * by setting flags per command so that the underlying command and + * motion routines know what's really going on. + * + * The VM_* flags are set in the vikeys array and by the underlying + * functions (motion component or command) as well. For this reason, + * the flags in the VICMD and VIKEYS structures live in the same name + * space. + */ +#define VM_CMDFAILED 0x00000001 /* Command failed. */ +#define VM_CUTREQ 0x00000002 /* Always cut into numeric buffers. */ +#define VM_LDOUBLE 0x00000004 /* Doubled command for line mode. */ +#define VM_LMODE 0x00000008 /* Motion is line oriented. */ +#define VM_COMMASK 0x0000000f /* Mask for VM flags. */ + + /* + * The VM_RCM_* flags are single usage, i.e. if you set one, you have + * to clear the others. + */ +#define VM_RCM 0x00000010 /* Use relative cursor movment (RCM). */ +#define VM_RCM_SET 0x00000020 /* RCM: set to current position. */ +#define VM_RCM_SETFNB 0x00000040 /* RCM: set to first non-blank (FNB). */ +#define VM_RCM_SETLAST 0x00000080 /* RCM: set to last character. */ +#define VM_RCM_SETNNB 0x00000100 /* RCM: set to next non-blank. */ +#define VM_RCM_MASK 0x000001f0 /* Mask for RCM flags. */ + + /* Flags for the underlying function. */ +#define VC_BUFFER 0x00000200 /* The buffer was set. */ +#define VC_C1RESET 0x00000400 /* Reset C1SET flag for dot commands. */ +#define VC_C1SET 0x00000800 /* Count 1 was set. */ +#define VC_C2SET 0x00001000 /* Count 2 was set. */ +#define VC_ISDOT 0x00002000 /* Command was the dot command. */ + u_int32_t flags; + + /* + * There are four cursor locations that we worry about: the initial + * cursor position, the start of the range, the end of the range, + * and the final cursor position. The initial cursor position and + * the start of the range are both m_start, and are always the same. + * All locations are initialized to the starting cursor position by + * the main vi routines, and the underlying functions depend on this. + * + * Commands that can be motion components set the end of the range + * cursor position, m_stop. All commands must set the ending cursor + * position, m_final. The reason that m_stop isn't the same as m_final + * is that there are situations where the final position of the cursor + * is outside of the cut/delete range (e.g. 'd[[' from the first column + * of a line). The final cursor position often varies based on the + * direction of the movement, as well as the command. The only special + * case that the delete code handles is that it will make adjustments + * if the final cursor position is deleted. + * + * The reason for all of this is that the historic vi semantics were + * defined command-by-command. Every function has to roll its own + * starting and stopping positions, and adjust them if it's being used + * as a motion component. The general rules are as follows: + * + * 1: If not a motion component, the final cursor is at the end + * of the range. + * 2: If moving backward in the file, delete and yank move the + * final cursor to the end of the range. + * 3: If moving forward in the file, delete and yank leave the + * final cursor at the start of the range. + * + * Usually, if moving backward in the file and it's a motion component, + * the starting cursor is decremented by a single character (or, in a + * few cases, to the end of the previous line) so that the starting + * cursor character isn't cut or deleted. No cursor adjustment is + * needed for moving forward, because the cut/delete routines handle + * m_stop inclusively, i.e. the last character in the range is cut or + * deleted. This makes cutting to the EOF/EOL reasonable. + * + * The 'c', '<', '>', and '!' commands are special cases. We ignore + * the final cursor position for all of them: for 'c', the text input + * routines set the cursor to the last character inserted; for '<', + * '>' and '!', the underlying ex commands that do the operation will + * set the cursor for us, usually to something related to the first + * <nonblank>. + */ + MARK m_start; /* mark: initial cursor, range start. */ + MARK m_stop; /* mark: range end. */ + MARK m_final; /* mark: final cursor position. */ +} VICMD; + +/* Vi command table structure. */ +struct _vikeys { /* Underlying function. */ + int (*func) __P((SCR *, VICMD *)); +#define V_ABS 0x00004000 /* Absolute movement, set '' mark. */ +#define V_ABS_C 0x00008000 /* V_ABS: if the line/column changed. */ +#define V_ABS_L 0x00010000 /* V_ABS: if the line changed. */ +#define V_CHAR 0x00020000 /* Character (required, trailing). */ +#define V_CNT 0x00040000 /* Count (optional, leading). */ +#define V_DOT 0x00080000 /* On success, sets dot command. */ +#define V_KEYW 0x00100000 /* Cursor referenced word. */ +#define V_MOTION 0x00200000 /* Motion (required, trailing). */ +#define V_MOVE 0x00400000 /* Command defines movement. */ +#define V_OBUF 0x00800000 /* Buffer (optional, leading). */ +#define V_RBUF 0x01000000 /* Buffer (required, trailing). */ +#define V_SECURE 0x02000000 /* Permission denied if O_SECURE set. */ + u_int32_t flags; + char *usage; /* Usage line. */ + char *help; /* Help line. */ +}; +#define MAXVIKEY 126 /* List of vi commands. */ +extern VIKEYS const vikeys[MAXVIKEY + 1]; +extern VIKEYS const tmotion; /* XXX Hacked ~ command. */ + +/* Character stream structure, prototypes. */ +typedef struct _vcs { + recno_t cs_lno; /* Line. */ + size_t cs_cno; /* Column. */ + CHAR_T *cs_bp; /* Buffer. */ + size_t cs_len; /* Length. */ + CHAR_T cs_ch; /* Character. */ +#define CS_EMP 1 /* Empty line. */ +#define CS_EOF 2 /* End-of-file. */ +#define CS_EOL 3 /* End-of-line. */ +#define CS_SOF 4 /* Start-of-file. */ + int cs_flags; /* Return flags. */ +} VCS; + +int cs_bblank __P((SCR *, VCS *)); +int cs_fblank __P((SCR *, VCS *)); +int cs_fspace __P((SCR *, VCS *)); +int cs_init __P((SCR *, VCS *)); +int cs_next __P((SCR *, VCS *)); +int cs_prev __P((SCR *, VCS *)); + +/* + * We use a single "window" for each set of vi screens. 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. + * + * 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. + * + * The SMAP always has an entry for each line of the physical screen, plus a + * slot for the colon command line, so there is room to add any screen into + * another one at screen exit. + * + * Lno is the line number. If doing the historic vi long line folding, 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. In the case of + * long lines, the screen map will tend to be staggered, e.g., 1:1, 1:2, 1:3, + * 2:1, 3:1, etc. If doing left-right scrolling, the off field is the screen + * column offset into the lines, and can take on any value, as it's adjusted + * by the user set value O_SIDESCROLL. + */ +typedef struct _smap { + recno_t lno; /* 1-N: Physical file line number. */ + size_t coff; /* 0-N: Column offset in the line. */ + size_t soff; /* 1-N: Screen offset in the line. */ + + /* vs_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_int8_t c_scoff; /* 0-N: offset into the first character. */ + u_int8_t c_eclen; /* 1-N: columns from the last character. */ + u_int8_t 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) + + /* Character search information. */ +typedef enum { CNOTSET, FSEARCH, fSEARCH, TSEARCH, tSEARCH } cdir_t; + +typedef enum { AB_NOTSET, AB_NOTWORD, AB_INWORD } abb_t; +typedef enum { Q_NOTSET, Q_BNEXT, Q_BTHIS, Q_VNEXT, Q_VTHIS } quote_t; + +/* Vi private, per-screen memory. */ +typedef struct _vi_private { + VICMD cmd; /* Current command, motion. */ + VICMD motion; + + /* + * !!! + * The saved command structure can be modified by the underlying + * vi functions, see v_Put() and v_put(). + */ + VICMD sdot; /* Saved dot, motion command. */ + VICMD sdotmotion; + + CHAR_T *keyw; /* Keyword buffer. */ + size_t klen; /* Keyword length. */ + size_t keywlen; /* Keyword buffer length. */ + + CHAR_T rlast; /* Last 'r' replacement character. */ + e_key_t rvalue; /* Value of last replacement character. */ + + EVENT *rep; /* Input replay buffer. */ + size_t rep_len; /* Input replay buffer length. */ + size_t rep_cnt; /* Input replay buffer characters. */ + + mtype_t mtype; /* Last displayed message type. */ + size_t linecount; /* 1-N: Output overwrite count. */ + size_t lcontinue; /* 1-N: Output line continue value. */ + size_t totalcount; /* 1-N: Output overwrite count. */ + + /* Busy state. */ + int busy_ref; /* Busy reference count. */ + int busy_ch; /* Busy character. */ + size_t busy_fx; /* Busy character x coordinate. */ + size_t busy_oldy; /* Saved y coordinate. */ + size_t busy_oldx; /* Saved x coordinate. */ + struct timeval busy_tv; /* Busy timer. */ + + char *ps; /* Paragraph plus section list. */ + + u_long u_ccnt; /* Undo command count. */ + + CHAR_T lastckey; /* Last search character. */ + cdir_t csearchdir; /* Character search direction. */ + + SMAP *h_smap; /* First slot of the line map. */ + SMAP *t_smap; /* Last slot of the line map. */ + + /* + * One extra slot is always allocated for the map so that we can use + * it to do vi :colon command input; see v_tcmd(). + */ + recno_t sv_tm_lno; /* tcmd: saved TMAP lno field. */ + size_t sv_tm_coff; /* tcmd: saved TMAP coff field. */ + size_t sv_tm_soff; /* tcmd: saved TMAP soff field. */ + size_t sv_t_maxrows; /* tcmd: saved t_maxrows. */ + size_t sv_t_minrows; /* tcmd: saved t_minrows. */ + size_t sv_t_rows; /* tcmd: saved t_rows. */ +#define SIZE_HMAP(sp) (VIP(sp)->srows + 1) + + /* + * Macros to get to the head/tail of the smap. If the screen only has + * one line, HMAP can be equal to TMAP, so the code has to understand + * the off-by-one errors that can result. If stepping through an SMAP + * and operating on each entry, use sp->t_rows as the count of slots, + * don't use a loop that compares <= TMAP. + */ +#define _HMAP(sp) (VIP(sp)->h_smap) +#define HMAP _HMAP(sp) +#define _TMAP(sp) (VIP(sp)->t_smap) +#define TMAP _TMAP(sp) + + recno_t ss_lno; /* 1-N: vi_opt_screens cached line number. */ + size_t ss_screens; /* vi_opt_screens cached return value. */ +#define VI_SCR_CFLUSH(vip) vip->ss_lno = OOBLNO + + size_t srows; /* 1-N: rows in the terminal/window. */ + 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. */ + SMAP *sc_smap; /* SMAP entry where sc_col occurs. */ + +#define VIP_CUR_INVALID 0x0001 /* Cursor position is unknown. */ +#define VIP_DIVIDER 0x0002 /* Divider line was displayed. */ +#define VIP_N_EX_PAINT 0x0004 /* Clear and repaint when ex finishes. */ +#define VIP_N_EX_REDRAW 0x0008 /* Schedule SC_SCR_REDRAW when ex finishes. */ +#define VIP_N_REFRESH 0x0010 /* Repaint (from SMAP) on the next refresh. */ +#define VIP_N_RENUMBER 0x0020 /* Renumber screen on the next refresh. */ +#define VIP_RCM_LAST 0x0040 /* Cursor drawn to the last column. */ +#define VIP_S_MODELINE 0x0080 /* Skip next modeline refresh. */ +#define VIP_S_REFRESH 0x0100 /* Skip next refresh. */ + u_int16_t flags; +} VI_PRIVATE; + +/* Vi private area. */ +#define VIP(sp) ((VI_PRIVATE *)((sp)->vi_private)) + +#define O_NUMBER_FMT "%7lu " /* O_NUMBER format, length. */ +#define O_NUMBER_LENGTH 8 +#define SCREEN_COLS(sp) /* Screen columns. */ \ + ((O_ISSET(sp, O_NUMBER) ? (sp)->cols - O_NUMBER_LENGTH : (sp)->cols)) + +/* + * LASTLINE is the zero-based, last line in the screen. Note that it is correct + * regardless of the changes in the screen to permit text input on the last line + * of the screen, or the existence of small screens. + */ +#define LASTLINE(sp) \ + ((sp)->t_maxrows < (sp)->rows ? (sp)->t_maxrows : (sp)->rows - 1) + +/* + * Small screen (see vs_refresh.c, section 6a) and one-line screen test. + * Note, both cannot be true for the same screen. + */ +#define IS_SMALL(sp) ((sp)->t_minrows != (sp)->t_maxrows) +#define IS_ONELINE(sp) ((sp)->rows == 1) + +#define HALFTEXT(sp) /* Half text. */ \ + ((sp)->t_rows == 1 ? 1 : (sp)->t_rows / 2) +#define HALFSCREEN(sp) /* Half text screen. */ \ + ((sp)->t_maxrows == 1 ? 1 : (sp)->t_maxrows / 2) + +/* + * 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 believe that nvi does tabs correctly, + * but there are some historical incompatibilities. + */ +#define TAB_OFF(c) COL_OFF((c), O_VAL(sp, O_TABSTOP)) + +/* If more than one screen being shown. */ +#define IS_SPLIT(sp) \ + ((sp)->q.cqe_next != (void *)&(sp)->gp->dq || \ + (sp)->q.cqe_prev != (void *)&(sp)->gp->dq) + +/* Screen adjustment operations. */ +typedef enum { A_DECREASE, A_INCREASE, A_SET } adj_t; + +/* Screen position operations. */ +typedef enum { P_BOTTOM, P_FILL, P_MIDDLE, P_TOP } pos_t; + +/* Scrolling operations. */ +typedef enum { + CNTRL_B, CNTRL_D, CNTRL_E, CNTRL_F, + CNTRL_U, CNTRL_Y, Z_CARAT, Z_PLUS +} scroll_t; + +/* Vi common error messages. */ +typedef enum { + VIM_COMBUF, VIM_EMPTY, VIM_EOF, VIM_EOL, + VIM_NOCOM, VIM_NOCOM_B, VIM_USAGE, VIM_WRESIZE +} vim_t; + +#include "vi_extern.h" diff --git a/contrib/nvi/vi/vs_line.c b/contrib/nvi/vi/vs_line.c new file mode 100644 index 0000000..b439de9 --- /dev/null +++ b/contrib/nvi/vi/vs_line.c @@ -0,0 +1,514 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)vs_line.c 10.19 (Berkeley) 9/26/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +#ifdef VISIBLE_TAB_CHARS +#define TABCH '-' +#else +#define TABCH ' ' +#endif + +/* + * vs_line -- + * Update one line on the screen. + * + * PUBLIC: int vs_line __P((SCR *, SMAP *, size_t *, size_t *)); + */ +int +vs_line(sp, smp, yp, xp) + SCR *sp; + SMAP *smp; + size_t *xp, *yp; +{ + CHAR_T *kp; + GS *gp; + SMAP *tsmp; + size_t chlen, cno_cnt, cols_per_screen, len, nlen; + size_t offset_in_char, offset_in_line, oldx, oldy; + size_t scno, skip_cols, skip_screens; + int ch, dne, is_cached, is_partial, is_tab; + int list_tab, list_dollar; + char *p, *cbp, *ecbp, cbuf[128]; + +#if defined(DEBUG) && 0 + TRACE(sp, "vs_line: row %u: line: %u off: %u\n", + smp - HMAP, smp->lno, smp->off); +#endif + /* + * If ex modifies the screen after ex output is already on the screen, + * don't touch it -- we'll get scrolling wrong, at best. + */ + if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1) + return (0); + if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp)) + return (0); + + /* + * 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 vs_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. + */ + gp = sp->gp; + (void)gp->scr_cursor(sp, &oldy, &oldx); + (void)gp->scr_move(sp, smp - HMAP, 0); + + /* Get the line. */ + dne = db_get(sp, smp->lno, 0, &p, &len); + + /* + * Special case if we're printing the info/mode line. Skip printing + * the leading number, as well as other minor setup. 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 columns for this screen. + * Set the number of chars or screens to skip until a character is to + * be displayed. + */ + cols_per_screen = sp->cols; + if (O_ISSET(sp, O_LEFTRIGHT)) { + skip_screens = 0; + skip_cols = smp->coff; + } else { + skip_screens = smp->soff - 1; + skip_cols = skip_screens * cols_per_screen; + } + + list_tab = O_ISSET(sp, O_LIST); + if (F_ISSET(sp, SC_TINPUT_INFO)) + list_dollar = 0; + else { + list_dollar = list_tab; + + /* + * If O_NUMBER is set, the line doesn't exist and it's line + * number 1, i.e., an empty file, display the line number. + * + * If O_NUMBER is set, the line exists and the first character + * on the screen is the first character in the line, display + * the line number. + * + * !!! + * If O_NUMBER set, decrement the number of columns in the + * first screen. DO NOT CHANGE THIS -- IT'S RIGHT! The + * rest of the code expects this to reflect the number of + * columns in the first screen, regardless of the number of + * columns we're going to skip. + */ + if (O_ISSET(sp, O_NUMBER)) { + cols_per_screen -= O_NUMBER_LENGTH; + if ((!dne || smp->lno == 1) && skip_cols == 0) { + nlen = snprintf(cbuf, + sizeof(cbuf), O_NUMBER_FMT, smp->lno); + (void)gp->scr_addstr(sp, cbuf, nlen); + } + } + } + + /* + * Special case non-existent lines and the first line of an empty + * file. In both cases, the cursor position is 0, but corrected + * as necessary for the O_NUMBER field, if it was displayed. + */ + if (dne || 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 ret1; + + /* Set line cache information. */ + smp->c_sboff = smp->c_eboff = 0; + smp->c_scoff = smp->c_eclen = 0; + + /* + * Lots of special cases for empty lines, but they only apply + * if we're displaying the first screen of the line. + */ + if (skip_cols == 0) + if (dne) { + if (smp->lno == 1) { + if (list_dollar) { + ch = '$'; + goto empty; + } + } else { + ch = '~'; + goto empty; + } + } else + if (list_dollar) { + ch = '$'; +empty: (void)gp->scr_addstr(sp, + KEY_NAME(sp, ch), KEY_LEN(sp, ch)); + } + + (void)gp->scr_clrtoeol(sp); + (void)gp->scr_move(sp, oldy, oldx); + return (0); + } + + /* + * If we just wrote this or a previous line, 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 direction, 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. + * + * Test for the first screen of the line. + */ + if (skip_cols == 0) { + smp->c_sboff = offset_in_line = 0; + smp->c_scoff = offset_in_char = 0; + p = &p[offset_in_line]; + goto display; + } + + /* Test to see if we've seen this exact line before. */ + if (is_cached) { + offset_in_line = smp->c_sboff; + offset_in_char = smp->c_scoff; + p = &p[offset_in_line]; + + /* Set cols_per_screen to 2nd and later line length. */ + if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) + cols_per_screen = sp->cols; + goto display; + } + + /* Test to see if we saw an earlier part of this line before. */ + 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]; + + /* Set cols_per_screen to 2nd and later line length. */ + if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) + cols_per_screen = sp->cols; + goto display; + } + + scno = 0; + offset_in_line = 0; + offset_in_char = 0; + + /* Do it the hard way, for leftright scrolling screens. */ + if (O_ISSET(sp, O_LEFTRIGHT)) { + for (; offset_in_line < len; ++offset_in_line) { + chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ? + TAB_OFF(scno) : KEY_LEN(sp, ch); + if ((scno += chlen) >= skip_cols) + break; + } + + /* Set cols_per_screen to 2nd and later line length. */ + cols_per_screen = sp->cols; + + /* Put starting info for this line in the cache. */ + if (scno != skip_cols) { + smp->c_sboff = offset_in_line; + smp->c_scoff = + offset_in_char = chlen - (scno - skip_cols); + --p; + } else { + smp->c_sboff = ++offset_in_line; + smp->c_scoff = 0; + } + } + + /* Do it the hard way, for historic line-folding screens. */ + else { + for (; offset_in_line < len; ++offset_in_line) { + chlen = (ch = *(u_char *)p++) == '\t' && !list_tab ? + TAB_OFF(scno) : KEY_LEN(sp, ch); + if ((scno += chlen) < cols_per_screen) + continue; + scno -= cols_per_screen; + + /* Set cols_per_screen to 2nd and later line length. */ + cols_per_screen = sp->cols; + + /* + * If crossed the last skipped screen boundary, start + * displaying the characters. + */ + if (--skip_screens == 0) + break; + } + + /* Put starting info for this line in the cache. */ + if (scno != 0) { + 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; + } + } + +display: + /* + * Set the number of characters to skip before reaching the cursor + * character. Offset by 1 and use 0 as a flag value. Vs_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 ret1; + } else + cno_cnt = (sp->cno - offset_in_line) + 1; + + /* This is the loop that actually displays characters. */ + ecbp = (cbp = cbuf) + sizeof(cbuf) - 1; + 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(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) { + if (is_tab == 1) { + chlen -= scno - cols_per_screen; + smp->c_ecsize = smp->c_eclen = chlen; + scno = cols_per_screen; + } else { + smp->c_ecsize = chlen; + chlen -= scno - cols_per_screen; + smp->c_eclen = chlen; + + if (scno > cols_per_screen) + is_partial = 1; + } + smp->c_eboff = offset_in_line; + + /* 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, SC_TINPUT) || !is_partial)) { + *yp = smp - HMAP; + if (F_ISSET(sp, SC_TINPUT)) + *xp = scno - chlen; + else + *xp = scno - 1; + if (O_ISSET(sp, O_NUMBER) && + !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0) + *xp += O_NUMBER_LENGTH; + + /* If the line is on the screen, quit. */ + if (is_cached) + goto ret1; + } + + /* If the line is on the screen, don't display anything. */ + if (is_cached) + continue; + +#define FLUSH { \ + *cbp = '\0'; \ + (void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \ + cbp = cbuf; \ +} + /* + * Display the character. We do tab expansion here because + * the screen interface doesn't have any way to set the tab + * length. Note, it's theoretically possible for chlen to + * be larger than cbuf, if the user set a impossibly large + * tabstop. + */ + if (is_tab) + while (chlen--) { + if (cbp >= ecbp) + FLUSH; + *cbp++ = TABCH; + } + else { + if (cbp + chlen >= ecbp) + FLUSH; + for (kp = KEY_NAME(sp, ch) + offset_in_char; chlen--;) + *cbp++ = *kp++; + } + } + + 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; + + chlen = KEY_LEN(sp, '$'); + if (cbp + chlen >= ecbp) + FLUSH; + for (kp = KEY_NAME(sp, '$'); chlen--;) + *cbp++ = *kp++; + } + + /* If still didn't paint the whole line, clear the rest. */ + if (scno < cols_per_screen) + (void)gp->scr_clrtoeol(sp); + } + + /* Flush any buffered characters. */ + if (cbp > cbuf) + FLUSH; + +ret1: (void)gp->scr_move(sp, oldy, oldx); + return (0); +} + +/* + * vs_number -- + * Repaint the numbers on all the lines. + * + * PUBLIC: int vs_number __P((SCR *)); + */ +int +vs_number(sp) + SCR *sp; +{ + GS *gp; + SMAP *smp; + VI_PRIVATE *vip; + size_t len, oldy, oldx; + int exist; + char nbuf[10]; + + gp = sp->gp; + vip = VIP(sp); + + /* No reason to do anything if we're in input mode on the info line. */ + if (F_ISSET(sp, SC_TINPUT_INFO)) + return (0); + + /* + * 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. (The problem is that file_lline will lie, and tell us + * that the info line is the last line in the file.) If that test + * fails, we have to check each line for existence. + */ + exist = db_exist(sp, TMAP->lno + 1); + + (void)gp->scr_cursor(sp, &oldy, &oldx); + for (smp = HMAP; smp <= TMAP; ++smp) { + /* Numbers are only displayed for the first screen line. */ + if (O_ISSET(sp, O_LEFTRIGHT)) { + if (smp->coff != 0) + continue; + } else + if (smp->soff != 1) + continue; + + /* + * The first line of an empty file gets numbered, otherwise + * number any existing line. + */ + if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno)) + break; + + (void)gp->scr_move(sp, smp - HMAP, 0); + len = snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, smp->lno); + (void)gp->scr_addstr(sp, nbuf, len); + } + (void)gp->scr_move(sp, oldy, oldx); + return (0); +} diff --git a/contrib/nvi/vi/vs_msg.c b/contrib/nvi/vi/vs_msg.c new file mode 100644 index 0000000..7ef8f53 --- /dev/null +++ b/contrib/nvi/vi/vs_msg.c @@ -0,0 +1,927 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)vs_msg.c 10.77 (Berkeley) 10/13/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "vi.h" + +typedef enum { + SCROLL_W, /* User wait. */ + SCROLL_W_EX, /* User wait, or enter : to continue. */ + SCROLL_W_QUIT /* User wait, or enter q to quit. */ + /* + * SCROLL_W_QUIT has another semantic + * -- only wait if the screen is full + */ +} sw_t; + +static void vs_divider __P((SCR *)); +static void vs_msgsave __P((SCR *, mtype_t, char *, size_t)); +static void vs_output __P((SCR *, mtype_t, const char *, int)); +static void vs_scroll __P((SCR *, int *, sw_t)); +static void vs_wait __P((SCR *, int *, sw_t)); + +/* + * vs_busy -- + * Display, update or clear a busy message. + * + * This routine is the default editor interface for vi busy messages. It + * implements a standard strategy of stealing lines from the bottom of the + * vi text screen. Screens using an alternate method of displaying busy + * messages, e.g. X11 clock icons, should set their scr_busy function to the + * correct function before calling the main editor routine. + * + * PUBLIC: void vs_busy __P((SCR *, const char *, busy_t)); + */ +void +vs_busy(sp, msg, btype) + SCR *sp; + const char *msg; + busy_t btype; +{ + GS *gp; + VI_PRIVATE *vip; + static const char flagc[] = "|/-\\"; + struct timeval tv; + size_t len, notused; + const char *p; + + /* Ex doesn't display busy messages. */ + if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) + return; + + gp = sp->gp; + vip = VIP(sp); + + /* + * Most of this routine is to deal with the screen sharing real estate + * between the normal edit messages and the busy messages. Logically, + * all that's needed is something that puts up a message, periodically + * updates it, and then goes away. + */ + switch (btype) { + case BUSY_ON: + ++vip->busy_ref; + if (vip->totalcount != 0 || vip->busy_ref != 1) + break; + + /* Initialize state for updates. */ + vip->busy_ch = 0; + (void)gettimeofday(&vip->busy_tv, NULL); + + /* Save the current cursor. */ + (void)gp->scr_cursor(sp, &vip->busy_oldy, &vip->busy_oldx); + + /* Display the busy message. */ + p = msg_cat(sp, msg, &len); + (void)gp->scr_move(sp, LASTLINE(sp), 0); + (void)gp->scr_addstr(sp, p, len); + (void)gp->scr_cursor(sp, ¬used, &vip->busy_fx); + (void)gp->scr_clrtoeol(sp); + (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx); + break; + case BUSY_OFF: + if (vip->busy_ref == 0) + break; + --vip->busy_ref; + + /* + * If the line isn't in use for another purpose, clear it. + * Always return to the original position. + */ + if (vip->totalcount == 0 && vip->busy_ref == 0) { + (void)gp->scr_move(sp, LASTLINE(sp), 0); + (void)gp->scr_clrtoeol(sp); + } + (void)gp->scr_move(sp, vip->busy_oldy, vip->busy_oldx); + break; + case BUSY_UPDATE: + if (vip->totalcount != 0 || vip->busy_ref == 0) + break; + + /* Update no more than every 1/8 of a second. */ + (void)gettimeofday(&tv, NULL); + if (((tv.tv_sec - vip->busy_tv.tv_sec) * 1000000 + + (tv.tv_usec - vip->busy_tv.tv_usec)) < 125000) + return; + vip->busy_tv = tv; + + /* Display the update. */ + if (vip->busy_ch == sizeof(flagc) - 1) + vip->busy_ch = 0; + (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx); + (void)gp->scr_addstr(sp, flagc + vip->busy_ch++, 1); + (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx); + break; + } + (void)gp->scr_refresh(sp, 0); +} + +/* + * vs_home -- + * Home the cursor to the bottom row, left-most column. + * + * PUBLIC: void vs_home __P((SCR *)); + */ +void +vs_home(sp) + SCR *sp; +{ + (void)sp->gp->scr_move(sp, LASTLINE(sp), 0); + (void)sp->gp->scr_refresh(sp, 0); +} + +/* + * vs_update -- + * Update a command. + * + * PUBLIC: void vs_update __P((SCR *, const char *, const char *)); + */ +void +vs_update(sp, m1, m2) + SCR *sp; + const char *m1, *m2; +{ + GS *gp; + size_t len, mlen, oldx, oldy; + + gp = sp->gp; + + /* + * This routine displays a message on the bottom line of the screen, + * without updating any of the command structures that would keep it + * there for any period of time, i.e. it is overwritten immediately. + * + * It's used by the ex read and ! commands when the user's command is + * expanded, and by the ex substitution confirmation prompt. + */ + if (F_ISSET(sp, SC_SCR_EXWROTE)) { + (void)ex_printf(sp, + "%s\n", m1 == NULL? "" : m1, m2 == NULL ? "" : m2); + (void)ex_fflush(sp); + } + + /* + * Save the cursor position, the substitute-with-confirmation code + * will have already set it correctly. + */ + (void)gp->scr_cursor(sp, &oldy, &oldx); + + /* Clear the bottom line. */ + (void)gp->scr_move(sp, LASTLINE(sp), 0); + (void)gp->scr_clrtoeol(sp); + + /* + * XXX + * Don't let long file names screw up the screen. + */ + if (m1 != NULL) { + mlen = len = strlen(m1); + if (len > sp->cols - 2) + mlen = len = sp->cols - 2; + (void)gp->scr_addstr(sp, m1, mlen); + } else + len = 0; + if (m2 != NULL) { + mlen = strlen(m2); + if (len + mlen > sp->cols - 2) + mlen = (sp->cols - 2) - len; + (void)gp->scr_addstr(sp, m2, mlen); + } + + (void)gp->scr_move(sp, oldy, oldx); + (void)gp->scr_refresh(sp, 0); +} + +/* + * vs_msg -- + * Display ex output or error messages for the screen. + * + * This routine is the default editor interface for all ex output, and all ex + * and vi error/informational messages. It implements the standard strategy + * of stealing lines from the bottom of the vi text screen. Screens using an + * alternate method of displaying messages, e.g. dialog boxes, should set their + * scr_msg function to the correct function before calling the editor. + * + * PUBLIC: void vs_msg __P((SCR *, mtype_t, char *, size_t)); + */ +void +vs_msg(sp, mtype, line, len) + SCR *sp; + mtype_t mtype; + char *line; + size_t len; +{ + GS *gp; + VI_PRIVATE *vip; + size_t maxcols, oldx, oldy, padding; + const char *e, *s, *t; + + gp = sp->gp; + vip = VIP(sp); + + /* + * Ring the bell if it's scheduled. + * + * XXX + * Shouldn't we save this, too? + */ + if (F_ISSET(sp, SC_TINPUT_INFO) || F_ISSET(gp, G_BELLSCHED)) + if (F_ISSET(sp, SC_SCR_VI)) { + F_CLR(gp, G_BELLSCHED); + (void)gp->scr_bell(sp); + } else + F_SET(gp, G_BELLSCHED); + + /* + * If vi is using the error line for text input, there's no screen + * real-estate for the error message. Nothing to do without some + * information as to how important the error message is. + */ + if (F_ISSET(sp, SC_TINPUT_INFO)) + return; + + /* + * Ex or ex controlled screen output. + * + * If output happens during startup, e.g., a .exrc file, we may be + * in ex mode but haven't initialized the screen. Initialize here, + * and in this case, stay in ex mode. + * + * If the SC_SCR_EXWROTE bit is set, then we're switching back and + * forth between ex and vi, but the screen is trashed and we have + * to respect that. Switch to ex mode long enough to put out the + * message. + * + * If the SC_EX_WAIT_NO bit is set, turn it off -- we're writing to + * the screen, so previous opinions are ignored. + */ + if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) { + if (!F_ISSET(sp, SC_SCR_EX)) + if (F_ISSET(sp, SC_SCR_EXWROTE)) { + if (sp->gp->scr_screen(sp, SC_EX)) + return; + } else + if (ex_init(sp)) + return; + + if (mtype == M_ERR) + (void)gp->scr_attr(sp, SA_INVERSE, 1); + (void)printf("%.*s", (int)len, line); + if (mtype == M_ERR) + (void)gp->scr_attr(sp, SA_INVERSE, 0); + (void)fflush(stdout); + + F_CLR(sp, SC_EX_WAIT_NO); + + if (!F_ISSET(sp, SC_SCR_EX)) + (void)sp->gp->scr_screen(sp, SC_VI); + return; + } + + /* If the vi screen isn't ready, save the message. */ + if (!F_ISSET(sp, SC_SCR_VI)) { + (void)vs_msgsave(sp, mtype, line, len); + return; + } + + /* Save the cursor position. */ + (void)gp->scr_cursor(sp, &oldy, &oldx); + + /* If it's an ex output message, just write it out. */ + if (mtype == M_NONE) { + vs_output(sp, mtype, line, len); + goto ret; + } + + /* + * If it's a vi message, strip the trailing <newline> so we can + * try and paste messages together. + */ + if (line[len - 1] == '\n') + --len; + + /* + * If a message won't fit on a single line, try to split on a <blank>. + * If a subsequent message fits on the same line, write a separator + * and output it. Otherwise, put out a newline. + * + * Need up to two padding characters normally; a semi-colon and a + * separating space. If only a single line on the screen, add some + * more for the trailing continuation message. + * + * XXX + * Assume that periods and semi-colons take up a single column on the + * screen. + * + * XXX + * There are almost certainly pathological cases that will break this + * code. + */ + if (IS_ONELINE(sp)) + (void)msg_cmsg(sp, CMSG_CONT_S, &padding); + else + padding = 0; + padding += 2; + + maxcols = sp->cols - 1; + if (vip->lcontinue != 0) + if (len + vip->lcontinue + padding > maxcols) + vs_output(sp, vip->mtype, ".\n", 2); + else { + vs_output(sp, vip->mtype, ";", 1); + vs_output(sp, M_NONE, " ", 1); + } + vip->mtype = mtype; + for (s = line;; s = t) { + for (; len > 0 && isblank(*s); --len, ++s); + if (len == 0) + break; + if (len + vip->lcontinue > maxcols) { + for (e = s + (maxcols - vip->lcontinue); + e > s && !isblank(*e); --e); + if (e == s) + e = t = s + (maxcols - vip->lcontinue); + else + for (t = e; isblank(e[-1]); --e); + } else + e = t = s + len; + + /* + * If the message ends in a period, discard it, we want to + * gang messages where possible. + */ + len -= t - s; + if (len == 0 && (e - s) > 1 && s[(e - s) - 1] == '.') + --e; + vs_output(sp, mtype, s, e - s); + + if (len != 0) + vs_output(sp, M_NONE, "\n", 1); + + if (INTERRUPTED(sp)) + break; + } + +ret: (void)gp->scr_move(sp, oldy, oldx); + (void)gp->scr_refresh(sp, 0); +} + +/* + * vs_output -- + * Output the text to the screen. + */ +static void +vs_output(sp, mtype, line, llen) + SCR *sp; + mtype_t mtype; + const char *line; + int llen; +{ + CHAR_T *kp; + GS *gp; + VI_PRIVATE *vip; + size_t chlen, notused; + int ch, len, rlen, tlen; + const char *p, *t; + char *cbp, *ecbp, cbuf[128]; + + gp = sp->gp; + vip = VIP(sp); + for (p = line, rlen = llen; llen > 0;) { + /* Get the next physical 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 + vip->lcontinue > sp->cols) + len = sp->cols - vip->lcontinue; + + /* + * If the first line output, do nothing. If the second line + * output, draw the divider line. If drew a full screen, we + * remove the divider line. If it's a continuation line, move + * to the continuation point, else, move the screen up. + */ + if (vip->lcontinue == 0) { + if (!IS_ONELINE(sp)) { + if (vip->totalcount == 1) { + (void)gp->scr_move(sp, + LASTLINE(sp) - 1, 0); + (void)gp->scr_clrtoeol(sp); + (void)vs_divider(sp); + F_SET(vip, VIP_DIVIDER); + ++vip->totalcount; + ++vip->linecount; + } + if (vip->totalcount == sp->t_maxrows && + F_ISSET(vip, VIP_DIVIDER)) { + --vip->totalcount; + --vip->linecount; + F_CLR(vip, VIP_DIVIDER); + } + } + if (vip->totalcount != 0) + vs_scroll(sp, NULL, SCROLL_W_QUIT); + + (void)gp->scr_move(sp, LASTLINE(sp), 0); + ++vip->totalcount; + ++vip->linecount; + + if (INTERRUPTED(sp)) + break; + } else + (void)gp->scr_move(sp, LASTLINE(sp), vip->lcontinue); + + /* Error messages are in inverse video. */ + if (mtype == M_ERR) + (void)gp->scr_attr(sp, SA_INVERSE, 1); + + /* Display the line, doing character translation. */ +#define FLUSH { \ + *cbp = '\0'; \ + (void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \ + cbp = cbuf; \ +} + ecbp = (cbp = cbuf) + sizeof(cbuf) - 1; + for (t = line, tlen = len; tlen--; ++t) { + ch = *t; + /* + * Replace tabs with spaces, there are places in + * ex that do column calculations without looking + * at <tabs> -- and all routines that care about + * <tabs> do their own expansions. This catches + * <tabs> in things like tag search strings. + */ + if (ch == '\t') + ch = ' '; + chlen = KEY_LEN(sp, ch); + if (cbp + chlen >= ecbp) + FLUSH; + for (kp = KEY_NAME(sp, ch); chlen--;) + *cbp++ = *kp++; + } + if (cbp > cbuf) + FLUSH; + if (mtype == M_ERR) + (void)gp->scr_attr(sp, SA_INVERSE, 0); + + /* Clear the rest of the line. */ + (void)gp->scr_clrtoeol(sp); + + /* If we loop, it's a new line. */ + vip->lcontinue = 0; + + /* Reset for the next line. */ + line += len; + llen -= len; + if (p != NULL) { + ++line; + --llen; + } + } + + /* Set up next continuation line. */ + if (p == NULL) + gp->scr_cursor(sp, ¬used, &vip->lcontinue); +} + +/* + * vs_ex_resolve -- + * Deal with ex message output. + * + * This routine is called when exiting a colon command to resolve any ex + * output that may have occurred. + * + * PUBLIC: int vs_ex_resolve __P((SCR *, int *)); + */ +int +vs_ex_resolve(sp, continuep) + SCR *sp; + int *continuep; +{ + EVENT ev; + GS *gp; + VI_PRIVATE *vip; + sw_t wtype; + + gp = sp->gp; + vip = VIP(sp); + *continuep = 0; + + /* If we ran any ex command, we can't trust the cursor position. */ + F_SET(vip, VIP_CUR_INVALID); + + /* Terminate any partially written message. */ + if (vip->lcontinue != 0) { + vs_output(sp, vip->mtype, ".", 1); + vip->lcontinue = 0; + + vip->mtype = M_NONE; + } + + /* + * If we switched out of the vi screen into ex, switch back while we + * figure out what to do with the screen and potentially get another + * command to execute. + * + * If we didn't switch into ex, we're not required to wait, and less + * than 2 lines of output, we can continue without waiting for the + * wait. + * + * Note, all other code paths require waiting, so we leave the report + * of modified lines until later, so that we won't wait for no other + * reason than a threshold number of lines were modified. This means + * we display cumulative line modification reports for groups of ex + * commands. That seems right to me (well, at least not wrong). + */ + if (F_ISSET(sp, SC_SCR_EXWROTE)) { + if (sp->gp->scr_screen(sp, SC_VI)) + return (1); + } else + if (!F_ISSET(sp, SC_EX_WAIT_YES) && vip->totalcount < 2) { + F_CLR(sp, SC_EX_WAIT_NO); + return (0); + } + + /* Clear the required wait flag, it's no longer needed. */ + F_CLR(sp, SC_EX_WAIT_YES); + + /* + * Wait, unless explicitly told not to wait or the user interrupted + * the command. If the user is leaving the screen, for any reason, + * they can't continue with further ex commands. + */ + if (!F_ISSET(sp, SC_EX_WAIT_NO) && !INTERRUPTED(sp)) { + wtype = F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | + SC_FSWITCH | SC_SSWITCH) ? SCROLL_W : SCROLL_W_EX; + if (F_ISSET(sp, SC_SCR_EXWROTE)) + vs_wait(sp, continuep, wtype); + else + vs_scroll(sp, continuep, wtype); + if (*continuep) + return (0); + } + + /* If ex wrote on the screen, refresh the screen image. */ + if (F_ISSET(sp, SC_SCR_EXWROTE)) + F_SET(vip, VIP_N_EX_PAINT); + + /* + * If we're not the bottom of the split screen stack, the screen + * image itself is wrong, so redraw everything. + */ + if (sp->q.cqe_next != (void *)&sp->gp->dq) + F_SET(sp, SC_SCR_REDRAW); + + /* If ex changed the underlying file, the map itself is wrong. */ + if (F_ISSET(vip, VIP_N_EX_REDRAW)) + F_SET(sp, SC_SCR_REFORMAT); + + /* Ex may have switched out of the alternate screen, return. */ + (void)gp->scr_attr(sp, SA_ALTERNATE, 1); + + /* + * Whew. We're finally back home, after what feels like years. + * Kiss the ground. + */ + F_CLR(sp, SC_SCR_EXWROTE | SC_EX_WAIT_NO); + + /* + * We may need to repaint some of the screen, e.g.: + * + * :set + * :!ls + * + * gives us a combination of some lines that are "wrong", and a need + * for a full refresh. + */ + if (vip->totalcount > 1) { + /* Set up the redraw of the overwritten lines. */ + ev.e_event = E_REPAINT; + ev.e_flno = vip->totalcount >= + sp->rows ? 1 : sp->rows - vip->totalcount; + ev.e_tlno = sp->rows; + + /* Reset the count of overwriting lines. */ + vip->linecount = vip->lcontinue = vip->totalcount = 0; + + /* Redraw. */ + (void)vs_repaint(sp, &ev); + } else + /* Reset the count of overwriting lines. */ + vip->linecount = vip->lcontinue = vip->totalcount = 0; + + return (0); +} + +/* + * vs_resolve -- + * Deal with message output. + * + * PUBLIC: int vs_resolve __P((SCR *, SCR *, int)); + */ +int +vs_resolve(sp, csp, forcewait) + SCR *sp, *csp; + int forcewait; +{ + EVENT ev; + GS *gp; + MSGS *mp; + VI_PRIVATE *vip; + size_t oldy, oldx; + int redraw; + + /* + * Vs_resolve is called from the main vi loop and the refresh function + * to periodically ensure that the user has seen any messages that have + * been displayed and that any status lines are correct. The sp screen + * is the screen we're checking, usually the current screen. When it's + * not, csp is the current screen, used for final cursor positioning. + */ + gp = sp->gp; + vip = VIP(sp); + if (csp == NULL) + csp = sp; + + /* Save the cursor position. */ + (void)gp->scr_cursor(csp, &oldy, &oldx); + + /* Ring the bell if it's scheduled. */ + if (F_ISSET(gp, G_BELLSCHED)) { + F_CLR(gp, G_BELLSCHED); + (void)gp->scr_bell(sp); + } + + /* Display new file status line. */ + if (F_ISSET(sp, SC_STATUS)) { + F_CLR(sp, SC_STATUS); + msgq_status(sp, sp->lno, MSTAT_TRUNCATE); + } + + /* Report on line modifications. */ + mod_rpt(sp); + + /* + * Flush any saved messages. If the screen isn't ready, refresh + * it. (A side-effect of screen refresh is that we can display + * messages.) Once this is done, don't trust the cursor. That + * extra refresh screwed the pooch. + */ + if (gp->msgq.lh_first != NULL) { + if (!F_ISSET(sp, SC_SCR_VI) && vs_refresh(sp, 1)) + return (1); + while ((mp = gp->msgq.lh_first) != NULL) { + gp->scr_msg(sp, mp->mtype, mp->buf, mp->len); + LIST_REMOVE(mp, q); + free(mp->buf); + free(mp); + } + F_SET(vip, VIP_CUR_INVALID); + } + + switch (vip->totalcount) { + case 0: + redraw = 0; + break; + case 1: + /* + * If we're switching screens, we have to wait for messages, + * regardless. If we don't wait, skip updating the modeline. + */ + if (forcewait) + vs_scroll(sp, NULL, SCROLL_W); + else + F_SET(vip, VIP_S_MODELINE); + + redraw = 0; + break; + default: + /* + * If >1 message line in use, prompt the user to continue and + * repaint overwritten lines. + */ + vs_scroll(sp, NULL, SCROLL_W); + + ev.e_event = E_REPAINT; + ev.e_flno = vip->totalcount >= + sp->rows ? 1 : sp->rows - vip->totalcount; + ev.e_tlno = sp->rows; + + redraw = 1; + break; + } + + /* Reset the count of overwriting lines. */ + vip->linecount = vip->lcontinue = vip->totalcount = 0; + + /* Redraw. */ + if (redraw) + (void)vs_repaint(sp, &ev); + + /* Restore the cursor position. */ + (void)gp->scr_move(csp, oldy, oldx); + + return (0); +} + +/* + * vs_scroll -- + * Scroll the screen for output. + */ +static void +vs_scroll(sp, continuep, wtype) + SCR *sp; + int *continuep; + sw_t wtype; +{ + GS *gp; + VI_PRIVATE *vip; + + gp = sp->gp; + vip = VIP(sp); + if (!IS_ONELINE(sp)) { + /* + * 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. + */ + (void)gp->scr_move(sp, vip->totalcount < + sp->rows ? LASTLINE(sp) - vip->totalcount : 0, 0); + (void)gp->scr_deleteln(sp); + + /* If there are screens below us, push them back into place. */ + if (sp->q.cqe_next != (void *)&sp->gp->dq) { + (void)gp->scr_move(sp, LASTLINE(sp), 0); + (void)gp->scr_insertln(sp); + } + } + if (wtype == SCROLL_W_QUIT && vip->linecount < sp->t_maxrows) + return; + vs_wait(sp, continuep, wtype); +} + +/* + * vs_wait -- + * Prompt the user to continue. + */ +static void +vs_wait(sp, continuep, wtype) + SCR *sp; + int *continuep; + sw_t wtype; +{ + EVENT ev; + VI_PRIVATE *vip; + const char *p; + GS *gp; + size_t len; + + gp = sp->gp; + vip = VIP(sp); + + (void)gp->scr_move(sp, LASTLINE(sp), 0); + if (IS_ONELINE(sp)) + p = msg_cmsg(sp, CMSG_CONT_S, &len); + else + switch (wtype) { + case SCROLL_W_QUIT: + p = msg_cmsg(sp, CMSG_CONT_Q, &len); + break; + case SCROLL_W_EX: + p = msg_cmsg(sp, CMSG_CONT_EX, &len); + break; + case SCROLL_W: + p = msg_cmsg(sp, CMSG_CONT, &len); + break; + default: + abort(); + /* NOTREACHED */ + } + (void)gp->scr_addstr(sp, p, len); + + ++vip->totalcount; + vip->linecount = 0; + + (void)gp->scr_clrtoeol(sp); + (void)gp->scr_refresh(sp, 0); + + /* Get a single character from the terminal. */ + if (continuep != NULL) + *continuep = 0; + for (;;) { + if (v_event_get(sp, &ev, 0, 0)) + return; + if (ev.e_event == E_CHARACTER) + break; + if (ev.e_event == E_INTERRUPT) { + ev.e_c = CH_QUIT; + F_SET(gp, G_INTERRUPTED); + break; + } + (void)gp->scr_bell(sp); + } + switch (wtype) { + case SCROLL_W_QUIT: + if (ev.e_c == CH_QUIT) + F_SET(gp, G_INTERRUPTED); + break; + case SCROLL_W_EX: + if (ev.e_c == ':' && continuep != NULL) + *continuep = 1; + break; + case SCROLL_W: + break; + } +} + +/* + * vs_divider -- + * Draw a dividing line between the screen and the output. + */ +static void +vs_divider(sp) + SCR *sp; +{ + GS *gp; + size_t len; + +#define DIVIDESTR "+=+=+=+=+=+=+=+" + len = + sizeof(DIVIDESTR) - 1 > sp->cols ? sp->cols : sizeof(DIVIDESTR) - 1; + gp = sp->gp; + (void)gp->scr_attr(sp, SA_INVERSE, 1); + (void)gp->scr_addstr(sp, DIVIDESTR, len); + (void)gp->scr_attr(sp, SA_INVERSE, 0); +} + +/* + * vs_msgsave -- + * Save a message for later display. + */ +static void +vs_msgsave(sp, mt, p, len) + SCR *sp; + mtype_t mt; + char *p; + size_t len; +{ + GS *gp; + MSGS *mp_c, *mp_n; + + /* + * We have to handle messages before we have any place to put them. + * If there's no screen support yet, allocate a msg structure, copy + * in the message, and queue it on the global structure. If we can't + * allocate memory here, we're genuinely screwed, dump the message + * to stderr in the (probably) vain hope that someone will see it. + */ + CALLOC_GOTO(sp, mp_n, MSGS *, 1, sizeof(MSGS)); + MALLOC_GOTO(sp, mp_n->buf, char *, len); + + memmove(mp_n->buf, p, len); + mp_n->len = len; + mp_n->mtype = mt; + + gp = sp->gp; + if ((mp_c = gp->msgq.lh_first) == NULL) { + LIST_INSERT_HEAD(&gp->msgq, mp_n, q); + } else { + for (; mp_c->q.le_next != NULL; mp_c = mp_c->q.le_next); + LIST_INSERT_AFTER(mp_c, mp_n, q); + } + return; + +alloc_err: + if (mp_n != NULL) + free(mp_n); + (void)fprintf(stderr, "%.*s\n", (int)len, p); +} diff --git a/contrib/nvi/vi/vs_refresh.c b/contrib/nvi/vi/vs_refresh.c new file mode 100644 index 0000000..8158760 --- /dev/null +++ b/contrib/nvi/vi/vs_refresh.c @@ -0,0 +1,885 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)vs_refresh.c 10.44 (Berkeley) 10/13/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +#define UPDATE_CURSOR 0x01 /* Update the cursor. */ +#define UPDATE_SCREEN 0x02 /* Flush to screen. */ + +static void vs_modeline __P((SCR *)); +static int vs_paint __P((SCR *, u_int)); + +/* + * v_repaint -- + * Repaint selected lines from the screen. + * + * PUBLIC: int vs_repaint __P((SCR *, EVENT *)); + */ +int +vs_repaint(sp, evp) + SCR *sp; + EVENT *evp; +{ + SMAP *smp; + + for (; evp->e_flno <= evp->e_tlno; ++evp->e_flno) { + smp = HMAP + evp->e_flno - 1; + SMAP_FLUSH(smp); + if (vs_line(sp, smp, NULL, NULL)) + return (1); + } + return (0); +} + +/* + * vs_refresh -- + * Refresh all screens. + * + * PUBLIC: int vs_refresh __P((SCR *, int)); + */ +int +vs_refresh(sp, forcepaint) + SCR *sp; + int forcepaint; +{ + GS *gp; + SCR *tsp; + int need_refresh; + u_int priv_paint, pub_paint; + + gp = sp->gp; + + /* + * 1: Refresh the screen. + * + * If SC_SCR_REDRAW is set in the current screen, repaint everything + * that we can find, including status lines. + */ + if (F_ISSET(sp, SC_SCR_REDRAW)) + for (tsp = gp->dq.cqh_first; + tsp != (void *)&gp->dq; tsp = tsp->q.cqe_next) + if (tsp != sp) + F_SET(tsp, SC_SCR_REDRAW | SC_STATUS); + + /* + * 2: 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 that aren't exiting that + * have paint or dirty bits set. Always update their screens, we + * are not likely to get another chance. Finally, if we refresh any + * screens other than the current one, the cursor will be trashed. + */ + pub_paint = SC_SCR_REFORMAT | SC_SCR_REDRAW; + priv_paint = VIP_CUR_INVALID | VIP_N_REFRESH; + if (O_ISSET(sp, O_NUMBER)) + priv_paint |= VIP_N_RENUMBER; + for (tsp = gp->dq.cqh_first; + tsp != (void *)&gp->dq; tsp = tsp->q.cqe_next) + if (tsp != sp && !F_ISSET(tsp, SC_EXIT | SC_EXIT_FORCE) && + (F_ISSET(tsp, pub_paint) || + F_ISSET(VIP(tsp), priv_paint))) { + (void)vs_paint(tsp, + (F_ISSET(VIP(tsp), VIP_CUR_INVALID) ? + UPDATE_CURSOR : 0) | UPDATE_SCREEN); + F_SET(VIP(sp), VIP_CUR_INVALID); + } + + /* + * 3: Refresh the current screen. + * + * Always refresh the current screen, it may be a cursor movement. + * Also, always do it last -- that way, SC_SCR_REDRAW can be set + * in the current screen only, and the screen won't flash. + */ + if (vs_paint(sp, UPDATE_CURSOR | (!forcepaint && + F_ISSET(sp, SC_SCR_VI) && KEYS_WAITING(sp) ? 0 : UPDATE_SCREEN))) + return (1); + + /* + * 4: Paint any missing status lines. + * + * XXX + * This is fairly evil. Status lines are written using the vi message + * mechanism, since we have no idea how long they are. Since we may be + * painting screens other than the current one, we don't want to make + * the user wait. We depend heavily on there not being any other lines + * currently waiting to be displayed and the message truncation code in + * the msgq_status routine working. + * + * And, finally, if we updated any status lines, make sure the cursor + * gets back to where it belongs. + */ + for (need_refresh = 0, tsp = gp->dq.cqh_first; + tsp != (void *)&gp->dq; tsp = tsp->q.cqe_next) + if (F_ISSET(tsp, SC_STATUS)) { + need_refresh = 1; + vs_resolve(tsp, sp, 0); + } + if (need_refresh) + (void)gp->scr_refresh(sp, 0); + + /* + * A side-effect of refreshing the screen is that it's now ready + * for everything else, i.e. messages. + */ + F_SET(sp, SC_SCR_VI); + return (0); +} + +/* + * vs_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. + */ +static int +vs_paint(sp, flags) + SCR *sp; + u_int flags; +{ + GS *gp; + SMAP *smp, tmp; + VI_PRIVATE *vip; + recno_t lastline, lcnt; + size_t cwtotal, cnt, len, notused, off, y; + int ch, didpaint, isempty, leftright_warp; + char *p; + +#define LNO sp->lno /* Current file line. */ +#define OLNO vip->olno /* Remembered file line. */ +#define CNO sp->cno /* Current file column. */ +#define OCNO vip->ocno /* Remembered file column. */ +#define SCNO vip->sc_col /* Current screen column. */ + + gp = sp->gp; + vip = VIP(sp); + didpaint = leftright_warp = 0; + + /* + * 5: 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, SC_SCR_REFORMAT)) { + /* Invalidate the line size cache. */ + VI_SCR_CFLUSH(vip); + + /* Toss vs_line() cached information. */ + if (F_ISSET(sp, SC_SCR_TOP)) { + if (vs_sm_fill(sp, LNO, P_TOP)) + return (1); + } + else if (F_ISSET(sp, SC_SCR_CENTER)) { + if (vs_sm_fill(sp, LNO, P_MIDDLE)) + return (1); + } else + if (vs_sm_fill(sp, OOBLNO, P_TOP)) + return (1); + F_SET(sp, SC_SCR_REDRAW); + } + + /* + * 6: Line movement. + * + * Line changes can cause the top line to change as well. As + * before, if the movement is large, the screen is repainted. + * + * 6a: 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 (IS_SMALL(sp)) + if (LNO < HMAP->lno) { + lcnt = vs_sm_nlines(sp, HMAP, LNO, sp->t_maxrows); + if (lcnt <= HALFSCREEN(sp)) + for (; lcnt && sp->t_rows != sp->t_maxrows; + --lcnt, ++sp->t_rows) { + ++TMAP; + if (vs_sm_1down(sp)) + return (1); + } + else + goto small_fill; + } else if (LNO > TMAP->lno) { + lcnt = vs_sm_nlines(sp, TMAP, LNO, sp->t_maxrows); + if (lcnt <= HALFSCREEN(sp)) + for (; lcnt && sp->t_rows != sp->t_maxrows; + --lcnt, ++sp->t_rows) { + if (vs_sm_next(sp, TMAP, TMAP + 1)) + return (1); + ++TMAP; + if (vs_line(sp, TMAP, NULL, NULL)) + return (1); + } + else { +small_fill: (void)gp->scr_move(sp, LASTLINE(sp), 0); + (void)gp->scr_clrtoeol(sp); + for (; sp->t_rows > sp->t_minrows; + --sp->t_rows, --TMAP) { + (void)gp->scr_move(sp, TMAP - HMAP, 0); + (void)gp->scr_clrtoeol(sp); + } + if (vs_sm_fill(sp, LNO, P_FILL)) + return (1); + F_SET(sp, SC_SCR_REDRAW); + goto adjust; + } + } + + /* + * 6b: Line down, or current screen. + */ + if (LNO >= HMAP->lno) { + /* Current screen. */ + if (LNO <= TMAP->lno) + goto adjust; + if (F_ISSET(sp, SC_SCR_TOP)) + goto top; + if (F_ISSET(sp, SC_SCR_CENTER)) + goto middle; + + /* + * If less than half a screen above the line, scroll down + * until the line is on the screen. + */ + lcnt = vs_sm_nlines(sp, TMAP, LNO, HALFTEXT(sp)); + if (lcnt < HALFTEXT(sp)) { + while (lcnt--) + if (vs_sm_1up(sp)) + return (1); + goto adjust; + } + goto bottom; + } + + /* + * 6c: If not on the current screen, may request center or top. + */ + if (F_ISSET(sp, SC_SCR_TOP)) + goto top; + if (F_ISSET(sp, SC_SCR_CENTER)) + goto middle; + + /* + * 6d: Line up. + */ + lcnt = vs_sm_nlines(sp, 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 (db_exist(sp, HMAP->lno)) { + while (lcnt--) + if (vs_sm_1down(sp)) + return (1); + goto adjust; + } + + /* + * If less than a half screen from the bottom of the file, + * put the last line of the file on the bottom of the screen. + */ +bottom: if (db_last(sp, &lastline)) + return (1); + tmp.lno = LNO; + tmp.coff = HMAP->coff; + tmp.soff = 1; + lcnt = vs_sm_nlines(sp, &tmp, lastline, sp->t_rows); + if (lcnt < HALFTEXT(sp)) { + if (vs_sm_fill(sp, lastline, P_BOTTOM)) + return (1); + F_SET(sp, SC_SCR_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.coff = HMAP->coff; + tmp.soff = 1; + lcnt = vs_sm_nlines(sp, &tmp, LNO, HALFTEXT(sp)); + if (lcnt < HALFTEXT(sp)) { + if (vs_sm_fill(sp, 1, P_TOP)) + return (1); + } else +middle: if (vs_sm_fill(sp, LNO, P_MIDDLE)) + return (1); + if (0) { +top: if (vs_sm_fill(sp, LNO, P_TOP)) + return (1); + } + F_SET(sp, SC_SCR_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 = vs_screens(sp, LNO, &CNO); + if (LNO == HMAP->lno && cnt < HMAP->soff) + if ((HMAP->soff - cnt) > HALFTEXT(sp)) { + HMAP->soff = cnt; + vs_sm_fill(sp, OOBLNO, P_TOP); + F_SET(sp, SC_SCR_REDRAW); + } else + while (cnt < HMAP->soff) + if (vs_sm_1down(sp)) + return (1); + if (LNO == TMAP->lno && cnt > TMAP->soff) + if ((cnt - TMAP->soff) > HALFTEXT(sp)) { + TMAP->soff = cnt; + vs_sm_fill(sp, OOBLNO, P_BOTTOM); + F_SET(sp, SC_SCR_REDRAW); + } else + while (cnt > TMAP->soff) + if (vs_sm_1up(sp)) + 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, SC_SCR_REDRAW)) { + if (O_ISSET(sp, O_LEFTRIGHT)) + goto slow; + goto paint; + } + + /* + * 7: Cursor movements (current screen only). + */ + if (!LF_ISSET(UPDATE_CURSOR)) + goto number; + + /* + * 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, reread it.. + */ + if (F_ISSET(vip, VIP_CUR_INVALID) || LNO != OLNO) + goto slow; + + /* Otherwise, if nothing's changed, ignore the cursor. */ + 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 (db_eget(sp, LNO, &p, &len, &isempty)) { + if (isempty) + goto slow; + return (1); + } + +#ifdef DEBUG + /* Sanity checking. */ + 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) { + /* + * 7a: 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 slow; + + /* + * 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. + */ + if (SCNO < cwtotal) + goto slow; + SCNO -= cwtotal; + } else { + /* + * 7b: 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 6a. */ + if (SCNO >= SCREEN_COLS(sp)) + goto slow; + } + + /* + * 7c: Fast cursor update. + * + * We have the current column, retrieve the current row. + */ +fast: (void)gp->scr_cursor(sp, &y, ¬used); + goto done_cursor; + + /* + * 7d: Slow cursor update. + * + * Walk through the map and find the current line. + */ +slow: for (smp = HMAP; smp->lno != LNO; ++smp); + + /* + * 7e: Leftright scrolling adjustment. + * + * If doing left-right scrolling and the cursor movement has changed + * the displayed screen, scroll the screen left or right, unless we're + * updating the info line in which case we just scroll that one line. + * We adjust the offset up or down until we have a window that covers + * the current column, making sure that we adjust differently for the + * first screen as compared to subsequent ones. + */ + if (O_ISSET(sp, O_LEFTRIGHT)) { + /* + * Get the screen column for this character, and correct + * for the number option offset. + */ + cnt = vs_columns(sp, NULL, LNO, &CNO, NULL); + if (O_ISSET(sp, O_NUMBER)) + cnt -= O_NUMBER_LENGTH; + + /* Adjust the window towards the beginning of the line. */ + off = smp->coff; + if (off >= cnt) { + do { + if (off >= O_VAL(sp, O_SIDESCROLL)) + off -= O_VAL(sp, O_SIDESCROLL); + else { + off = 0; + break; + } + } while (off >= cnt); + goto shifted; + } + + /* Adjust the window towards the end of the line. */ + if (off == 0 && off + SCREEN_COLS(sp) < cnt || + off != 0 && off + sp->cols < cnt) { + do { + off += O_VAL(sp, O_SIDESCROLL); + } while (off + sp->cols < cnt); + +shifted: /* Fill in screen map with the new offset. */ + if (F_ISSET(sp, SC_TINPUT_INFO)) + smp->coff = off; + else { + for (smp = HMAP; smp <= TMAP; ++smp) + smp->coff = off; + leftright_warp = 1; + } + goto paint; + } + + /* + * We may have jumped here to adjust a leftright screen because + * redraw was set. If so, we have to paint the entire screen. + */ + if (F_ISSET(sp, SC_SCR_REDRAW)) + goto paint; + } + + /* + * Update the screen lines for this particular file line until we + * have a new screen cursor position. + */ + for (y = -1, + vip->sc_smap = NULL; smp <= TMAP && smp->lno == LNO; ++smp) { + if (vs_line(sp, smp, &y, &SCNO)) + return (1); + if (y != -1) { + vip->sc_smap = smp; + break; + } + } + goto done_cursor; + + /* + * 8: Repaint the entire screen. + * + * Lost big, do what you have to do. We flush the cache, since + * SC_SCR_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 (y = -1, vip->sc_smap = NULL, smp = HMAP; smp <= TMAP; ++smp) { + if (vs_line(sp, smp, &y, &SCNO)) + return (1); + if (y != -1 && vip->sc_smap == NULL) + vip->sc_smap = smp; + } + /* + * If it's a small screen and we're redrawing, clear the unused lines, + * ex may have overwritten them. + */ + if (F_ISSET(sp, SC_SCR_REDRAW) && IS_SMALL(sp)) + for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) { + (void)gp->scr_move(sp, cnt, 0); + (void)gp->scr_clrtoeol(sp); + } + + didpaint = 1; + +done_cursor: + /* + * Sanity checking. When the repainting code messes up, the usual + * result is we don't repaint the cursor and so sc_smap will be + * NULL. If we're debugging, die, otherwise restart from scratch. + */ +#ifdef DEBUG + if (vip->sc_smap == NULL) + abort(); +#else + if (vip->sc_smap == NULL) { + F_SET(sp, SC_SCR_REFORMAT); + return (vs_paint(sp, flags)); + } +#endif + + /* + * 9: Set the remembered cursor values. + */ + OCNO = CNO; + OLNO = LNO; + + /* + * 10: Repaint the line numbers. + * + * If O_NUMBER is set and the VIP_N_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(vip, VIP_N_RENUMBER) && !didpaint && vs_number(sp)) + return (1); + + /* + * 11: Update the mode line, position the cursor, and flush changes. + * + * If we warped the screen, we have to refresh everything. + */ + if (leftright_warp) + LF_SET(UPDATE_CURSOR | UPDATE_SCREEN); + + if (LF_ISSET(UPDATE_SCREEN) && !IS_ONELINE(sp) && + !F_ISSET(vip, VIP_S_MODELINE) && !F_ISSET(sp, SC_TINPUT_INFO)) + vs_modeline(sp); + + if (LF_ISSET(UPDATE_CURSOR)) { + (void)gp->scr_move(sp, y, SCNO); + + /* + * XXX + * If the screen shifted, we recalculate the "most favorite" + * cursor position. Vi won't know that we've warped the + * screen, so it's going to have a wrong idea about where the + * cursor should be. This is vi's problem, and fixing it here + * is a gross layering violation. + */ + if (leftright_warp) + (void)vs_column(sp, &sp->rcm); + } + + if (LF_ISSET(UPDATE_SCREEN)) + (void)gp->scr_refresh(sp, F_ISSET(vip, VIP_N_EX_PAINT)); + + /* 12: Clear the flags that are handled by this routine. */ + F_CLR(sp, SC_SCR_CENTER | SC_SCR_REDRAW | SC_SCR_REFORMAT | SC_SCR_TOP); + F_CLR(vip, VIP_CUR_INVALID | + VIP_N_EX_PAINT | VIP_N_REFRESH | VIP_N_RENUMBER | VIP_S_MODELINE); + + return (0); + +#undef LNO +#undef OLNO +#undef CNO +#undef OCNO +#undef SCNO +} + +/* + * vs_modeline -- + * Update the mode line. + */ +static void +vs_modeline(sp) + SCR *sp; +{ + static char * const modes[] = { + "215|Append", /* SM_APPEND */ + "216|Change", /* SM_CHANGE */ + "217|Command", /* SM_COMMAND */ + "218|Insert", /* SM_INSERT */ + "219|Replace", /* SM_REPLACE */ + }; + GS *gp; + size_t cols, curcol, curlen, endpoint, len, midpoint; + const char *t; + int ellipsis; + char *p, buf[20]; + + gp = sp->gp; + + /* + * 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. + * + * Move to the last line on the screen. + */ + (void)gp->scr_move(sp, LASTLINE(sp), 0); + + /* If more than one screen in the display, show the file name. */ + curlen = 0; + if (IS_SPLIT(sp)) { + for (p = sp->frp->name; *p != '\0'; ++p); + for (ellipsis = 0, cols = sp->cols / 2; --p > sp->frp->name;) { + if (*p == '/') { + ++p; + break; + } + if ((curlen += KEY_LEN(sp, *p)) > cols) { + ellipsis = 3; + curlen += + KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' '); + while (curlen > cols) { + ++p; + curlen -= KEY_LEN(sp, *p); + } + break; + } + } + if (ellipsis) { + while (ellipsis--) + (void)gp->scr_addstr(sp, + KEY_NAME(sp, '.'), KEY_LEN(sp, '.')); + (void)gp->scr_addstr(sp, + KEY_NAME(sp, ' '), KEY_LEN(sp, ' ')); + } + for (; *p != '\0'; ++p) + (void)gp->scr_addstr(sp, + KEY_NAME(sp, *p), KEY_LEN(sp, *p)); + } + + /* Clear the rest of the line. */ + (void)gp->scr_clrtoeol(sp); + + /* + * Display the ruler. If we're not at the midpoint yet, move there. + * Otherwise, add in two extra spaces. + * + * Adjust the current column for the fact that the editor uses it as + * a zero-based number. + * + * XXX + * Assume that numbers, commas, and spaces only take up a single + * column on the screen. + */ + cols = sp->cols - 1; + if (O_ISSET(sp, O_RULER)) { + vs_column(sp, &curcol); + len = + snprintf(buf, sizeof(buf), "%lu,%lu", sp->lno, curcol + 1); + + midpoint = (cols - ((len + 1) / 2)) / 2; + if (curlen < midpoint) { + (void)gp->scr_move(sp, LASTLINE(sp), midpoint); + curlen += len; + } else if (curlen + 2 + len < cols) { + (void)gp->scr_addstr(sp, " ", 2); + curlen += 2 + len; + } + (void)gp->scr_addstr(sp, buf, 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. + */ +#define MODESIZE 9 + endpoint = cols; + if (O_ISSET(sp, O_SHOWMODE)) { + if (F_ISSET(sp->ep, F_MODIFIED)) + --endpoint; + t = msg_cat(sp, modes[sp->showmode], &len); + endpoint -= len; + } + + if (endpoint > curlen + 2) { + (void)gp->scr_move(sp, LASTLINE(sp), endpoint); + if (O_ISSET(sp, O_SHOWMODE)) { + if (F_ISSET(sp->ep, F_MODIFIED)) + (void)gp->scr_addstr(sp, + KEY_NAME(sp, '*'), KEY_LEN(sp, '*')); + (void)gp->scr_addstr(sp, t, len); + } + } +} diff --git a/contrib/nvi/vi/vs_relative.c b/contrib/nvi/vi/vs_relative.c new file mode 100644 index 0000000..c92c10c --- /dev/null +++ b/contrib/nvi/vi/vs_relative.c @@ -0,0 +1,305 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)vs_relative.c 10.11 (Berkeley) 5/13/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +/* + * vs_column -- + * Return the logical column of the cursor in the line. + * + * PUBLIC: int vs_column __P((SCR *, size_t *)); + */ +int +vs_column(sp, colp) + SCR *sp; + size_t *colp; +{ + VI_PRIVATE *vip; + + vip = VIP(sp); + + *colp = (O_ISSET(sp, O_LEFTRIGHT) ? + vip->sc_smap->coff : (vip->sc_smap->soff - 1) * sp->cols) + + vip->sc_col - (O_ISSET(sp, O_NUMBER) ? O_NUMBER_LENGTH : 0); + return (0); +} + +/* + * vs_screens -- + * Return the screens 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. + * + * PUBLIC: size_t vs_screens __P((SCR *, recno_t, size_t *)); + */ +size_t +vs_screens(sp, lno, cnop) + SCR *sp; + recno_t lno; + size_t *cnop; +{ + size_t cols, screens; + + /* Left-right screens are simple, it's always 1. */ + if (O_ISSET(sp, O_LEFTRIGHT)) + return (1); + + /* + * 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 (VIP(sp)->ss_lno == lno) + return (VIP(sp)->ss_screens); + } else if (*cnop == 0) + return (1); + + /* Figure out how many columns the line/column needs. */ + cols = vs_columns(sp, NULL, lno, cnop, NULL); + + screens = (cols / sp->cols + (cols % sp->cols ? 1 : 0)); + if (screens == 0) + screens = 1; + + /* Cache the value. */ + if (cnop == NULL) { + VIP(sp)->ss_lno = lno; + VIP(sp)->ss_screens = screens; + } + return (screens); +} + +/* + * vs_columns -- + * Return the screen columns necessary to display the line, or, + * if specified, the physical character column within the line. + * + * PUBLIC: size_t vs_columns __P((SCR *, char *, recno_t, size_t *, size_t *)); + */ +size_t +vs_columns(sp, lp, lno, cnop, diffp) + SCR *sp; + char *lp; + recno_t lno; + size_t *cnop, *diffp; +{ + size_t chlen, cno, curoff, last, len, scno; + int ch, leftright, listset; + char *p; + + /* Need the line to go any further. */ + if (lp == NULL) { + (void)db_get(sp, lno, 0, &lp, &len); + if (len == 0) + goto done; + } + + /* Missing or empty lines are easy. */ + if (lp == NULL) { +done: if (diffp != NULL) /* XXX */ + *diffp = 0; + return (0); + } + + /* Store away the values of the list and leftright edit options. */ + listset = O_ISSET(sp, O_LIST); + leftright = O_ISSET(sp, O_LEFTRIGHT); + + /* + * Initialize the pointer into the buffer and screen and current + * offsets. + */ + p = lp; + curoff = scno = 0; + + /* Leading number if O_NUMBER option set. */ + if (O_ISSET(sp, O_NUMBER)) + scno += O_NUMBER_LENGTH; + + /* Macro to return the display length of any signal character. */ +#define CHLEN(val) (ch = *(u_char *)p++) == '\t' && \ + !listset ? TAB_OFF(val) : KEY_LEN(sp, ch); + + /* + * If folding screens (the historic vi screen format), past the end + * of the current screen, and the character was a tab, reset the + * current screen column to 0, and the total screen columns to the + * last column of the screen. Otherwise, display the rest of the + * character in the next screen. + */ +#define TAB_RESET { \ + curoff += chlen; \ + if (!leftright && curoff >= sp->cols) \ + if (ch == '\t') { \ + curoff = 0; \ + scno -= scno % sp->cols; \ + } else \ + curoff -= sp->cols; \ +} + if (cnop == NULL) + while (len--) { + chlen = CHLEN(curoff); + last = scno; + scno += chlen; + TAB_RESET; + } + else + for (cno = *cnop;; --cno) { + chlen = CHLEN(curoff); + last = scno; + scno += chlen; + TAB_RESET; + if (cno == 0) + break; + } + + /* Add the trailing '$' if the O_LIST option set. */ + if (listset && cnop == NULL) + scno += KEY_LEN(sp, '$'); + + /* + * The text input screen code needs to know how much additional + * room the last two characters required, so that it can handle + * tab character displays correctly. + */ + if (diffp != NULL) + *diffp = scno - last; + return (scno); +} + +/* + * vs_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). + * + * PUBLIC: size_t vs_rcm __P((SCR *, recno_t, int)); + */ +size_t +vs_rcm(sp, lno, islast) + SCR *sp; + recno_t lno; + int islast; +{ + size_t len; + + /* Last character is easy, and common. */ + if (islast) { + if (db_get(sp, lno, 0, NULL, &len) || len == 0) + return (0); + return (len - 1); + } + + /* First character is easy, and common. */ + if (sp->rcm == 0) + return (0); + + return (vs_colpos(sp, lno, sp->rcm)); +} + +/* + * vs_colpos -- + * Return the physical column from the line that will display a + * character closest to the specified screen column. + * + * PUBLIC: size_t vs_colpos __P((SCR *, recno_t, size_t)); + */ +size_t +vs_colpos(sp, lno, cno) + SCR *sp; + recno_t lno; + size_t cno; +{ + size_t chlen, curoff, len, llen, off, scno; + int ch, leftright, listset; + char *lp, *p; + + /* Need the line to go any further. */ + (void)db_get(sp, lno, 0, &lp, &llen); + + /* Missing or empty lines are easy. */ + if (lp == NULL || llen == 0) + return (0); + + /* Store away the values of the list and leftright edit options. */ + listset = O_ISSET(sp, O_LIST); + leftright = O_ISSET(sp, O_LEFTRIGHT); + + /* Discard screen (logical) lines. */ + off = cno / sp->cols; + cno %= sp->cols; + for (scno = 0, p = lp, len = llen; off--;) { + for (; len && scno < sp->cols; --len) + scno += CHLEN(scno); + + /* + * If reached the end of the physical line, return the last + * physical character in the line. + */ + if (len == 0) + return (llen - 1); + + /* + * If folding screens (the historic vi screen format), past + * the end of the current screen, and the character was a tab, + * reset the current screen column to 0. Otherwise, the rest + * of the character is displayed in the next screen. + */ + if (leftright && ch == '\t') + scno = 0; + else + scno -= sp->cols; + } + + /* Step through the line until reach the right character or EOL. */ + for (curoff = scno; len--;) { + chlen = CHLEN(curoff); + + /* + * 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/contrib/nvi/vi/vs_smap.c b/contrib/nvi/vi/vs_smap.c new file mode 100644 index 0000000..af38057 --- /dev/null +++ b/contrib/nvi/vi/vs_smap.c @@ -0,0 +1,1260 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)vs_smap.c 10.25 (Berkeley) 7/12/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +static int vs_deleteln __P((SCR *, int)); +static int vs_insertln __P((SCR *, int)); +static int vs_sm_delete __P((SCR *, recno_t)); +static int vs_sm_down __P((SCR *, MARK *, recno_t, scroll_t, SMAP *)); +static int vs_sm_erase __P((SCR *)); +static int vs_sm_insert __P((SCR *, recno_t)); +static int vs_sm_reset __P((SCR *, recno_t)); +static int vs_sm_up __P((SCR *, MARK *, recno_t, scroll_t, SMAP *)); + +/* + * vs_change -- + * Make a change to the screen. + * + * PUBLIC: int vs_change __P((SCR *, recno_t, lnop_t)); + */ +int +vs_change(sp, lno, op) + SCR *sp; + recno_t lno; + lnop_t op; +{ + VI_PRIVATE *vip; + SMAP *p; + size_t cnt, oldy, oldx; + + vip = VIP(sp); + + /* + * 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 screen. + * The vi text input functions detect it explicitly and don't insert + * a new line. + * + * Check for line #2 before going to the end of the file. + */ + if ((op == LINE_APPEND && lno == 0 || op == LINE_INSERT && lno == 1) && + !db_exist(sp, 2)) { + lno = 1; + op = LINE_RESET; + } + + /* 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, cnt = sp->t_rows; cnt--; ++p) + --p->lno; + if (sp->lno >= lno) + --sp->lno; + F_SET(vip, VIP_N_RENUMBER); + break; + case LINE_INSERT: + for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) + ++p->lno; + if (sp->lno >= lno) + ++sp->lno; + F_SET(vip, VIP_N_RENUMBER); + break; + case LINE_RESET: + break; + } + return (0); + } + + F_SET(vip, VIP_N_REFRESH); + + /* + * Invalidate the line size cache, and invalidate the cursor if it's + * on this line, + */ + VI_SCR_CFLUSH(vip); + if (sp->lno == lno) + F_SET(vip, VIP_CUR_INVALID); + + /* + * If ex modifies the screen after ex output is already on the screen + * or if we've switched into ex canonical mode, don't touch it -- we'll + * get scrolling wrong, at best. + */ + if (!F_ISSET(sp, SC_TINPUT_INFO) && + (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) { + F_SET(vip, VIP_N_EX_REDRAW); + return (0); + } + + /* Save and restore the cursor for these routines. */ + (void)sp->gp->scr_cursor(sp, &oldy, &oldx); + + switch (op) { + case LINE_DELETE: + if (vs_sm_delete(sp, lno)) + return (1); + F_SET(vip, VIP_N_RENUMBER); + break; + case LINE_INSERT: + if (vs_sm_insert(sp, lno)) + return (1); + F_SET(vip, VIP_N_RENUMBER); + break; + case LINE_RESET: + if (vs_sm_reset(sp, lno)) + return (1); + break; + default: + abort(); + } + + (void)sp->gp->scr_move(sp, oldy, oldx); + return (0); +} + +/* + * vs_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. + * + * PUBLIC: int vs_sm_fill __P((SCR *, recno_t, pos_t)); + */ +int +vs_sm_fill(sp, lno, pos) + SCR *sp; + recno_t lno; + pos_t pos; +{ + SMAP *p, tmp; + size_t cnt; + + /* Flush all cached information from the SMAP. */ + for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) + SMAP_FLUSH(p); + + /* + * If the map is filled, the screen must be redrawn. + * + * XXX + * This is a bug. We should try and figure out if the desired line + * is already in the map or close by -- scrolling the screen would + * be a lot better than redrawing. + */ + F_SET(sp, SC_SCR_REDRAW); + + switch (pos) { + case P_FILL: + tmp.lno = 1; + tmp.coff = 0; + tmp.soff = 1; + + /* See if less than half a screen from the top. */ + if (vs_sm_nlines(sp, + &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { + lno = 1; + goto top; + } + + /* See if less than half a screen from the bottom. */ + if (db_last(sp, &tmp.lno)) + return (1); + tmp.coff = 0; + tmp.soff = vs_screens(sp, tmp.lno, NULL); + if (vs_sm_nlines(sp, + &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { + TMAP->lno = tmp.lno; + TMAP->coff = tmp.coff; + TMAP->soff = tmp.soff; + goto bottom; + } + goto middle; + case P_TOP: + if (lno != OOBLNO) { +top: HMAP->lno = lno; + HMAP->coff = 0; + HMAP->soff = 1; + } + /* If we fail, just punt. */ + for (p = HMAP, cnt = sp->t_rows; --cnt; ++p) + if (vs_sm_next(sp, p, p + 1)) + goto err; + break; + case P_MIDDLE: + /* If we fail, guess that the file is too small. */ +middle: p = HMAP + sp->t_rows / 2; + p->lno = lno; + p->coff = 0; + p->soff = 1; + for (; p > HMAP; --p) + if (vs_sm_prev(sp, p, p - 1)) { + lno = 1; + goto top; + } + + /* If we fail, just punt. */ + p = HMAP + sp->t_rows / 2; + for (; p < TMAP; ++p) + if (vs_sm_next(sp, p, p + 1)) + goto err; + break; + case P_BOTTOM: + if (lno != OOBLNO) { + TMAP->lno = lno; + TMAP->coff = 0; + TMAP->soff = vs_screens(sp, lno, NULL); + } + /* If we fail, guess that the file is too small. */ +bottom: for (p = TMAP; p > HMAP; --p) + if (vs_sm_prev(sp, 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->coff = 0; + HMAP->soff = 1; + for (p = HMAP; p < TMAP; ++p) + if (vs_sm_next(sp, p, p + 1)) + return (1); + return (0); +} + +/* + * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the + * screen contains only a single line (whether because the screen is small + * or the line large), it gets fairly exciting. Skip the fun, set a flag + * so the screen map is refilled and the screen redrawn, and return. This + * is amazingly slow, but it's not clear that anyone will care. + */ +#define HANDLE_WEIRDNESS(cnt) { \ + if (cnt >= sp->t_rows) { \ + F_SET(sp, SC_SCR_REFORMAT); \ + return (0); \ + } \ +} + +/* + * vs_sm_delete -- + * Delete a line out of the SMAP. + */ +static int +vs_sm_delete(sp, lno) + SCR *sp; + 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); + + HANDLE_WEIRDNESS(cnt_orig); + + /* Delete that many lines from the screen. */ + (void)sp->gp->scr_move(sp, p - HMAP, 0); + if (vs_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 && vs_sm_next(sp, p, p + 1)) + return (1); + /* vs_sm_next() flushed the cache. */ + if (vs_line(sp, ++p, NULL, NULL)) + return (1); + if (p == TMAP) + break; + } + return (0); +} + +/* + * vs_sm_insert -- + * Insert a line into the SMAP. + */ +static int +vs_sm_insert(sp, lno) + SCR *sp; + recno_t lno; +{ + SMAP *p, *t; + size_t cnt_orig, cnt, coff; + + /* Save the offset. */ + coff = HMAP->coff; + + /* + * Find the line in the map, find out how many screen lines + * needed to display the line. + */ + for (p = HMAP; p->lno != lno; ++p); + + cnt_orig = vs_screens(sp, lno, NULL); + HANDLE_WEIRDNESS(cnt_orig); + + /* + * 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. */ + (void)sp->gp->scr_move(sp, p - HMAP, 0); + if (vs_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->coff = coff; + t->soff = cnt; + SMAP_FLUSH(t); + if (vs_line(sp, t, NULL, NULL)) + return (1); + } + return (0); +} + +/* + * vs_sm_reset -- + * Reset a line in the SMAP. + */ +static int +vs_sm_reset(sp, lno) + SCR *sp; + 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 = vs_screens(sp, lno, NULL); + } + + HANDLE_WEIRDNESS(cnt_orig); + + if (cnt_orig == cnt_new) { + do { + SMAP_FLUSH(p); + if (vs_line(sp, 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; + + /* If there are any following lines, push them down. */ + if (cnt > 1) { + (void)sp->gp->scr_move(sp, p - HMAP, 0); + if (vs_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->soff = cnt; + SMAP_FLUSH(t); + if (vs_line(sp, t, NULL, NULL)) + return (1); + } + } else { + /* Get the difference. */ + diff = cnt_orig - cnt_new; + + /* Delete that many lines from the screen. */ + (void)sp->gp->scr_move(sp, p - HMAP, 0); + if (vs_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->soff = cnt; + SMAP_FLUSH(t); + if (vs_line(sp, t, NULL, NULL)) + return (1); + } + + /* Display the new lines at the bottom of the screen. */ + for (t = TMAP - diff;;) { + if (t < TMAP && vs_sm_next(sp, t, t + 1)) + return (1); + /* vs_sm_next() flushed the cache. */ + if (vs_line(sp, ++t, NULL, NULL)) + return (1); + if (t == TMAP) + break; + } + } + return (0); +} + +/* + * vs_sm_scroll + * Scroll the SMAP up/down count logical lines. Different + * semantics based on the vi command, *sigh*. + * + * PUBLIC: int vs_sm_scroll __P((SCR *, MARK *, recno_t, scroll_t)); + */ +int +vs_sm_scroll(sp, rp, count, scmd) + SCR *sp; + MARK *rp; + recno_t count; + scroll_t 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(VIP(sp), VIP_CUR_INVALID); + + /* Find the cursor in the screen. */ + if (vs_sm_cursor(sp, &smp)) + return (1); + + switch (scmd) { + case CNTRL_B: + case CNTRL_U: + case CNTRL_Y: + case Z_CARAT: + if (vs_sm_down(sp, rp, count, scmd, smp)) + return (1); + break; + case CNTRL_D: + case CNTRL_E: + case CNTRL_F: + case Z_PLUS: + if (vs_sm_up(sp, 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, rp->lno, &rp->cno)) + return (1); + + return (0); +} + +/* + * vs_sm_up -- + * Scroll the SMAP up count logical lines. + */ +static int +vs_sm_up(sp, rp, count, scmd, smp) + SCR *sp; + MARK *rp; + scroll_t scmd; + recno_t count; + SMAP *smp; +{ + int cursor_set, echanged, zset; + SMAP *ssmp, 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 (vs_sm_next(sp, TMAP, &s1)) + return (1); + if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) { + if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) { + v_eof(sp, NULL); + return (1); + } + if (vs_sm_next(sp, smp, &s1)) + return (1); + if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) { + v_eof(sp, NULL); + return (1); + } + } + + /* + * Small screens: see vs_refresh.c section 6a. + * + * 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 down one + * line for each 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 (IS_SMALL(sp)) { + if (count >= sp->t_maxrows || scmd == CNTRL_F) { + s1 = TMAP[0]; + if (vs_sm_erase(sp)) + return (1); + for (; count--; s1 = s2) { + if (vs_sm_next(sp, &s1, &s2)) + return (1); + if (s2.lno != s1.lno && !db_exist(sp, s2.lno)) + break; + } + TMAP[0] = s2; + if (vs_sm_fill(sp, OOBLNO, P_BOTTOM)) + return (1); + return (vs_sm_position(sp, rp, 0, P_TOP)); + } + cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp); + for (; count && + sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { + if (vs_sm_next(sp, TMAP, &s1)) + return (1); + if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno)) + break; + *++TMAP = s1; + /* vs_sm_next() flushed the cache. */ + if (vs_line(sp, TMAP, NULL, NULL)) + return (1); + + if (!cursor_set) + ++ssmp; + } + if (!cursor_set) { + rp->lno = ssmp->lno; + rp->cno = ssmp->c_sboff; + } + if (count == 0) + return (0); + } + + for (echanged = zset = 0; count; --count) { + /* Decide what would show up on the screen. */ + if (vs_sm_next(sp, TMAP, &s1)) + return (1); + + /* If the line doesn't exist, we're done. */ + if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno)) + break; + + /* Scroll the screen cursor up one logical line. */ + if (vs_sm_1up(sp)) + 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 = vs_colpos(sp, smp->lno, + (O_ISSET(sp, O_LEFTRIGHT) ? + smp->coff : (smp->soff - 1) * sp->cols) + + sp->rcm % sp->cols); + } + return (0); + case CNTRL_F: + /* + * If there are more lines, the ^F command is 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 || !db_exist(sp, smp[1].lno)) + break; + break; + case Z_PLUS: + /* The z+ command moves the cursor to the first new line. */ + break; + default: + abort(); + } + + if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) + return (1); + rp->lno = smp->lno; + rp->cno = smp->c_sboff; + return (0); +} + +/* + * vs_sm_1up -- + * Scroll the SMAP up one. + * + * PUBLIC: int vs_sm_1up __P((SCR *)); + */ +int +vs_sm_1up(sp) + SCR *sp; +{ + /* + * Delete the top line of the screen. Shift the screen map + * up and display a new line at the bottom of the screen. + */ + (void)sp->gp->scr_move(sp, 0, 0); + if (vs_deleteln(sp, 1)) + return (1); + + /* One-line screens can fail. */ + if (IS_ONELINE(sp)) { + if (vs_sm_next(sp, TMAP, TMAP)) + return (1); + } else { + memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP)); + if (vs_sm_next(sp, TMAP - 1, TMAP)) + return (1); + } + /* vs_sm_next() flushed the cache. */ + return (vs_line(sp, TMAP, NULL, NULL)); +} + +/* + * vs_deleteln -- + * Delete a line a la curses, make sure to put the information + * line and other screens back. + */ +static int +vs_deleteln(sp, cnt) + SCR *sp; + int cnt; +{ + GS *gp; + size_t oldy, oldx; + + gp = sp->gp; + if (IS_ONELINE(sp)) + (void)gp->scr_clrtoeol(sp); + else { + (void)gp->scr_cursor(sp, &oldy, &oldx); + while (cnt--) { + (void)gp->scr_deleteln(sp); + (void)gp->scr_move(sp, LASTLINE(sp), 0); + (void)gp->scr_insertln(sp); + (void)gp->scr_move(sp, oldy, oldx); + } + } + return (0); +} + +/* + * vs_sm_down -- + * Scroll the SMAP down count logical lines. + */ +static int +vs_sm_down(sp, rp, count, scmd, smp) + SCR *sp; + MARK *rp; + recno_t count; + SMAP *smp; + scroll_t scmd; +{ + SMAP *ssmp, s1, s2; + int cursor_set, ychanged, zset; + + /* Check to see if movement is possible. */ + if (HMAP->lno == 1 && + (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) && + (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) { + v_sof(sp, NULL); + return (1); + } + + /* + * Small screens: see vs_refresh.c section 6a. + * + * 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 up one + * line for each 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 (IS_SMALL(sp)) { + if (count >= sp->t_maxrows || scmd == CNTRL_B) { + s1 = HMAP[0]; + if (vs_sm_erase(sp)) + return (1); + for (; count--; s1 = s2) { + if (vs_sm_prev(sp, &s1, &s2)) + return (1); + if (s2.lno == 1 && + (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1)) + break; + } + HMAP[0] = s2; + if (vs_sm_fill(sp, OOBLNO, P_TOP)) + return (1); + return (vs_sm_position(sp, rp, 0, P_BOTTOM)); + } + cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp); + for (; count && + sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { + if (HMAP->lno == 1 && + (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1)) + break; + ++TMAP; + if (vs_sm_1down(sp)) + return (1); + } + if (!cursor_set) { + rp->lno = ssmp->lno; + rp->cno = ssmp->c_sboff; + } + if (count == 0) + return (0); + } + + for (ychanged = zset = 0; count; --count) { + /* If the line doesn't exist, we're done. */ + if (HMAP->lno == 1 && + (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1)) + break; + + /* Scroll the screen and cursor down one logical line. */ + if (vs_sm_1down(sp)) + 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 positioned at + * the last line of the screen. However, the line may not + * exist. + */ + if (!count) { + for (smp = TMAP; smp > HMAP; --smp) + if (db_exist(sp, smp->lno)) + break; + 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 = vs_colpos(sp, smp->lno, + (O_ISSET(sp, O_LEFTRIGHT) ? + smp->coff : (smp->soff - 1) * sp->cols) + + sp->rcm % sp->cols); + } + return (0); + case Z_CARAT: + /* The z^ command moves the cursor to the first new line. */ + break; + default: + abort(); + } + + if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) + return (1); + rp->lno = smp->lno; + rp->cno = smp->c_sboff; + return (0); +} + +/* + * vs_sm_erase -- + * Erase the small screen area for the scrolling functions. + */ +static int +vs_sm_erase(sp) + SCR *sp; +{ + GS *gp; + + gp = sp->gp; + (void)gp->scr_move(sp, LASTLINE(sp), 0); + (void)gp->scr_clrtoeol(sp); + for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) { + (void)gp->scr_move(sp, TMAP - HMAP, 0); + (void)gp->scr_clrtoeol(sp); + } + return (0); +} + +/* + * vs_sm_1down -- + * Scroll the SMAP down one. + * + * PUBLIC: int vs_sm_1down __P((SCR *)); + */ +int +vs_sm_1down(sp) + SCR *sp; +{ + /* + * Insert a line at the top of the screen. Shift the screen map + * down and display a new line at the top of the screen. + */ + (void)sp->gp->scr_move(sp, 0, 0); + if (vs_insertln(sp, 1)) + return (1); + + /* One-line screens can fail. */ + if (IS_ONELINE(sp)) { + if (vs_sm_prev(sp, HMAP, HMAP)) + return (1); + } else { + memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP)); + if (vs_sm_prev(sp, HMAP + 1, HMAP)) + return (1); + } + /* vs_sm_prev() flushed the cache. */ + return (vs_line(sp, HMAP, NULL, NULL)); +} + +/* + * vs_insertln -- + * Insert a line a la curses, make sure to put the information + * line and other screens back. + */ +static int +vs_insertln(sp, cnt) + SCR *sp; + int cnt; +{ + GS *gp; + size_t oldy, oldx; + + gp = sp->gp; + if (IS_ONELINE(sp)) { + (void)gp->scr_move(sp, LASTLINE(sp), 0); + (void)gp->scr_clrtoeol(sp); + } else { + (void)gp->scr_cursor(sp, &oldy, &oldx); + while (cnt--) { + (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0); + (void)gp->scr_deleteln(sp); + (void)gp->scr_move(sp, oldy, oldx); + (void)gp->scr_insertln(sp); + } + } + return (0); +} + +/* + * vs_sm_next -- + * Fill in the next entry in the SMAP. + * + * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *)); + */ +int +vs_sm_next(sp, p, t) + SCR *sp; + SMAP *p, *t; +{ + size_t lcnt; + + SMAP_FLUSH(t); + if (O_ISSET(sp, O_LEFTRIGHT)) { + t->lno = p->lno + 1; + t->coff = p->coff; + } else { + lcnt = vs_screens(sp, p->lno, NULL); + if (lcnt == p->soff) { + t->lno = p->lno + 1; + t->soff = 1; + } else { + t->lno = p->lno; + t->soff = p->soff + 1; + } + } + return (0); +} + +/* + * vs_sm_prev -- + * Fill in the previous entry in the SMAP. + * + * PUBLIC: int vs_sm_prev __P((SCR *, SMAP *, SMAP *)); + */ +int +vs_sm_prev(sp, p, t) + SCR *sp; + SMAP *p, *t; +{ + SMAP_FLUSH(t); + if (O_ISSET(sp, O_LEFTRIGHT)) { + t->lno = p->lno - 1; + t->coff = p->coff; + } else { + if (p->soff != 1) { + t->lno = p->lno; + t->soff = p->soff - 1; + } else { + t->lno = p->lno - 1; + t->soff = vs_screens(sp, t->lno, NULL); + } + } + return (t->lno == 0); +} + +/* + * vs_sm_cursor -- + * Return the SMAP entry referenced by the cursor. + * + * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **)); + */ +int +vs_sm_cursor(sp, smpp) + SCR *sp; + 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) && vs_line(sp, 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); +} + +/* + * vs_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. + * + * PUBLIC: int vs_sm_position __P((SCR *, MARK *, u_long, pos_t)); + */ +int +vs_sm_position(sp, rp, cnt, pos) + SCR *sp; + MARK *rp; + u_long cnt; + pos_t 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 && !db_exist(sp, smp->lno)) { +sof: msgq(sp, M_BERR, "220|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 (!db_exist(sp, TMAP->lno)) { + if (db_last(sp, &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 (!db_exist(sp, smp->lno)) { + if (db_last(sp, &last)) + return (1); + for (; smp->lno > last && smp > HMAP; --smp); + if (cnt > smp - HMAP) { +eof: msgq(sp, M_BERR, + "221|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) && vs_line(sp, smp, NULL, NULL)) + return (1); + rp->lno = smp->lno; + rp->cno = smp->c_sboff; + + return (0); +} + +/* + * vs_sm_nlines -- + * Return the number of screen lines from an SMAP entry to the + * start of some file line, less than a maximum value. + * + * PUBLIC: recno_t vs_sm_nlines __P((SCR *, SMAP *, recno_t, size_t)); + */ +recno_t +vs_sm_nlines(sp, from_sp, to_lno, max) + SCR *sp; + 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->soff - 1); + + if (from_sp->lno > to_lno) { + lcnt = from_sp->soff - 1; /* Correct for off-by-one. */ + for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;) + lcnt += vs_screens(sp, lno, NULL); + } else { + lno = from_sp->lno; + lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1; + for (; ++lno < to_lno && lcnt <= max;) + lcnt += vs_screens(sp, lno, NULL); + } + return (lcnt); +} diff --git a/contrib/nvi/vi/vs_split.c b/contrib/nvi/vi/vs_split.c new file mode 100644 index 0000000..d017354 --- /dev/null +++ b/contrib/nvi/vi/vs_split.c @@ -0,0 +1,607 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)vs_split.c 10.31 (Berkeley) 10/13/96"; +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "vi.h" + +static SCR *vs_getbg __P((SCR *, char *)); + +/* + * vs_split -- + * Create a new screen. + * + * PUBLIC: int vs_split __P((SCR *, SCR *, int)); + */ +int +vs_split(sp, new, ccl) + SCR *sp, *new; + int ccl; /* Colon-command line split. */ +{ + GS *gp; + SMAP *smp; + size_t half; + int issmallscreen, splitup; + + gp = sp->gp; + + /* Check to see if it's possible. */ + /* XXX: The IS_ONELINE fix will change this, too. */ + if (sp->rows < 4) { + msgq(sp, M_ERR, + "222|Screen must be larger than %d lines to split", 4 - 1); + return (1); + } + + /* Wait for any messages in the screen. */ + vs_resolve(sp, NULL, 1); + + half = sp->rows / 2; + if (ccl && half > 6) + half = 6; + + /* Get a new screen map. */ + CALLOC(sp, _HMAP(new), SMAP *, SIZE_HMAP(sp), sizeof(SMAP)); + if (_HMAP(new) == NULL) + return (1); + _HMAP(new)->lno = sp->lno; + _HMAP(new)->coff = 0; + _HMAP(new)->soff = 1; + + /* + * Small screens: see vs_refresh.c section 6a. Set a flag so + * we know to fix the screen up later. + */ + issmallscreen = IS_SMALL(sp); + + /* The columns in the screen don't change. */ + new->cols = sp->cols; + + /* + * Split the screen, and link the screens together. If creating a + * screen to edit the colon command line or 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. + * + * Recalculate current cursor position based on sp->lno, we're called + * with the cursor on the colon command line. Then split the screen + * in half and update the shared information. + */ + splitup = + !ccl && (vs_sm_cursor(sp, &smp) ? 0 : (smp - HMAP) + 1) >= half; + if (splitup) { /* Old is bottom half. */ + new->rows = sp->rows - half; /* New. */ + new->woff = sp->woff; + sp->rows = half; /* Old. */ + sp->woff += new->rows; + /* Link in before old. */ + CIRCLEQ_INSERT_BEFORE(&gp->dq, sp, new, q); + + /* + * If the parent is the bottom half of the screen, shift + * the map down to match on-screen text. + */ + memmove(_HMAP(sp), _HMAP(sp) + new->rows, + (sp->t_maxrows - new->rows) * sizeof(SMAP)); + } else { /* Old is top half. */ + new->rows = half; /* New. */ + sp->rows -= half; /* Old. */ + new->woff = sp->woff + sp->rows; + /* Link in after old. */ + CIRCLEQ_INSERT_AFTER(&gp->dq, sp, new, q); + } + + /* Adjust maximum text count. */ + sp->t_maxrows = IS_ONELINE(sp) ? 1 : sp->rows - 1; + new->t_maxrows = IS_ONELINE(new) ? 1 : new->rows - 1; + + /* + * Small screens: see vs_refresh.c, section 6a. + * + * The child may have different screen options sizes than the parent, + * so use them. Guarantee that 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 -= new->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. */ + new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW); + if (new->t_rows > new->t_maxrows) + new->t_rows = new->t_maxrows; + if (new->t_minrows > new->t_maxrows) + new->t_minrows = new->t_maxrows; + } else { + sp->t_minrows = sp->t_rows = IS_ONELINE(sp) ? 1 : sp->rows - 1; + + /* + * The new screen may be a small screen, even if 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. + */ + new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW); + if (new->t_rows > new->rows - 1) + new->t_minrows = new->t_rows = + IS_ONELINE(new) ? 1 : new->rows - 1; + } + + /* Adjust the ends of the new and old maps. */ + _TMAP(sp) = IS_ONELINE(sp) ? + _HMAP(sp) : _HMAP(sp) + (sp->t_rows - 1); + _TMAP(new) = IS_ONELINE(new) ? + _HMAP(new) : _HMAP(new) + (new->t_rows - 1); + + /* Reset the length of the default scroll. */ + if ((sp->defscroll = sp->t_maxrows / 2) == 0) + sp->defscroll = 1; + if ((new->defscroll = new->t_maxrows / 2) == 0) + new->defscroll = 1; + + /* + * Initialize the screen flags: + * + * If we're in vi mode in one screen, we don't have to reinitialize. + * This isn't just a cosmetic fix. The path goes like this: + * + * return into vi(), SC_SSWITCH set + * call vs_refresh() with SC_STATUS set + * call vs_resolve to display the status message + * call vs_refresh() because the SC_SCR_VI bit isn't set + * + * Things go downhill at this point. + * + * Draw the new screen from scratch, and add a status line. + */ + F_SET(new, + SC_SCR_REFORMAT | SC_STATUS | + F_ISSET(sp, SC_EX | SC_VI | SC_SCR_VI | SC_SCR_EX)); + return (0); +} + +/* + * vs_discard -- + * Discard the screen, folding the real-estate into a related screen, + * if one exists, and return that screen. + * + * PUBLIC: int vs_discard __P((SCR *, SCR **)); + */ +int +vs_discard(sp, spp) + SCR *sp, **spp; +{ + SCR *nsp; + dir_t dir; + + /* + * Save the old screen's cursor information. + * + * XXX + * If called after file_end(), and the underlying file was a tmp + * file, it may have gone away. + */ + if (sp->frp != NULL) { + sp->frp->lno = sp->lno; + sp->frp->cno = sp->cno; + F_SET(sp->frp, FR_CURSORSET); + } + + /* + * Add into a previous screen and then into a subsequent screen, as + * they're the closest to the current screen. If that doesn't work, + * there was no screen to join. + */ + if ((nsp = sp->q.cqe_prev) != (void *)&sp->gp->dq) { + nsp->rows += sp->rows; + sp = nsp; + dir = FORWARD; + } else if ((nsp = sp->q.cqe_next) != (void *)&sp->gp->dq) { + nsp->woff = sp->woff; + nsp->rows += sp->rows; + sp = nsp; + dir = BACKWARD; + } else + sp = NULL; + + if (spp != NULL) + *spp = sp; + if (sp == NULL) + return (0); + + /* + * Make no effort to clean up the discarded screen's information. If + * it's not exiting, we'll do the work when the user redisplays it. + * + * Small screens: see vs_refresh.c section 6a. Adjust text line info, + * unless it's a small screen. + * + * Reset the length of the default scroll. + */ + if (!IS_SMALL(sp)) + sp->t_rows = sp->t_minrows = sp->rows - 1; + sp->t_maxrows = sp->rows - 1; + sp->defscroll = sp->t_maxrows / 2; + *(HMAP + (sp->t_rows - 1)) = *TMAP; + TMAP = HMAP + (sp->t_rows - 1); + + /* + * Draw the new screen from scratch, and add a status line. + * + * XXX + * We could play games with the map, if this were ever to be a + * performance problem, but I wrote the code a few times and it + * was never clean or easy. + */ + switch (dir) { + case FORWARD: + vs_sm_fill(sp, OOBLNO, P_TOP); + break; + case BACKWARD: + vs_sm_fill(sp, OOBLNO, P_BOTTOM); + break; + default: + abort(); + } + + F_SET(sp, SC_STATUS); + return (0); +} + +/* + * vs_fg -- + * Background the current screen, and foreground a new one. + * + * PUBLIC: int vs_fg __P((SCR *, SCR **, CHAR_T *, int)); + */ +int +vs_fg(sp, nspp, name, newscreen) + SCR *sp, **nspp; + CHAR_T *name; + int newscreen; +{ + GS *gp; + SCR *nsp; + + gp = sp->gp; + + if (newscreen) + /* Get the specified background screen. */ + nsp = vs_getbg(sp, name); + else + /* Swap screens. */ + if (vs_swap(sp, &nsp, name)) + return (1); + + if ((*nspp = nsp) == NULL) { + msgq_str(sp, M_ERR, name, + name == NULL ? + "223|There are no background screens" : + "224|There's no background screen editing a file named %s"); + return (1); + } + + if (newscreen) { + /* Remove the new screen from the background queue. */ + CIRCLEQ_REMOVE(&gp->hq, nsp, q); + + /* Split the screen; if we fail, hook the screen back in. */ + if (vs_split(sp, nsp, 0)) { + CIRCLEQ_INSERT_TAIL(&gp->hq, nsp, q); + return (1); + } + } else { + /* Move the old screen to the background queue. */ + CIRCLEQ_REMOVE(&gp->dq, sp, q); + CIRCLEQ_INSERT_TAIL(&gp->hq, sp, q); + } + return (0); +} + +/* + * vs_bg -- + * Background the screen, and switch to the next one. + * + * PUBLIC: int vs_bg __P((SCR *)); + */ +int +vs_bg(sp) + SCR *sp; +{ + GS *gp; + SCR *nsp; + + gp = sp->gp; + + /* Try and join with another screen. */ + if (vs_discard(sp, &nsp)) + return (1); + if (nsp == NULL) { + msgq(sp, M_ERR, + "225|You may not background your only displayed screen"); + return (1); + } + + /* Move the old screen to the background queue. */ + CIRCLEQ_REMOVE(&gp->dq, sp, q); + CIRCLEQ_INSERT_TAIL(&gp->hq, sp, q); + + /* Toss the screen map. */ + free(_HMAP(sp)); + _HMAP(sp) = NULL; + + /* Switch screens. */ + sp->nextdisp = nsp; + F_SET(sp, SC_SSWITCH); + + return (0); +} + +/* + * vs_swap -- + * Swap the current screen with a backgrounded one. + * + * PUBLIC: int vs_swap __P((SCR *, SCR **, char *)); + */ +int +vs_swap(sp, nspp, name) + SCR *sp, **nspp; + char *name; +{ + GS *gp; + SCR *nsp; + + gp = sp->gp; + + /* Get the specified background screen. */ + if ((*nspp = nsp = vs_getbg(sp, name)) == NULL) + return (0); + + /* + * Save the old screen's cursor information. + * + * XXX + * If called after file_end(), and the underlying file was a tmp + * file, it may have gone away. + */ + if (sp->frp != NULL) { + sp->frp->lno = sp->lno; + sp->frp->cno = sp->cno; + F_SET(sp->frp, FR_CURSORSET); + } + + /* Switch screens. */ + sp->nextdisp = nsp; + F_SET(sp, SC_SSWITCH); + + /* Initialize terminal information. */ + VIP(nsp)->srows = VIP(sp)->srows; + + /* Initialize screen information. */ + nsp->cols = sp->cols; + nsp->rows = sp->rows; /* XXX: Only place in vi that sets rows. */ + nsp->woff = sp->woff; + + /* + * Small screens: see vs_refresh.c, section 6a. + * + * 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 (IS_SMALL(nsp)) { + nsp->t_minrows = nsp->t_rows = O_VAL(nsp, O_WINDOW); + if (nsp->t_rows > sp->t_maxrows) + nsp->t_rows = nsp->t_maxrows; + if (nsp->t_minrows > sp->t_maxrows) + nsp->t_minrows = nsp->t_maxrows; + } else + nsp->t_rows = nsp->t_maxrows = nsp->t_minrows = nsp->rows - 1; + + /* Reset the length of the default scroll. */ + nsp->defscroll = nsp->t_maxrows / 2; + + /* Allocate a new screen map. */ + CALLOC_RET(nsp, _HMAP(nsp), SMAP *, SIZE_HMAP(nsp), sizeof(SMAP)); + _TMAP(nsp) = _HMAP(nsp) + (nsp->t_rows - 1); + + /* Fill the map. */ + if (vs_sm_fill(nsp, nsp->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 background queue. + */ + CIRCLEQ_REMOVE(&gp->hq, nsp, q); + CIRCLEQ_INSERT_AFTER(&gp->dq, sp, nsp, q); + + /* + * Don't change the screen's cursor information other than to + * note that the cursor is wrong. + */ + F_SET(VIP(nsp), VIP_CUR_INVALID); + + /* Draw the new screen from scratch, and add a status line. */ + F_SET(nsp, SC_SCR_REDRAW | SC_STATUS); + return (0); +} + +/* + * vs_resize -- + * Change the absolute size of the current screen. + * + * PUBLIC: int vs_resize __P((SCR *, long, adj_t)); + */ +int +vs_resize(sp, count, adj) + SCR *sp; + long count; + adj_t adj; +{ + GS *gp; + SCR *g, *s; + size_t g_off, s_off; + + gp = sp->gp; + + /* + * 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; + } + } + + g_off = s_off = 0; + 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 *)&gp->dq) { + if ((g = sp->q.cqe_next) == (void *)&gp->dq) + goto toobig; + g_off = -count; + } else + s_off = count; + } else { + g = sp; + if ((s = sp->q.cqe_next) != (void *)&gp->dq) + if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) + s = NULL; + else + s_off = count; + else + s = NULL; + if (s == NULL) { + if ((s = sp->q.cqe_prev) == (void *)&gp->dq) { +toobig: msgq(sp, M_BERR, adj == A_DECREASE ? + "227|The screen cannot shrink" : + "228|The screen cannot grow"); + return (1); + } + if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) { +toosmall: msgq(sp, M_BERR, + "226|The screen can only shrink to %d rows", + MINIMUM_SCREEN_ROWS); + return (1); + } + g_off = -count; + } + } + + /* + * Fix up 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. + */ + s->rows += -count; + s->woff += s_off; + g->rows += count; + g->woff += g_off; + + g->t_rows += count; + if (g->t_minrows == g->t_maxrows) + g->t_minrows += count; + g->t_maxrows += count; + _TMAP(g) += count; + F_SET(g, SC_SCR_REFORMAT | SC_STATUS); + + s->t_rows -= count; + s->t_maxrows -= count; + if (s->t_minrows > s->t_maxrows) + s->t_minrows = s->t_maxrows; + _TMAP(s) -= count; + F_SET(s, SC_SCR_REFORMAT | SC_STATUS); + + return (0); +} + +/* + * vs_getbg -- + * Get the specified background screen, or, if name is NULL, the first + * background screen. + */ +static SCR * +vs_getbg(sp, name) + SCR *sp; + char *name; +{ + GS *gp; + SCR *nsp; + char *p; + + gp = sp->gp; + + /* If name is NULL, return the first background screen on the list. */ + if (name == NULL) { + nsp = gp->hq.cqh_first; + return (nsp == (void *)&gp->hq ? NULL : nsp); + } + + /* Search for a full match. */ + for (nsp = gp->hq.cqh_first; + nsp != (void *)&gp->hq; nsp = nsp->q.cqe_next) + if (!strcmp(nsp->frp->name, name)) + break; + if (nsp != (void *)&gp->hq) + return (nsp); + + /* Search for a last-component match. */ + for (nsp = gp->hq.cqh_first; + nsp != (void *)&gp->hq; nsp = nsp->q.cqe_next) { + if ((p = strrchr(nsp->frp->name, '/')) == NULL) + p = nsp->frp->name; + else + ++p; + if (!strcmp(p, name)) + break; + } + if (nsp != (void *)&gp->hq) + return (nsp); + + return (NULL); +} |