Index: vendor/nvi/dist/CMakeLists.txt =================================================================== --- vendor/nvi/dist/CMakeLists.txt (revision 366306) +++ vendor/nvi/dist/CMakeLists.txt (revision 366307) @@ -1,190 +1,210 @@ cmake_minimum_required(VERSION 3.9) get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(is_multi_config) set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "Semicolon separated list of supported configuration types") mark_as_advanced(CMAKE_CONFIGURATION_TYPES) elseif(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_C_FLAGS) message(WARNING "No CMAKE_BUILD_TYPE is selected") endif() project(nvi2 C) include(CheckIncludeFiles) include(CheckFunctionExists) +include(CheckStructHasMember) include(CheckCSourceCompiles) mark_as_advanced(CMAKE_INSTALL_PREFIX) option(USE_WIDECHAR "Enable wide character support" ON) option(USE_ICONV "Enable iconv support" ON) add_compile_options(-fcolor-diagnostics) add_compile_options($<$:-Wall>) add_compile_options($<$:-Wno-parentheses>) add_compile_options($<$:-Wno-uninitialized>) add_compile_options($<$:-Wmissing-prototypes>) add_compile_options($<$:-Wsystem-headers>) add_compile_options($<$:-Wuninitialized>) add_compile_options($<$:-Wno-dangling-else>) add_compile_options(-Wstack-protector -fstack-protector) add_compile_options(-Wstrict-aliasing -fstrict-aliasing) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(MAIN_PROTOS cl/extern.h common/extern.h ex/extern.h vi/extern.h common/options_def.h ex/ex_def.h ex/version.h) set(CL_SRCS cl/cl_funcs.c cl/cl_main.c cl/cl_read.c cl/cl_screen.c cl/cl_term.c) set(COMMON_SRCS common/conv.c common/cut.c common/delete.c common/encoding.c common/exf.c common/key.c common/line.c common/log.c common/main.c common/mark.c common/msg.c common/options.c common/options_f.c common/put.c common/recover.c common/screen.c common/search.c common/seq.c common/util.c) set(EX_SRCS ex/ex.c ex/ex_abbrev.c ex/ex_append.c ex/ex_args.c ex/ex_argv.c ex/ex_at.c ex/ex_bang.c ex/ex_cd.c ex/ex_cmd.c ex/ex_cscope.c ex/ex_delete.c ex/ex_display.c ex/ex_edit.c ex/ex_equal.c ex/ex_file.c ex/ex_filter.c ex/ex_global.c ex/ex_init.c ex/ex_join.c ex/ex_map.c ex/ex_mark.c ex/ex_mkexrc.c ex/ex_move.c ex/ex_open.c ex/ex_preserve.c ex/ex_print.c ex/ex_put.c ex/ex_quit.c ex/ex_read.c ex/ex_screen.c ex/ex_script.c ex/ex_set.c ex/ex_shell.c ex/ex_shift.c ex/ex_source.c ex/ex_stop.c ex/ex_subst.c ex/ex_tag.c ex/ex_txt.c ex/ex_undo.c ex/ex_usage.c ex/ex_util.c ex/ex_version.c ex/ex_visual.c ex/ex_write.c ex/ex_yank.c ex/ex_z.c) set(VI_SRCS vi/getc.c vi/v_at.c vi/v_ch.c vi/v_cmd.c vi/v_delete.c vi/v_ex.c vi/v_increment.c vi/v_init.c vi/v_itxt.c vi/v_left.c vi/v_mark.c vi/v_match.c vi/v_paragraph.c vi/v_put.c vi/v_redraw.c vi/v_replace.c vi/v_right.c vi/v_screen.c vi/v_scroll.c vi/v_search.c vi/v_section.c vi/v_sentence.c vi/v_status.c vi/v_txt.c vi/v_ulcase.c vi/v_undo.c vi/v_util.c vi/v_word.c vi/v_xchar.c vi/v_yank.c vi/v_z.c vi/v_zexit.c vi/vi.c vi/vs_line.c vi/vs_msg.c vi/vs_refresh.c vi/vs_relative.c vi/vs_smap.c vi/vs_split.c) set(REGEX_SRCS regex/regcomp.c regex/regerror.c regex/regexec.c regex/regfree.c) # commands to generate the public headers set(extract_protos sed -n 's/^ \\* PUBLIC: \\\(.*\\\)/\\1/p') set(extract_version sed -n 's/^.*version \\\([^\)]*\)\\\).*/\#define VI_VERSION \\\"\\1\\\"/p') add_custom_command(OUTPUT cl/extern.h COMMAND ${extract_protos} ${CL_SRCS} > cl/extern.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${CL_SRCS}) add_custom_command(OUTPUT common/extern.h COMMAND ${extract_protos} ${COMMON_SRCS} > common/extern.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${COMMON_SRCS}) add_custom_command(OUTPUT ex/extern.h COMMAND ${extract_protos} ${EX_SRCS} > ex/extern.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${EX_SRCS}) add_custom_command(OUTPUT vi/extern.h COMMAND ${extract_protos} ${VI_SRCS} > vi/extern.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${VI_SRCS}) add_custom_command(OUTPUT common/options_def.h COMMAND awk -f common/options.awk common/options.c > common/options_def.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS common/options.c) add_custom_command(OUTPUT ex/ex_def.h COMMAND awk -f ex/ex.awk ex/ex_cmd.c > ex/ex_def.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ex/ex_cmd.c) add_custom_command(OUTPUT ex/version.h COMMAND ${extract_version} README > ex/version.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS README) add_executable(nvi) target_sources(nvi PRIVATE ${MAIN_PROTOS} ${CL_SRCS} ${COMMON_SRCS} ${EX_SRCS} ${VI_SRCS}) target_compile_definitions(nvi PRIVATE $<$:DEBUG> $<$:COMLOG>) check_function_exists(openpty UTIL_IN_LIBC) if(NOT UTIL_IN_LIBC) find_library(UTIL_LIBRARY util) target_link_libraries(nvi PRIVATE ${UTIL_LIBRARY}) endif() check_function_exists(__b64_ntop RESOLV_IN_LIBC) if(NOT RESOLV_IN_LIBC) find_library(RESOLV_LIBRARY resolv) target_link_libraries(nvi PRIVATE ${RESOLV_LIBRARY}) endif() if(USE_WIDECHAR) find_library(CURSES_LIBRARY NAMES ncursesw cursesw curses HINTS /usr/lib) + find_library(TERMINFO_LIBRARY NAMES tinfow terminfo HINTS /usr/lib) # link to the wchar_t awared BSD libregex.a add_library(regex STATIC) target_sources(regex PRIVATE ${REGEX_SRCS}) target_include_directories(regex PUBLIC regex) target_compile_definitions(regex PUBLIC __REGEX_PRIVATE) target_link_libraries(nvi PRIVATE regex) else() find_library(CURSES_LIBRARY NAMES ncurses curses HINTS /usr/lib) + find_library(TERMINFO_LIBRARY NAMES tinfo terminfo HINTS /usr/lib) target_compile_options(nvi PRIVATE -Wno-pointer-sign) endif() -target_link_libraries(nvi PRIVATE ${CURSES_LIBRARY}) +target_link_libraries(nvi PRIVATE ${CURSES_LIBRARY} ${TERMINFO_LIBRARY}) if(USE_ICONV) - check_function_exists(__iconv ICONV_IN_LIBC) + check_function_exists(iconv ICONV_IN_LIBC) if(NOT ICONV_IN_LIBC) find_path(ICONV_INCLUDE_DIR iconv.h) find_library(ICONV_LIBRARY iconv) endif() # detect the prototype of iconv(3) set(CMAKE_C_FLAGS_BACKUP "${CMAKE_C_FLAGS}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") set(CMAKE_REQUIRED_INCLUDES "${ICONV_INCLUDE_DIR}") set(CMAKE_REQUIRED_LIBRARIES "${ICONV_LIBRARY}") check_c_source_compiles(" #include int main() { iconv_t conv = 0; char* in = 0; size_t ilen = 0; char* out = 0; size_t olen = 0; iconv(conv, &in, &ilen, &out, &olen); return 0; } " ICONV_TRADITIONAL) set(CMAKE_REQUIRED_INCLUDES) set(CMAKE_REQUIRED_LIBRARIES) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS_BACKUP}") target_include_directories(nvi PRIVATE ${ICONV_INCLUDE_DIR}) target_link_libraries(nvi PRIVATE ${ICONV_LIBRARY}) endif() +check_function_exists(getprogname GETPROGNAME_IN_LIBC) +check_function_exists(strlcpy STRLCPY_IN_LIBC) +if(NOT GETPROGNAME_IN_LIBC OR NOT STRLCPY_IN_LIBC) + find_package(PkgConfig REQUIRED) + pkg_check_modules(LIBBSD libbsd-overlay) + add_definitions(${LIBBSD_CFLAGS}) + target_link_libraries(nvi PRIVATE ${LIBBSD_LIBRARIES}) +endif() + +check_function_exists(dbopen DBOPEN_IN_LIBC) +if(NOT DBOPEN_IN_LIBC) + target_link_libraries(nvi PRIVATE db1) +endif() + check_include_files(libutil.h HAVE_LIBUTIL_H) check_include_files(ncurses.h HAVE_NCURSES_H) +check_include_files(ncursesw/ncurses.h HAVE_NCURSESW_NCURSES_H) +check_include_files(pty.h HAVE_PTY_H) check_include_files(term.h HAVE_TERM_H) +check_struct_has_member("struct dirent" d_namlen dirent.h HAVE_DIRENT_D_NAMLEN LANGUAGE C) configure_file(files/config.h.in config.h) set(vi_cv_path_preserve /var/tmp/vi.recover/) if(APPLE) set(vi_cv_path_msgcat /usr/local/share/vi/catalog/) else() set(vi_cv_path_msgcat /usr/share/vi/catalog/) endif() configure_file(files/pathnames.h.in pathnames.h) configure_file(files/recover.in recover @ONLY) Index: vendor/nvi/dist/catalog/dump.c =================================================================== --- vendor/nvi/dist/catalog/dump.c (revision 366306) +++ vendor/nvi/dist/catalog/dump.c (revision 366307) @@ -1,97 +1,97 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include static void parse(FILE *fp) { int ch, s1, s2, s3; -#define TESTD(s) { \ +#define TESTD(s) do { \ if ((s = getc(fp)) == EOF) \ return; \ if (!isdigit(s)) \ continue; \ -} -#define TESTP { \ +} while (0) +#define TESTP do { \ if ((ch = getc(fp)) == EOF) \ return; \ if (ch != '|') \ continue; \ -} -#define MOVEC(t) { \ +} while (0) +#define MOVEC(t) do { \ do { \ if ((ch = getc(fp)) == EOF) \ return; \ } while (ch != (t)); \ -} +} while (0) for (;;) { MOVEC('"'); TESTD(s1); TESTD(s2); TESTD(s3); TESTP; putchar('"'); putchar(s1); putchar(s2); putchar(s3); putchar('|'); for (;;) { /* dump to end quote. */ if ((ch = getc(fp)) == EOF) return; putchar(ch); if (ch == '"') break; if (ch == '\\') { if ((ch = getc(fp)) == EOF) return; putchar(ch); } } putchar('\n'); } } int main(int argc, char *argv[]) { FILE *fp; for (; *argv != NULL; ++argv) { if ((fp = fopen(*argv, "r")) == NULL) { perror(*argv); return (1); } parse(fp); (void)fclose(fp); } return (0); } Index: vendor/nvi/dist/cl/cl.h =================================================================== --- vendor/nvi/dist/cl/cl.h (revision 366306) +++ vendor/nvi/dist/cl/cl.h (revision 366307) @@ -1,81 +1,83 @@ /*- * 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. */ #ifdef USE_WIDECHAR #define _XOPEN_SOURCE_EXTENDED #endif -#ifdef HAVE_NCURSES_H +#ifdef HAVE_NCURSESW_NCURSES_H +#include +#elif defined HAVE_NCURSES_H #include #else #include #endif typedef struct _cl_private { char ibuf[256]; /* Input keys. */ size_t skip; /* Remaining keys. */ CONVWIN cw; /* Conversion buffer. */ int eof_count; /* EOF count. */ struct termios orig; /* Original terminal values. */ struct termios ex_enter;/* Terminal values to enter ex. */ struct termios vi_enter;/* Terminal values to enter vi. */ char *el; /* Clear to EOL terminal string. */ char *cup; /* Cursor movement terminal string. */ char *cuu1; /* Cursor up terminal string. */ char *rmso, *smso; /* Inverse video terminal strings. */ char *smcup, *rmcup; /* Terminal start/stop strings. */ char *oname; /* Original screen window name. */ SCR *focus; /* Screen that has the "focus". */ int killersig; /* Killer signal. */ #define INDX_HUP 0 #define INDX_INT 1 #define INDX_TERM 2 #define INDX_WINCH 3 #define INDX_MAX 4 /* Original signal information. */ struct sigaction oact[INDX_MAX]; enum { /* Tty group write mode. */ TGW_UNKNOWN=0, TGW_SET, TGW_UNSET } tgw; enum { /* Terminal initialization strings. */ TE_SENT=0, TI_SENT } ti_te; #define CL_IN_EX 0x0001 /* Currently running ex. */ #define CL_LAYOUT 0x0002 /* Screen layout changed. */ #define CL_RENAME 0x0004 /* X11 xterm icon/window renamed. */ #define CL_RENAME_OK 0x0008 /* User wants the windows renamed. */ #define CL_SCR_EX_INIT 0x0010 /* Ex screen initialized. */ #define CL_SCR_VI_INIT 0x0020 /* Vi screen initialized. */ #define CL_SIGHUP 0x0040 /* SIGHUP arrived. */ #define CL_SIGINT 0x0080 /* SIGINT arrived. */ #define CL_SIGTERM 0x0100 /* SIGTERM arrived. */ #define CL_SIGWINCH 0x0200 /* SIGWINCH arrived. */ #define CL_STDIN_TTY 0x0400 /* Talking to a terminal. */ u_int32_t flags; } CL_PRIVATE; #define CLP(sp) ((CL_PRIVATE *)((sp)->gp->cl_private)) #define GCLP(gp) ((CL_PRIVATE *)gp->cl_private) #define CLSP(sp) ((WINDOW *)((sp)->cl_private)) /* Return possibilities from the keyboard read routine. */ typedef enum { INP_OK=0, INP_EOF, INP_ERR, INP_INTR, INP_TIMEOUT } input_t; /* The screen position relative to a specific window. */ #define RCNO(sp, cno) (cno) #define RLNO(sp, lno) (lno) #include "extern.h" Index: vendor/nvi/dist/cl/cl_read.c =================================================================== --- vendor/nvi/dist/cl/cl_read.c (revision 366306) +++ vendor/nvi/dist/cl/cl_read.c (revision 366307) @@ -1,326 +1,327 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include +#include #include #include #include "../common/common.h" #include "../ex/script.h" #include "cl.h" /* Pollution by Solaris curses. */ #undef columns #undef lines static input_t cl_read(SCR *, u_int32_t, char *, size_t, int *, struct timeval *); static int cl_resize(SCR *, size_t, size_t); /* * cl_event -- * Return a single event. * * PUBLIC: int cl_event(SCR *, EVENT *, u_int32_t, int); */ int cl_event(SCR *sp, EVENT *evp, u_int32_t flags, int ms) { struct timeval t, *tp; CL_PRIVATE *clp; size_t lines, columns; int changed, nr = 0; CHAR_T *wp; size_t wlen; int rc; /* * Queue signal based events. We never clear SIGHUP or SIGTERM events, * so that we just keep returning them until the editor dies. */ clp = CLP(sp); retest: if (LF_ISSET(EC_INTERRUPT) || F_ISSET(clp, CL_SIGINT)) { if (F_ISSET(clp, CL_SIGINT)) { F_CLR(clp, CL_SIGINT); evp->e_event = E_INTERRUPT; } else evp->e_event = E_TIMEOUT; return (0); } if (F_ISSET(clp, CL_SIGHUP | CL_SIGTERM | CL_SIGWINCH)) { if (F_ISSET(clp, CL_SIGHUP)) { evp->e_event = E_SIGHUP; return (0); } if (F_ISSET(clp, CL_SIGTERM)) { evp->e_event = E_SIGTERM; return (0); } if (F_ISSET(clp, CL_SIGWINCH)) { F_CLR(clp, CL_SIGWINCH); if (cl_ssize(sp, 1, &lines, &columns, &changed)) return (1); if (changed) { (void)cl_resize(sp, lines, columns); evp->e_event = E_WRESIZE; return (0); } /* No real change, ignore the signal. */ } } /* Set timer. */ if (ms == 0) tp = NULL; else { t.tv_sec = ms / 1000; t.tv_usec = (ms % 1000) * 1000; tp = &t; } /* Read input characters. */ read: switch (cl_read(sp, LF_ISSET(EC_QUOTED | EC_RAW), clp->ibuf + clp->skip, SIZE(clp->ibuf) - clp->skip, &nr, tp)) { case INP_OK: rc = INPUT2INT5(sp, clp->cw, clp->ibuf, nr + clp->skip, wp, wlen); evp->e_csp = wp; evp->e_len = wlen; evp->e_event = E_STRING; if (rc < 0) { int n = -rc; memmove(clp->ibuf, clp->ibuf + nr + clp->skip - n, n); clp->skip = n; if (wlen == 0) goto read; } else if (rc == 0) clp->skip = 0; else msgq(sp, M_ERR, "323|Invalid input. Truncated."); break; case INP_EOF: evp->e_event = E_EOF; break; case INP_ERR: evp->e_event = E_ERR; break; case INP_INTR: goto retest; case INP_TIMEOUT: evp->e_event = E_TIMEOUT; break; default: abort(); } return (0); } /* * cl_read -- * Read characters from the input. */ static input_t cl_read(SCR *sp, u_int32_t flags, char *bp, size_t blen, int *nrp, struct timeval *tp) { struct termios term1, term2; CL_PRIVATE *clp; GS *gp; fd_set rdfd; input_t rval; int maxfd, nr, term_reset; gp = sp->gp; clp = CLP(sp); term_reset = 0; /* * 1: A read from a file or a pipe. In this case, the reads * never timeout regardless. This means that we can hang * when trying to complete a map, but we're going to hang * on the next read anyway. */ if (!F_ISSET(clp, CL_STDIN_TTY)) { switch (nr = read(STDIN_FILENO, bp, blen)) { case 0: return (INP_EOF); case -1: goto err; default: *nrp = nr; return (INP_OK); } /* NOTREACHED */ } /* * 2: A read with an associated timeout, e.g., trying to complete * a map sequence. If input exists, we fall into #3. */ if (tp != NULL) { FD_ZERO(&rdfd); FD_SET(STDIN_FILENO, &rdfd); switch (select(STDIN_FILENO + 1, &rdfd, NULL, NULL, tp)) { case 0: return (INP_TIMEOUT); case -1: goto err; default: break; } } /* * The user can enter a key in the editor to quote a character. If we * get here and the next key is supposed to be quoted, do what we can. * Reset the tty so that the user can enter a ^C, ^Q, ^S. There's an * obvious race here, when the key has already been entered, but there's * nothing that we can do to fix that problem. * * The editor can ask for the next literal character even thought it's * generally running in line-at-a-time mode. Do what we can. */ if (LF_ISSET(EC_QUOTED | EC_RAW) && !tcgetattr(STDIN_FILENO, &term1)) { term_reset = 1; if (LF_ISSET(EC_QUOTED)) { term2 = term1; term2.c_lflag &= ~ISIG; term2.c_iflag &= ~(IXON | IXOFF); (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &term2); } else (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->vi_enter); } /* * 3: Wait for input. * * Select on the command input and scripting window file descriptors. * It's ugly that we wait on scripting file descriptors here, but it's * the only way to keep from locking out scripting windows. */ if (F_ISSET(gp, G_SCRWIN)) { loop: FD_ZERO(&rdfd); FD_SET(STDIN_FILENO, &rdfd); maxfd = STDIN_FILENO; if (F_ISSET(sp, SC_SCRIPT)) { FD_SET(sp->script->sh_master, &rdfd); if (sp->script->sh_master > maxfd) maxfd = sp->script->sh_master; } switch (select(maxfd + 1, &rdfd, NULL, NULL, NULL)) { case 0: abort(); case -1: goto err; default: break; } if (!FD_ISSET(STDIN_FILENO, &rdfd)) { if (sscr_input(sp)) return (INP_ERR); goto loop; } } /* * 4: Read the input. * * !!! * What's going on here is some scary stuff. Ex runs the terminal in * canonical mode. So, the character terminating a line of * input is returned in the buffer, but a trailing character is * not similarly included. As ex uses 0 and ^ as autoindent * commands, it has to see the trailing characters to determine * the difference between the user entering "0ab" and "0ab". We * leave an extra slot in the buffer, so that we can add a trailing * character if the buffer isn't terminated by a . We * lose if the buffer is too small for the line and exactly N characters * are entered followed by an character. */ #define ONE_FOR_EOF 1 switch (nr = read(STDIN_FILENO, bp, blen - ONE_FOR_EOF)) { case 0: /* EOF. */ /* * ^D in canonical mode returns a read of 0, i.e. EOF. EOF is * a valid command, but we don't want to loop forever because * the terminal driver is returning EOF because the user has * disconnected. The editor will almost certainly try to write * something before this fires, which should kill us, but You * Never Know. */ if (++clp->eof_count < 50) { bp[0] = clp->orig.c_cc[VEOF]; *nrp = 1; rval = INP_OK; } else rval = INP_EOF; break; case -1: /* Error or interrupt. */ err: if (errno == EINTR) rval = INP_INTR; else { rval = INP_ERR; msgq(sp, M_SYSERR, "input"); } break; default: /* Input characters. */ if (F_ISSET(sp, SC_EX) && bp[nr - 1] != '\n') bp[nr++] = clp->orig.c_cc[VEOF]; *nrp = nr; clp->eof_count = 0; rval = INP_OK; break; } /* Restore the terminal state if it was modified. */ if (term_reset) (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &term1); return (rval); } /* * cl_resize -- * Reset the options for a resize event. */ static int cl_resize(SCR *sp, size_t lines, size_t columns) { ARGS *argv[2], a, b; CHAR_T b1[1024]; a.bp = b1; b.bp = NULL; a.len = b.len = 0; argv[0] = &a; argv[1] = &b; a.len = SPRINTF(b1, sizeof(b1), L("lines=%lu"), (u_long)lines); if (opts_set(sp, argv, NULL)) return (1); a.len = SPRINTF(b1, sizeof(b1), L("columns=%lu"), (u_long)columns); if (opts_set(sp, argv, NULL)) return (1); return (0); } Index: vendor/nvi/dist/cl/cl_term.c =================================================================== --- vendor/nvi/dist/cl/cl_term.c (revision 366306) +++ vendor/nvi/dist/cl/cl_term.c (revision 366307) @@ -1,490 +1,492 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_TERM_H #include #endif #include #include #include "../common/common.h" #include "cl.h" static int cl_pfmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t); static size_t atoz_or(const char *, size_t); /* * XXX * THIS REQUIRES THAT ALL SCREENS SHARE A TERMINAL TYPE. */ typedef struct _tklist { char *ts; /* Key's termcap string. */ char *output; /* Corresponding vi command. */ char *name; /* Name. */ u_char value; /* Special value (for lookup). */ } TKLIST; static TKLIST const c_tklist[] = { /* Command mappings. */ {"kil1", "O", "insert line"}, {"kdch1", "x", "delete character"}, {"kcud1", "j", "cursor down"}, {"kel", "D", "delete to eol"}, {"kind", "\004", "scroll down"}, /* ^D */ {"kll", "$", "go to eol"}, {"kend", "$", "go to eol"}, {"khome", "^", "go to sol"}, {"kich1", "i", "insert at cursor"}, {"kdl1", "dd", "delete line"}, {"kcub1", "h", "cursor left"}, {"knp", "\006", "page down"}, /* ^F */ {"kpp", "\002", "page up"}, /* ^B */ {"kri", "\025", "scroll up"}, /* ^U */ {"ked", "dG", "delete to end of screen"}, {"kcuf1", "l", "cursor right"}, {"kcuu1", "k", "cursor up"}, {NULL}, }; static TKLIST const m1_tklist[] = { /* Input mappings (lookup). */ {NULL}, }; static TKLIST const m2_tklist[] = { /* Input mappings (set or delete). */ {"kcud1", "\033ja", "cursor down"}, /* ^[ja */ {"kcub1", "\033ha", "cursor left"}, /* ^[ha */ {"kcuu1", "\033ka", "cursor up"}, /* ^[ka */ {"kcuf1", "\033la", "cursor right"}, /* ^[la */ {NULL}, }; /* * cl_term_init -- * Initialize the special keys defined by the termcap/terminfo entry. * * PUBLIC: int cl_term_init(SCR *); */ int cl_term_init(SCR *sp) { KEYLIST *kp; SEQ *qp; TKLIST const *tkp; char *t; CHAR_T name[60]; CHAR_T output[5]; CHAR_T ts[20]; CHAR_T *wp; size_t wlen; /* Command mappings. */ for (tkp = c_tklist; tkp->name != NULL; ++tkp) { if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) continue; CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen); MEMCPY(name, wp, wlen); CHAR2INT(sp, t, strlen(t), wp, wlen); MEMCPY(ts, wp, wlen); CHAR2INT(sp, tkp->output, strlen(tkp->output), wp, wlen); MEMCPY(output, wp, wlen); if (seq_set(sp, name, strlen(tkp->name), ts, strlen(t), output, strlen(tkp->output), SEQ_COMMAND, SEQ_NOOVERWRITE | SEQ_SCREEN)) return (1); } /* Input mappings needing to be looked up. */ for (tkp = m1_tklist; tkp->name != NULL; ++tkp) { if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) continue; for (kp = keylist;; ++kp) if (kp->value == tkp->value) break; if (kp == NULL) continue; CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen); MEMCPY(name, wp, wlen); CHAR2INT(sp, t, strlen(t), wp, wlen); MEMCPY(ts, wp, wlen); output[0] = (UCHAR_T)kp->ch; if (seq_set(sp, name, strlen(tkp->name), ts, strlen(t), output, 1, SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN)) return (1); } /* Input mappings that are already set or are text deletions. */ for (tkp = m2_tklist; tkp->name != NULL; ++tkp) { if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) continue; /* * !!! * Some terminals' keys send single * characters. This is okay in command mapping, but not okay * in input mapping. That combination is the only one we'll * ever see, hopefully, so kluge it here for now. */ if (!strcmp(t, "\b")) continue; if (tkp->output == NULL) { CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen); MEMCPY(name, wp, wlen); CHAR2INT(sp, t, strlen(t), wp, wlen); MEMCPY(ts, wp, wlen); if (seq_set(sp, name, strlen(tkp->name), ts, strlen(t), NULL, 0, SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN)) return (1); } else { CHAR2INT(sp, tkp->name, strlen(tkp->name), wp, wlen); MEMCPY(name, wp, wlen); CHAR2INT(sp, t, strlen(t), wp, wlen); MEMCPY(ts, wp, wlen); CHAR2INT(sp, tkp->output, strlen(tkp->output), wp, wlen); MEMCPY(output, wp, wlen); if (seq_set(sp, name, strlen(tkp->name), ts, strlen(t), output, strlen(tkp->output), SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN)) return (1); } } /* * Rework any function key mappings that were set before the * screen was initialized. */ SLIST_FOREACH(qp, sp->gp->seqq, q) if (F_ISSET(qp, SEQ_FUNCMAP)) (void)cl_pfmap(sp, qp->stype, qp->input, qp->ilen, qp->output, qp->olen); return (0); } /* * cl_term_end -- * End the special keys defined by the termcap/terminfo entry. * * PUBLIC: int cl_term_end(GS *); */ int cl_term_end(GS *gp) { SEQ *qp, *nqp, *pre_qp = NULL; /* Delete screen specific mappings. */ SLIST_FOREACH_SAFE(qp, gp->seqq, q, nqp) if (F_ISSET(qp, SEQ_SCREEN)) { if (qp == SLIST_FIRST(gp->seqq)) SLIST_REMOVE_HEAD(gp->seqq, q); else SLIST_REMOVE_AFTER(pre_qp, q); (void)seq_free(qp); } else pre_qp = qp; return (0); } /* * cl_fmap -- * Map a function key. * * PUBLIC: int cl_fmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t); */ int cl_fmap(SCR *sp, seq_t stype, CHAR_T *from, size_t flen, CHAR_T *to, size_t tlen) { /* Ignore until the screen is running, do the real work then. */ if (F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_SCR_VI)) return (0); if (F_ISSET(sp, SC_EX) && !F_ISSET(sp, SC_SCR_EX)) return (0); return (cl_pfmap(sp, stype, from, flen, to, tlen)); } /* * cl_pfmap -- * Map a function key (private version). */ static int cl_pfmap(SCR *sp, seq_t stype, CHAR_T *from, size_t flen, CHAR_T *to, size_t tlen) { size_t nlen; char *p; char name[64]; CHAR_T keyname[64]; CHAR_T ts[20]; CHAR_T *wp; size_t wlen; (void)snprintf(name, sizeof(name), "kf%d", (int)STRTOL(from+1,NULL,10)); if ((p = tigetstr(name)) == NULL || p == (char *)-1 || strlen(p) == 0) p = NULL; if (p == NULL) { msgq_wstr(sp, M_ERR, from, "233|This terminal has no %s key"); return (1); } nlen = SPRINTF(keyname, SIZE(keyname), L("function key %d"), (int)STRTOL(from+1,NULL,10)); CHAR2INT(sp, p, strlen(p), wp, wlen); MEMCPY(ts, wp, wlen); return (seq_set(sp, keyname, nlen, ts, strlen(p), to, tlen, stype, SEQ_NOOVERWRITE | SEQ_SCREEN)); } /* * cl_optchange -- * Curses screen specific "option changed" routine. * * PUBLIC: int cl_optchange(SCR *, int, char *, u_long *); */ int cl_optchange(SCR *sp, int opt, char *str, u_long *valp) { CL_PRIVATE *clp; clp = CLP(sp); switch (opt) { case O_TERM: F_CLR(sp, SC_SCR_EX | SC_SCR_VI); /* FALLTHROUGH */ case O_COLUMNS: case O_LINES: /* * Changing the terminal type requires that we reinitialize * curses, while resizing does not. */ F_SET(sp->gp, G_SRESTART); break; case O_MESG: (void)cl_omesg(sp, clp, *valp); break; case O_WINDOWNAME: if (*valp) { F_SET(clp, CL_RENAME_OK); /* * If the screen is live, i.e. we're not reading the * .exrc file, update the window. */ if (sp->frp != NULL && sp->frp->name != NULL) (void)cl_rename(sp, sp->frp->name, 1); } else { F_CLR(clp, CL_RENAME_OK); (void)cl_rename(sp, NULL, 0); } break; } return (0); } /* * cl_omesg -- * Turn the tty write permission on or off. * * PUBLIC: int cl_omesg(SCR *, CL_PRIVATE *, int); */ int cl_omesg(SCR *sp, CL_PRIVATE *clp, int on) { struct stat sb; char *tty; /* Find the tty, get the current permissions. */ if ((tty = ttyname(STDERR_FILENO)) == NULL) { if (sp != NULL) msgq(sp, M_SYSERR, "stderr"); return (1); } if (stat(tty, &sb) < 0) { if (sp != NULL) msgq(sp, M_SYSERR, "%s", tty); return (1); } /* Save the original status if it's unknown. */ if (clp->tgw == TGW_UNKNOWN) clp->tgw = sb.st_mode & S_IWGRP ? TGW_SET : TGW_UNSET; /* Toggle the permissions. */ if (on) { if (chmod(tty, sb.st_mode | S_IWGRP) < 0) { if (sp != NULL) msgq(sp, M_SYSERR, "046|messages not turned on: %s", tty); return (1); } } else if (chmod(tty, sb.st_mode & ~S_IWGRP) < 0) { if (sp != NULL) msgq(sp, M_SYSERR, "045|messages not turned off: %s", tty); return (1); } return (0); } /* * cl_ssize -- * Return the terminal size. * * PUBLIC: int cl_ssize(SCR *, int, size_t *, size_t *, int *); */ int cl_ssize(SCR *sp, int sigwinch, size_t *rowp, size_t *colp, int *changedp) { struct winsize win; size_t col, row; int rval; char *p; /* Assume it's changed. */ if (changedp != NULL) *changedp = 1; /* * !!! * sp may be NULL. * * Get the screen rows and columns. If the values are wrong, it's * not a big deal -- as soon as the user sets them explicitly the * environment will be set and the screen package will use the new * values. * * Try TIOCGWINSZ. */ row = col = 0; if (ioctl(STDERR_FILENO, TIOCGWINSZ, &win) != -1) { row = win.ws_row; col = win.ws_col; } /* If here because of suspend or a signal, only trust TIOCGWINSZ. */ if (sigwinch) { /* * Somebody didn't get TIOCGWINSZ right, or has suspend * without window resizing support. The user just lost, * but there's nothing we can do. */ if (row == 0 || col == 0) { if (changedp != NULL) *changedp = 0; return (0); } /* * SunOS systems deliver SIGWINCH when windows are uncovered * as well as when they change size. In addition, we call * here when continuing after being suspended since the window * may have changed size. Since we don't want to background * all of the screens just because the window was uncovered, * ignore the signal if there's no change. */ if (sp != NULL && row == O_VAL(sp, O_LINES) && col == O_VAL(sp, O_COLUMNS)) { if (changedp != NULL) *changedp = 0; return (0); } if (rowp != NULL) *rowp = row; if (colp != NULL) *colp = col; return (0); } /* * !!! * If TIOCGWINSZ failed, or had entries of 0, try termcap. This * routine is called before any termcap or terminal information * has been set up. If there's no TERM environmental variable set, * let it go, at least ex can run. */ if (row == 0 || col == 0) { if ((p = getenv("TERM")) == NULL) goto noterm; - if (row == 0) + if (row == 0) { if ((rval = tigetnum("lines")) < 0) msgq(sp, M_SYSERR, "tigetnum: lines"); else row = rval; - if (col == 0) + } + if (col == 0) { if ((rval = tigetnum("cols")) < 0) msgq(sp, M_SYSERR, "tigetnum: cols"); else col = rval; + } } /* If nothing else, well, it's probably a VT100. */ noterm: if (row == 0) row = 24; if (col == 0) col = 80; /* * !!! * POSIX 1003.2 requires the environment to override everything. * Often, people can get nvi to stop messing up their screen by * deleting the LINES and COLUMNS environment variables from their * dot-files. */ if ((p = getenv("LINES")) != NULL) row = atoz_or(p, row); if ((p = getenv("COLUMNS")) != NULL) col = atoz_or(p, col); if (rowp != NULL) *rowp = row; if (colp != NULL) *colp = col; return (0); } /* * atoz_or -- * Parse non-zero positive decimal with a fallback. */ static size_t atoz_or(const char *s, size_t y) { char *ep; long x = strtol(s, &ep, 10); if (*ep == '\0' && (0 < x && x < INT_MAX)) return (size_t)x; else return y; } /* * cl_putchar -- * Function version of putchar, for tputs. * * PUBLIC: int cl_putchar(int); */ int cl_putchar(int ch) { return (putchar(ch)); } Index: vendor/nvi/dist/common/common.h =================================================================== --- vendor/nvi/dist/common/common.h (revision 366306) +++ vendor/nvi/dist/common/common.h (revision 366307) @@ -1,86 +1,94 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ +#ifndef TCSASOFT +#define TCSASOFT 0 +#endif + +#ifdef __linux__ +#include "/usr/include/db1/db.h" /* Only include db1. */ +#else #include "/usr/include/db.h" /* Only include db1. */ +#endif #include /* May refer to the bundled regex. */ /* * Forward structure declarations. Not pretty, but the include files * are far too interrelated for a clean solution. */ typedef struct _cb CB; typedef struct _csc CSC; typedef struct _conv CONV; typedef struct _conv_win CONVWIN; typedef struct _event EVENT; typedef struct _excmd EXCMD; typedef struct _exf EXF; typedef struct _fref FREF; typedef struct _gs GS; typedef struct _lmark LMARK; typedef struct _mark MARK; typedef struct _msg MSGS; typedef struct _option OPTION; typedef struct _optlist OPTLIST; typedef struct _scr SCR; typedef struct _script SCRIPT; typedef struct _seq SEQ; typedef struct _tag TAG; typedef struct _tagf TAGF; typedef struct _tagq TAGQ; typedef struct _text TEXT; /* Autoindent state. */ typedef enum { C_NOTSET, C_CARATSET, C_ZEROSET } carat_t; /* Busy message types. */ typedef enum { BUSY_ON = 1, BUSY_OFF, BUSY_UPDATE } busy_t; /* * Routines that return a confirmation return: * * CONF_NO User answered no. * CONF_QUIT User answered quit, eof or an error. * CONF_YES User answered yes. */ typedef enum { CONF_NO, CONF_QUIT, CONF_YES } conf_t; /* Directions. */ typedef enum { NOTSET, FORWARD, BACKWARD } dir_t; /* Line operations. */ typedef enum { LINE_APPEND, LINE_DELETE, LINE_INSERT, LINE_RESET } lnop_t; /* Lock return values. */ typedef enum { LOCK_FAILED, LOCK_SUCCESS, LOCK_UNAVAIL } lockr_t; /* Sequence types. */ typedef enum { SEQ_ABBREV, SEQ_COMMAND, SEQ_INPUT } seq_t; /* * Local includes. */ #include "key.h" /* Required by args.h. */ #include "args.h" /* Required by options.h. */ #include "options.h" /* Required by screen.h. */ #include "msg.h" /* Required by gs.h. */ #include "cut.h" /* Required by gs.h. */ #include "seq.h" /* Required by screen.h. */ #include "util.h" /* Required by ex.h. */ #include "mark.h" /* Required by gs.h. */ #include "conv.h" /* Required by ex.h and screen.h */ #include "../ex/ex.h" /* Required by gs.h. */ #include "gs.h" /* Required by screen.h. */ #include "screen.h" /* Required by exf.h. */ #include "exf.h" #include "log.h" #include "mem.h" #include "extern.h" Index: vendor/nvi/dist/common/cut.h =================================================================== --- vendor/nvi/dist/common/cut.h (revision 366306) +++ vendor/nvi/dist/common/cut.h (revision 366307) @@ -1,77 +1,77 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ typedef struct _texth TEXTH; /* TEXT list head structure. */ TAILQ_HEAD(_texth, _text); /* Cut buffers. */ struct _cb { SLIST_ENTRY(_cb) q; /* Linked list of cut buffers. */ TEXTH textq[1]; /* Linked list of TEXT structures. */ /* XXXX Needed ? Can non ascii-chars be cut buffer names ? */ CHAR_T name; /* Cut buffer name. */ size_t len; /* Total length of cut text. */ #define CB_LMODE 0x01 /* Cut was in line mode. */ u_int8_t flags; }; /* Lines/blocks of text. */ struct _text { /* Text: a linked list of lines. */ TAILQ_ENTRY(_text) q; /* Linked list of text structures. */ CHAR_T *lb; /* Line buffer. */ size_t lb_len; /* Line buffer length. */ size_t len; /* Line length. */ /* These fields are used by the vi text input routine. */ recno_t lno; /* 1-N: file line. */ #define ENTIRE_LINE ((size_t)-1) /* cno: end of the line. */ size_t cno; /* 0-N: file character in line. */ size_t ai; /* 0-N: autoindent bytes. */ size_t insert; /* 0-N: bytes to insert (push). */ size_t offset; /* 0-N: initial, unerasable chars. */ size_t owrite; /* 0-N: chars to overwrite. */ size_t R_erase; /* 0-N: 'R' erase count. */ size_t sv_cno; /* 0-N: Saved line cursor. */ size_t sv_len; /* 0-N: Saved line length. */ /* * These fields returns information from the vi text input routine. * * The termination condition. Note, this field is only valid if the * text input routine returns success. * TERM_BS: User backspaced over the prompt. * TERM_CEDIT: User entered . * TERM_CR: User entered ; no data. * TERM_ESC: User entered ; no data. * TERM_OK: Data available. * TERM_SEARCH: Incremental search. */ enum { TERM_BS, TERM_CEDIT, TERM_CR, TERM_ESC, TERM_OK, TERM_SEARCH } term; }; /* * Get named buffer 'name'. * Translate upper-case buffer names to lower-case buffer names. */ -#define CBNAME(sp, cbp, nch) { \ +#define CBNAME(sp, cbp, nch) do { \ CHAR_T L__name; \ L__name = isupper(nch) ? tolower(nch) : (nch); \ SLIST_FOREACH(cbp, sp->gp->cutq, q) \ if (cbp->name == L__name) \ break; \ -} +} while (0) /* Flags to the cut() routine. */ #define CUT_LINEMODE 0x01 /* Cut in line mode. */ #define CUT_NUMOPT 0x02 /* Numeric buffer: optional. */ #define CUT_NUMREQ 0x04 /* Numeric buffer: required. */ Index: vendor/nvi/dist/common/exf.c =================================================================== --- vendor/nvi/dist/common/exf.c (revision 366306) +++ vendor/nvi/dist/common/exf.c (revision 366307) @@ -1,1476 +1,1479 @@ /*- * 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" #include #include #include #include /* * We include , because the flock(2) and open(2) #defines * were found there on historical systems. We also include * because the open(2) #defines are found there on newer systems. */ #include #include #include #include #include #include #include #include #include #include #include "common.h" static int file_backup(SCR *, char *, char *); static void file_cinit(SCR *); static void file_encinit(SCR *); static void file_comment(SCR *); static int file_spath(SCR *, FREF *, struct stat *, int *); /* * file_add -- * Insert a file name into the FREF list, if it doesn't already * appear in it. * * !!! * The "if it doesn't already appear" changes vi's semantics slightly. If * you do a "vi foo bar", and then execute "next bar baz", the edit of bar * will reflect the line/column of the previous edit session. Historic nvi * did not do this. The change is a logical extension of the change where * vi now remembers the last location in any file that it has ever edited, * not just the previously edited file. * * PUBLIC: FREF *file_add(SCR *, char *); */ FREF * file_add(SCR *sp, char *name) { GS *gp; FREF *frp, *tfrp; /* * Return it if it already exists. Note that we test against the * user's name, whatever that happens to be, including if it's a * temporary file. * * If the user added a file but was unable to initialize it, there * can be file list entries where the name field is NULL. Discard * them the next time we see them. */ gp = sp->gp; if (name != NULL) TAILQ_FOREACH_SAFE(frp, gp->frefq, q, tfrp) { if (frp->name == NULL) { TAILQ_REMOVE(gp->frefq, frp, q); free(frp->name); free(frp); continue; } if (!strcmp(frp->name, name)) return (frp); } /* Allocate and initialize the FREF structure. */ CALLOC(sp, frp, 1, sizeof(FREF)); if (frp == NULL) return (NULL); /* * If no file name specified, or if the file name is a request * for something temporary, file_init() will allocate the file * name. Temporary files are always ignored. */ if (name != NULL && strcmp(name, TEMPORARY_FILE_STRING) && (frp->name = strdup(name)) == NULL) { free(frp); msgq(sp, M_SYSERR, NULL); return (NULL); } /* Append into the chain of file names. */ TAILQ_INSERT_TAIL(gp->frefq, frp, q); return (frp); } /* * file_init -- * Start editing a file, based on the FREF structure. If successsful, * let go of any previous file. Don't release the previous file until * absolutely sure we have the new one. * * PUBLIC: int file_init(SCR *, FREF *, char *, int); */ int file_init(SCR *sp, FREF *frp, char *rcv_name, int flags) { EXF *ep; RECNOINFO oinfo = { 0 }; struct stat sb; size_t psize; int fd, exists, open_err, readonly; char *oname, *tname; open_err = readonly = 0; /* * If the file is a recovery file, let the recovery code handle it. * Clear the FR_RECOVER flag first -- the recovery code does set up, * and then calls us! If the recovery call fails, it's probably * because the named file doesn't exist. So, move boldly forward, * presuming that there's an error message the user will get to see. */ if (F_ISSET(frp, FR_RECOVER)) { F_CLR(frp, FR_RECOVER); return (rcv_read(sp, frp)); } /* * Required FRP initialization; the only flag we keep is the * cursor information. */ F_CLR(frp, ~FR_CURSORSET); /* * Required EXF initialization: * Flush the line caches. * Default recover mail file fd to -1. * Set initial EXF flag bits. */ CALLOC_RET(sp, ep, 1, sizeof(EXF)); ep->c_lno = ep->c_nlines = OOBLNO; ep->rcv_fd = -1; F_SET(ep, F_FIRSTMODIFY); /* * Scan the user's path to find the file that we're going to * try and open. */ if (file_spath(sp, frp, &sb, &exists)) return (1); /* * If no name or backing file, for whatever reason, create a backing * temporary file, saving the temp file name so we can later unlink * it. If the user never named this file, copy the temporary file name * to the real name (we display that until the user renames it). */ oname = frp->name; if (LF_ISSET(FS_OPENERR) || oname == NULL || !exists) { struct stat sb; if (opts_empty(sp, O_TMPDIR, 0)) goto err; if ((tname = join(O_STR(sp, O_TMPDIR), "vi.XXXXXXXXXX")) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } if ((fd = mkstemp(tname)) == -1 || fstat(fd, &sb)) { free(tname); msgq(sp, M_SYSERR, "237|Unable to create temporary file"); goto err; } (void)close(fd); frp->tname = tname; if (frp->name == NULL) { F_SET(frp, FR_TMPFILE); if ((frp->name = strdup(tname)) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } } oname = frp->tname; psize = 1024; if (!LF_ISSET(FS_OPENERR)) F_SET(frp, FR_NEWFILE); - ep->mtim = sb.st_mtimespec; + ep->mtim = sb.st_mtim; } else { /* * XXX * A seat of the pants calculation: try to keep the file in * 15 pages or less. Don't use a page size larger than 16K * (vi should have good locality) or smaller than 1K. */ psize = ((sb.st_size / 15) + 1023) / 1024; if (psize > 16) psize = 16; if (psize == 0) psize = 1; psize = p2roundup(psize) << 10; F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; - ep->mtim = sb.st_mtimespec; + ep->mtim = sb.st_mtim; if (!S_ISREG(sb.st_mode)) msgq_str(sp, M_ERR, oname, "238|Warning: %s is not a regular file"); } /* Set up recovery. */ oinfo.bval = '\n'; /* Always set. */ oinfo.psize = psize; oinfo.flags = F_ISSET(sp->gp, G_SNAPSHOT) ? R_SNAPSHOT : 0; if (rcv_name == NULL) { if (!rcv_tmp(sp, ep, frp->name)) oinfo.bfname = ep->rcv_path; } else { if ((ep->rcv_path = strdup(rcv_name)) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } oinfo.bfname = ep->rcv_path; F_SET(ep, F_MODIFIED); } /* Open a db structure. */ if ((ep->db = dbopen(rcv_name == NULL ? oname : NULL, O_NONBLOCK | O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, DB_RECNO, &oinfo)) == NULL) { msgq_str(sp, M_SYSERR, rcv_name == NULL ? oname : rcv_name, "%s"); if (F_ISSET(frp, FR_NEWFILE)) goto err; /* * !!! * Historically, vi permitted users to edit files that couldn't * be read. This isn't useful for single files from a command * line, but it's quite useful for "vi *.c", since you can skip * past files that you can't read. */ open_err = 1; goto oerr; } /* * Do the remaining things that can cause failure of the new file, * mark and logging initialization. */ if (mark_init(sp, ep) || log_init(sp, ep)) goto err; /* * Set the alternate file name to be the file we're discarding. * * !!! * Temporary files can't become alternate files, so there's no file * name. This matches historical practice, although it could only * happen in historical vi as the result of the initial command, i.e. * if vi was executed without a file name. */ if (LF_ISSET(FS_SETALT)) set_alt_name(sp, sp->frp == NULL || F_ISSET(sp->frp, FR_TMPFILE) ? NULL : sp->frp->name); /* * Close the previous file; if that fails, close the new one and run * for the border. * * !!! * There's a nasty special case. If the user edits a temporary file, * and then does an ":e! %", we need to re-initialize the backing * file, but we can't change the name. (It's worse -- we're dealing * with *names* here, we can't even detect that it happened.) Set a * flag so that the file_end routine ignores the backing information * of the old file if it happens to be the same as the new one. * * !!! * Side-effect: after the call to file_end(), sp->frp may be NULL. */ if (sp->ep != NULL) { F_SET(frp, FR_DONTDELETE); if (file_end(sp, NULL, LF_ISSET(FS_FORCE))) { (void)file_end(sp, ep, 1); goto err; } F_CLR(frp, FR_DONTDELETE); } /* * Lock the file; if it's a recovery file, it should already be * locked. Note, we acquire the lock after the previous file * has been ended, so that we don't get an "already locked" error * for ":edit!". * * XXX * While the user can't interrupt us between the open and here, * there's a race between the dbopen() and the lock. Not much * we can do about it. * * XXX * We don't make a big deal of not being able to lock the file. As * locking rarely works over NFS, and often fails if the file was * mmap(2)'d, it's far too common to do anything like print an error * message, let alone make the file readonly. At some future time, * when locking is a little more reliable, this should change to be * an error. */ if (rcv_name == NULL) switch (file_lock(sp, oname, ep->db->fd(ep->db), 0)) { case LOCK_FAILED: F_SET(frp, FR_UNLOCKED); break; case LOCK_UNAVAIL: readonly = 1; if (F_ISSET(sp, SC_READONLY)) break; msgq_str(sp, M_INFO, oname, "239|%s already locked, session is read-only"); break; case LOCK_SUCCESS: break; } /* * Historically, the readonly edit option was set per edit buffer in * vi, unless the -R command-line option was specified or the program * was executed as "view". (Well, to be truthful, if the letter 'w' * occurred anywhere in the program name, but let's not get into that.) * So, the persistent readonly state has to be stored in the screen * structure, and the edit option value toggles with the contents of * the edit buffer. If the persistent readonly flag is set, set the * readonly edit option. * * Otherwise, try and figure out if a file is readonly. This is a * dangerous thing to do. The kernel is the only arbiter of whether * or not a file is writeable, and the best that a user program can * do is guess. Obvious loopholes are files that are on a file system * mounted readonly (access catches this one on a few systems), or * alternate protection mechanisms, ACL's for example, that we can't * portably check. Lots of fun, and only here because users whined. * * !!! * Historic vi displayed the readonly message if none of the file * write bits were set, or if an an access(2) call on the path * failed. This seems reasonable. If the file is mode 444, root * users may want to know that the owner of the file did not expect * it to be written. * * Historic vi set the readonly bit if no write bits were set for * a file, even if the access call would have succeeded. This makes * the superuser force the write even when vi expects that it will * succeed. I'm less supportive of this semantic, but it's historic * practice and the conservative approach to vi'ing files as root. * * It would be nice if there was some way to update this when the user * does a "^Z; chmod ...". The problem is that we'd first have to * distinguish between readonly bits set because of file permissions * and those set for other reasons. That's not too hard, but deciding * when to reevaluate the permissions is trickier. An alternative * might be to turn off the readonly bit if the user forces a write * and it succeeds. * * XXX * Access(2) doesn't consider the effective uid/gid values. This * probably isn't a problem for vi when it's running standalone. */ if (readonly || F_ISSET(sp, SC_READONLY) || (!F_ISSET(frp, FR_NEWFILE) && (!(sb.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) || access(frp->name, W_OK)))) O_SET(sp, O_READONLY); else O_CLR(sp, O_READONLY); /* Switch... */ ++ep->refcnt; sp->ep = ep; sp->frp = frp; /* Detect and set the file encoding */ file_encinit(sp); /* Set the initial cursor position, queue initial command. */ file_cinit(sp); /* Redraw the screen from scratch, schedule a welcome message. */ F_SET(sp, SC_SCR_REFORMAT | SC_STATUS); return (0); err: free(frp->name); frp->name = NULL; if (frp->tname != NULL) { (void)unlink(frp->tname); free(frp->tname); frp->tname = NULL; } oerr: if (F_ISSET(ep, F_RCV_ON)) (void)unlink(ep->rcv_path); free(ep->rcv_path); ep->rcv_path = NULL; if (ep->db != NULL) (void)ep->db->close(ep->db); free(ep); return (open_err ? file_init(sp, frp, rcv_name, flags | FS_OPENERR) : 1); } /* * file_spath -- * Scan the user's path to find the file that we're going to * try and open. */ static int file_spath(SCR *sp, FREF *frp, struct stat *sbp, int *existsp) { int savech; size_t len; int found; char *name, *p, *t, *path; /* * If the name is NULL or an explicit reference (i.e., the first * component is . or ..) ignore the O_PATH option. */ name = frp->name; if (name == NULL) { *existsp = 0; return (0); } if (name[0] == '/' || (name[0] == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/')))) { *existsp = !stat(name, sbp); return (0); } /* Try . */ if (!stat(name, sbp)) { *existsp = 1; return (0); } /* Try the O_PATH option values. */ for (found = 0, p = t = O_STR(sp, O_PATH);; ++p) if (*p == ':' || *p == '\0') { /* * Ignore the empty strings and ".", since we've already * tried the current directory. */ if (t < p && (p - t != 1 || *t != '.')) { savech = *p; *p = '\0'; if ((path = join(t, name)) == NULL) { msgq(sp, M_SYSERR, NULL); break; } len = strlen(path); *p = savech; if (!stat(path, sbp)) { found = 1; break; } free(path); } t = p + 1; if (*p == '\0') break; } /* If we found it, build a new pathname and discard the old one. */ if (found) { free(frp->name); frp->name = path; } *existsp = found; return (0); } /* * file_cinit -- * Set up the initial cursor position. */ static void file_cinit(SCR *sp) { GS *gp; MARK m; size_t len; int nb; CHAR_T *wp; size_t wlen; /* Set some basic defaults. */ sp->lno = 1; sp->cno = 0; /* * Historically, initial commands (the -c option) weren't executed * until a file was loaded, e.g. "vi +10 nofile", followed by an * :edit or :tag command, would execute the +10 on the file loaded * by the subsequent command, (assuming that it existed). This * applied as well to files loaded using the tag commands, and we * follow that historic practice. Also, all initial commands were * ex commands and were always executed on the last line of the file. * * Otherwise, if no initial command for this file: * If in ex mode, move to the last line, first nonblank character. * If the file has previously been edited, move to the last known * position, and check it for validity. * Otherwise, move to the first line, first nonblank. * * This gets called by the file init code, because we may be in a * file of ex commands and we want to execute them from the right * location in the file. */ nb = 0; gp = sp->gp; if (gp->c_option != NULL && !F_ISSET(sp->frp, FR_NEWFILE)) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; } CHAR2INT(sp, gp->c_option, strlen(gp->c_option) + 1, wp, wlen); if (ex_run_str(sp, "-c option", wp, wlen - 1, 1, 0)) return; gp->c_option = NULL; } else if (F_ISSET(sp, SC_EX)) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; return; } nb = 1; } else { if (F_ISSET(sp->frp, FR_CURSORSET)) { sp->lno = sp->frp->lno; sp->cno = sp->frp->cno; /* If returning to a file in vi, center the line. */ F_SET(sp, SC_SCR_CENTER); } else { if (O_ISSET(sp, O_COMMENT)) file_comment(sp); else sp->lno = 1; nb = 1; } if (db_get(sp, sp->lno, 0, NULL, &len)) { sp->lno = 1; sp->cno = 0; return; } if (!nb && sp->cno > len) nb = 1; } if (nb) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * !!! * The initial column is also the most attractive column. */ sp->rcm = sp->cno; /* * !!! * Historically, vi initialized the absolute mark, but ex did not. * Which meant, that if the first command in ex mode was "visual", * or if an ex command was executed first (e.g. vi +10 file) vi was * entered without the mark being initialized. For consistency, if * the file isn't empty, we initialize it for everyone, believing * that it can't hurt, and is generally useful. Not initializing it * if the file is empty is historic practice, although it has always * been possible to set (and use) marks in empty vi files. */ m.lno = sp->lno; m.cno = sp->cno; (void)mark_set(sp, ABSMARK1, &m, 0); } /* * file_end -- * Stop editing a file. * * PUBLIC: int file_end(SCR *, EXF *, int); */ int file_end(SCR *sp, EXF *ep, int force) { FREF *frp; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * (If argument ep is NULL, use sp->ep.) * * If multiply referenced, just decrement the count and return. */ if (ep == NULL) ep = sp->ep; if (--ep->refcnt != 0) return (0); /* * * Clean up the FREF structure. * * Save the cursor location. * * XXX * It would be cleaner to do this somewhere else, but by the time * ex or vi knows that we're changing files it's already happened. */ frp = sp->frp; frp->lno = sp->lno; frp->cno = sp->cno; F_SET(frp, FR_CURSORSET); /* * We may no longer need the temporary backing file, so clean it * up. We don't need the FREF structure either, if the file was * never named, so lose it. * * !!! * Re: FR_DONTDELETE, see the comment above in file_init(). */ if (!F_ISSET(frp, FR_DONTDELETE) && frp->tname != NULL) { if (unlink(frp->tname)) msgq_str(sp, M_SYSERR, frp->tname, "240|%s: remove"); free(frp->tname); frp->tname = NULL; if (F_ISSET(frp, FR_TMPFILE)) { TAILQ_REMOVE(sp->gp->frefq, frp, q); free(frp->name); free(frp); } sp->frp = NULL; } /* * Clean up the EXF structure. * * Close the db structure. */ if (ep->db->close != NULL && ep->db->close(ep->db) && !force) { msgq_str(sp, M_SYSERR, frp->name, "241|%s: close"); ++ep->refcnt; return (1); } /* COMMITTED TO THE CLOSE. THERE'S NO GOING BACK... */ /* Stop logging. */ (void)log_end(sp, ep); /* Free up any marks. */ (void)mark_end(sp, ep); /* * Delete recovery files, close the open descriptor, free recovery * memory. See recover.c for a description of the protocol. * * XXX * Unlink backup file first, we can detect that the recovery file * doesn't reference anything when the user tries to recover it. * There's a race, here, obviously, but it's fairly small. */ if (!F_ISSET(ep, F_RCV_NORM)) { if (ep->rcv_path != NULL && unlink(ep->rcv_path)) msgq_str(sp, M_SYSERR, ep->rcv_path, "242|%s: remove"); if (ep->rcv_mpath != NULL && unlink(ep->rcv_mpath)) msgq_str(sp, M_SYSERR, ep->rcv_mpath, "243|%s: remove"); } if (ep->rcv_fd != -1) (void)close(ep->rcv_fd); free(ep->rcv_path); free(ep->rcv_mpath); if (ep->c_blen > 0) free(ep->c_lp); free(ep); return (0); } /* * file_write -- * Write the file to disk. Historic vi had fairly convoluted * semantics for whether or not writes would happen. That's * why all the flags. * * PUBLIC: int file_write(SCR *, MARK *, MARK *, char *, int); */ int file_write(SCR *sp, MARK *fm, MARK *tm, char *name, int flags) { enum { NEWFILE, OLDFILE } mtype; struct stat sb; EXF *ep; FILE *fp; FREF *frp; MARK from, to; size_t len; u_long nlno, nch; int fd, nf, noname, oflags, rval; char *p, *s, *t, buf[1024]; const char *msgstr; ep = sp->ep; frp = sp->frp; /* * Writing '%', or naming the current file explicitly, has the * same semantics as writing without a name. */ if (name == NULL || !strcmp(name, frp->name)) { noname = 1; name = frp->name; } else noname = 0; /* Can't write files marked read-only, unless forced. */ if (!LF_ISSET(FS_FORCE) && noname && O_ISSET(sp, O_READONLY)) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "244|Read-only file, not written; use ! to override" : "245|Read-only file, not written"); return (1); } /* If not forced, not appending, and "writeany" not set ... */ if (!LF_ISSET(FS_FORCE | FS_APPEND) && !O_ISSET(sp, O_WRITEANY)) { /* Don't overwrite anything but the original file. */ if ((!noname || F_ISSET(frp, FR_NAMECHANGE)) && !stat(name, &sb)) { msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ? "246|%s exists, not written; use ! to override" : "247|%s exists, not written"); return (1); } /* * Don't write part of any existing file. Only test for the * original file, the previous test catches anything else. */ if (!LF_ISSET(FS_ALL) && noname && !stat(name, &sb)) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "248|Partial file, not written; use ! to override" : "249|Partial file, not written"); return (1); } } /* * Figure out if the file already exists -- if it doesn't, we display * the "new file" message. The stat might not be necessary, but we * just repeat it because it's easier than hacking the previous tests. * The information is only used for the user message and modification * time test, so we can ignore the obvious race condition. * * One final test. If we're not forcing or appending the current file, * and we have a saved modification time, object if the file changed * since we last edited or wrote it, and make them force it. */ if (stat(name, &sb)) mtype = NEWFILE; else { if (noname && !LF_ISSET(FS_FORCE | FS_APPEND) && ((F_ISSET(ep, F_DEVSET) && (sb.st_dev != ep->mdev || sb.st_ino != ep->minode)) || - timespeccmp(&sb.st_mtimespec, &ep->mtim, !=))) { + timespeccmp(&sb.st_mtim, &ep->mtim, !=))) { msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ? "250|%s: file modified more recently than this copy; use ! to override" : "251|%s: file modified more recently than this copy"); return (1); } mtype = OLDFILE; } /* Set flags to create, write, and either append or truncate. */ oflags = O_CREAT | O_WRONLY | (LF_ISSET(FS_APPEND) ? O_APPEND : O_TRUNC); /* Backup the file if requested. */ if (!opts_empty(sp, O_BACKUP, 1) && file_backup(sp, name, O_STR(sp, O_BACKUP)) && !LF_ISSET(FS_FORCE)) return (1); /* Open the file. */ if ((fd = open(name, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) < 0) { if (errno == EACCES && LF_ISSET(FS_FORCE)) { /* * If the user owns the file but does not * have write permission on it, grant it * automatically for the duration of the * opening of the file, if possible. */ struct stat sb; mode_t fmode; if (stat(name, &sb) != 0) goto fail_open; fmode = sb.st_mode; if (!(sb.st_mode & S_IWUSR) && sb.st_uid == getuid()) fmode |= S_IWUSR; else goto fail_open; if (chmod(name, fmode) != 0) goto fail_open; fd = open(name, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd == -1) goto fail_open; (void)fchmod(fd, sb.st_mode); goto success_open; fail_open: errno = EACCES; } msgq_str(sp, M_SYSERR, name, "%s"); return (1); } success_open: /* Try and get a lock. */ if (!noname && file_lock(sp, NULL, fd, 0) == LOCK_UNAVAIL) msgq_str(sp, M_ERR, name, "252|%s: write lock was unavailable"); /* * Use stdio for buffering. * * XXX * SVR4.2 requires the fdopen mode exactly match the original open * mode, i.e. you have to open with "a" if appending. */ if ((fp = fdopen(fd, LF_ISSET(FS_APPEND) ? "a" : "w")) == NULL) { msgq_str(sp, M_SYSERR, name, "%s"); (void)close(fd); return (1); } /* Build fake addresses, if necessary. */ if (fm == NULL) { from.lno = 1; from.cno = 0; fm = &from; if (db_last(sp, &to.lno)) return (1); to.cno = 0; tm = &to; } rval = ex_writefp(sp, name, fp, fm, tm, &nlno, &nch, 0); /* * Save the new last modification time -- even if the write fails * we re-init the time. That way the user can clean up the disk * and rewrite without having to force it. */ - if (noname) + if (noname) { if (stat(name, &sb)) timepoint_system(&ep->mtim); else { F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; - ep->mtim = sb.st_mtimespec; + ep->mtim = sb.st_mtim; } + } /* * If the write failed, complain loudly. ex_writefp() has already * complained about the actual error, reinforce it if data was lost. */ if (rval) { if (!LF_ISSET(FS_APPEND)) msgq_str(sp, M_ERR, name, "254|%s: WARNING: FILE TRUNCATED"); return (1); } /* * Once we've actually written the file, it doesn't matter that the * file name was changed -- if it was, we've already whacked it. */ F_CLR(frp, FR_NAMECHANGE); /* * If wrote the entire file, and it wasn't by appending it to a file, * clear the modified bit. If the file was written to the original * file name and the file is a temporary, set the "no exit" bit. This * permits the user to write the file and use it in the context of the * filesystem, but still keeps them from discarding their changes by * exiting. */ if (LF_ISSET(FS_ALL) && !LF_ISSET(FS_APPEND)) { F_CLR(ep, F_MODIFIED); - if (F_ISSET(frp, FR_TMPFILE)) + if (F_ISSET(frp, FR_TMPFILE)) { if (noname) F_SET(frp, FR_TMPEXIT); else F_CLR(frp, FR_TMPEXIT); + } } p = msg_print(sp, name, &nf); switch (mtype) { case NEWFILE: msgstr = msg_cat(sp, "256|%s: new file: %lu lines, %lu characters", NULL); len = snprintf(buf, sizeof(buf), msgstr, p, nlno, nch); break; case OLDFILE: msgstr = msg_cat(sp, LF_ISSET(FS_APPEND) ? "315|%s: appended: %lu lines, %lu characters" : "257|%s: %lu lines, %lu characters", NULL); len = snprintf(buf, sizeof(buf), msgstr, p, nlno, nch); break; default: abort(); } /* * There's a nasty problem with long path names. Cscope and tags files * can result in long paths and vi will request a continuation key from * the user. Unfortunately, the user has typed ahead, and chaos will * result. If we assume that the characters in the filenames only take * a single screen column each, we can trim the filename. */ s = buf; if (len >= sp->cols) { for (s = buf, t = buf + strlen(p); s < t && (*s != '/' || len >= sp->cols - 3); ++s, --len); if (s == t) s = buf; else { *--s = '.'; /* Leading ellipses. */ *--s = '.'; *--s = '.'; } } msgq(sp, M_INFO, "%s", s); if (nf) FREE_SPACE(sp, p, 0); return (0); } /* * file_backup -- * Backup the about-to-be-written file. * * XXX * We do the backup by copying the entire file. It would be nice to do * a rename instead, but: (1) both files may not fit and we want to fail * before doing the rename; (2) the backup file may not be on the same * disk partition as the file being written; (3) there may be optional * file information (MACs, DACs, whatever) that we won't get right if we * recreate the file. So, let's not risk it. */ static int file_backup(SCR *sp, char *name, char *bname) { struct dirent *dp; struct stat sb; DIR *dirp; EXCMD cmd; off_t off; size_t blen; int flags, maxnum, nr, num, nw, rfd, wfd, version; char *bp, *estr, *p, *pct, *slash, *t, *wfname, buf[8192]; CHAR_T *wp; size_t wlen; size_t nlen; char *d = NULL; rfd = wfd = -1; bp = estr = wfname = NULL; /* * Open the current file for reading. Do this first, so that * we don't exec a shell before the most likely failure point. * If it doesn't exist, it's okay, there's just nothing to back * up. */ errno = 0; if ((rfd = open(name, O_RDONLY, 0)) < 0) { if (errno == ENOENT) return (0); estr = name; goto err; } /* * If the name starts with an 'N' character, add a version number * to the name. Strip the leading N from the string passed to the * expansion routines, for no particular reason. It would be nice * to permit users to put the version number anywhere in the backup * name, but there isn't a special character that we can use in the * name, and giving a new character a special meaning leads to ugly * hacks both here and in the supporting ex routines. * * Shell and file name expand the option's value. */ ex_cinit(sp, &cmd, 0, 0, 0, 0, 0); if (bname[0] == 'N') { version = 1; ++bname; } else version = 0; CHAR2INT(sp, bname, strlen(bname), wp, wlen); if ((wp = v_wstrdup(sp, wp, wlen)) == NULL) return (1); if (argv_exp2(sp, &cmd, wp, wlen)) { free(wp); return (1); } free(wp); /* * 0 args: impossible. * 1 args: use it. * >1 args: object, too many args. */ if (cmd.argc != 1) { msgq_str(sp, M_ERR, bname, "258|%s expanded into too many file names"); (void)close(rfd); return (1); } /* * If appending a version number, read through the directory, looking * for file names that match the name followed by a number. Make all * of the other % characters in name literal, so the user doesn't get * surprised and sscanf doesn't drop core indirecting through pointers * that don't exist. If any such files are found, increment its number * by one. */ if (version) { GET_SPACE_GOTOC(sp, bp, blen, cmd.argv[0]->len * 2 + 50); INT2CHAR(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, p, nlen); d = strdup(p); p = d; for (t = bp, slash = NULL; p[0] != '\0'; *t++ = *p++) if (p[0] == '%') { if (p[1] != '%') *t++ = '%'; } else if (p[0] == '/') slash = t; pct = t; *t++ = '%'; *t++ = 'd'; *t = '\0'; if (slash == NULL) { dirp = opendir("."); p = bp; } else { *slash = '\0'; dirp = opendir(bp); *slash = '/'; p = slash + 1; } if (dirp == NULL) { INT2CHAR(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, estr, nlen); goto err; } for (maxnum = 0; (dp = readdir(dirp)) != NULL;) if (sscanf(dp->d_name, p, &num) == 1 && num > maxnum) maxnum = num; (void)closedir(dirp); /* Format the backup file name. */ (void)snprintf(pct, blen - (pct - bp), "%d", maxnum + 1); wfname = bp; } else { bp = NULL; INT2CHAR(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, wfname, nlen); } /* Open the backup file, avoiding lurkers. */ if (stat(wfname, &sb) == 0) { if (!S_ISREG(sb.st_mode)) { msgq_str(sp, M_ERR, bname, "259|%s: not a regular file"); goto err; } if (sb.st_uid != getuid()) { msgq_str(sp, M_ERR, bname, "260|%s: not owned by you"); goto err; } if (sb.st_mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) { msgq_str(sp, M_ERR, bname, "261|%s: accessible by a user other than the owner"); goto err; } flags = O_TRUNC; } else flags = O_CREAT | O_EXCL; if ((wfd = open(wfname, flags | O_WRONLY, S_IRUSR | S_IWUSR)) < 0) { estr = bname; goto err; } /* Copy the file's current contents to its backup value. */ while ((nr = read(rfd, buf, sizeof(buf))) > 0) for (off = 0; nr != 0; nr -= nw, off += nw) if ((nw = write(wfd, buf + off, nr)) < 0) { estr = wfname; goto err; } if (nr < 0) { estr = name; goto err; } if (close(rfd)) { estr = name; goto err; } if (close(wfd)) { estr = wfname; goto err; } free(d); if (bp != NULL) FREE_SPACE(sp, bp, blen); return (0); alloc_err: err: if (rfd != -1) (void)close(rfd); if (wfd != -1) { (void)unlink(wfname); (void)close(wfd); } if (estr) msgq_str(sp, M_SYSERR, estr, "%s"); free(d); if (bp != NULL) FREE_SPACE(sp, bp, blen); return (1); } /* * file_encinit -- * Read the first line and set the O_FILEENCODING. */ static void file_encinit(SCR *sp) { #if defined(USE_WIDECHAR) && defined(USE_ICONV) size_t len; char *p; size_t blen = 0; char buf[4096]; /* not need to be '\0'-terminated */ recno_t ln = 1; EXF *ep; ep = sp->ep; while (!db_rget(sp, ln++, &p, &len)) { if (blen + len > sizeof(buf)) len = sizeof(buf) - blen; memcpy(buf + blen, p, len); blen += len; if (blen == sizeof(buf)) break; else buf[blen++] = '\n'; } /* * Detect UTF-8 and fallback to the locale/preset encoding. * * XXX * A manually set O_FILEENCODING indicates the "fallback * encoding", but UTF-8, which can be safely detected, is not * inherited from the old screen. */ if (looks_utf8(buf, blen) > 1) o_set(sp, O_FILEENCODING, OS_STRDUP, "utf-8", 0); else if (!O_ISSET(sp, O_FILEENCODING) || !strcasecmp(O_STR(sp, O_FILEENCODING), "utf-8")) o_set(sp, O_FILEENCODING, OS_STRDUP, codeset(), 0); conv_enc(sp, O_FILEENCODING, 0); #endif } /* * file_comment -- * Skip the first comment. */ static void file_comment(SCR *sp) { recno_t lno; size_t len; CHAR_T *p; for (lno = 1; !db_get(sp, lno, 0, &p, &len) && len == 0; ++lno); if (p == NULL) return; if (p[0] == '#') { F_SET(sp, SC_SCR_TOP); while (!db_get(sp, ++lno, 0, &p, &len)) if (len < 1 || p[0] != '#') { sp->lno = lno; return; } } else if (len > 1 && p[0] == '/' && p[1] == '*') { F_SET(sp, SC_SCR_TOP); do { for (; len > 1; --len, ++p) if (p[0] == '*' && p[1] == '/') { sp->lno = lno; return; } } while (!db_get(sp, ++lno, 0, &p, &len)); } else if (len > 1 && p[0] == '/' && p[1] == '/') { F_SET(sp, SC_SCR_TOP); p += 2; len -= 2; do { for (; len > 1; --len, ++p) if (p[0] == '/' && p[1] == '/') { sp->lno = lno; return; } } while (!db_get(sp, ++lno, 0, &p, &len)); } } /* * file_m1 -- * First modification check routine. The :next, :prev, :rewind, :tag, * :tagpush, :tagpop, ^^ modifications check. * * PUBLIC: int file_m1(SCR *, int, int); */ int file_m1(SCR *sp, int force, int flags) { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * If the file has been modified, we'll want to write it back or * fail. If autowrite is set, we'll write it back automatically, * unless force is also set. Otherwise, we fail unless forced or * there's another open screen on this file. */ - if (F_ISSET(ep, F_MODIFIED)) + if (F_ISSET(ep, F_MODIFIED)) { if (O_ISSET(sp, O_AUTOWRITE)) { if (!force && file_aw(sp, flags)) return (1); } else if (ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "262|File modified since last complete write; write or use ! to override" : "263|File modified since last complete write; write or use :edit! to override"); return (1); } + } return (file_m3(sp, force)); } /* * file_m2 -- * Second modification check routine. The :edit, :quit, :recover * modifications check. * * PUBLIC: int file_m2(SCR *, int); */ int file_m2(SCR *sp, int force) { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * If the file has been modified, we'll want to fail, unless forced * or there's another open screen on this file. */ if (F_ISSET(ep, F_MODIFIED) && ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, "264|File modified since last complete write; write or use ! to override"); return (1); } return (file_m3(sp, force)); } /* * file_m3 -- * Third modification check routine. * * PUBLIC: int file_m3(SCR *, int); */ int file_m3(SCR *sp, int force) { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * Don't exit while in a temporary files if the file was ever modified. * The problem is that if the user does a ":wq", we write and quit, * unlinking the temporary file. Not what the user had in mind at all. * We permit writing to temporary files, so that user maps using file * system names work with temporary files. */ if (F_ISSET(sp->frp, FR_TMPEXIT) && ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, "265|File is a temporary; exit will discard modifications"); return (1); } return (0); } /* * file_aw -- * Autowrite routine. If modified, autowrite is set and the readonly bit * is not set, write the file. A routine so there's a place to put the * comment. * * PUBLIC: int file_aw(SCR *, int); */ int file_aw(SCR *sp, int flags) { if (!F_ISSET(sp->ep, F_MODIFIED)) return (0); if (!O_ISSET(sp, O_AUTOWRITE)) return (0); /* * !!! * Historic 4BSD vi attempted to write the file if autowrite was set, * regardless of the writeability of the file (as defined by the file * readonly flag). System V changed this as some point, not attempting * autowrite if the file was readonly. This feels like a bug fix to * me (e.g. the principle of least surprise is violated if readonly is * set and vi writes the file), so I'm compatible with System V. */ if (O_ISSET(sp, O_READONLY)) { msgq(sp, M_INFO, "266|File readonly, modifications not auto-written"); return (1); } return (file_write(sp, NULL, NULL, NULL, flags)); } /* * set_alt_name -- * Set the alternate pathname. * * Set the alternate pathname. It's a routine because I wanted some place * to hang this comment. The alternate pathname (normally referenced using * the special character '#' during file expansion and in the vi ^^ command) * is set by almost all ex commands that take file names as arguments. The * rules go something like this: * * 1: If any ex command takes a file name as an argument (except for the * :next command), the alternate pathname is set to that file name. * This excludes the command ":e" and ":w !command" as no file name * was specified. Note, historically, the :source command did not set * the alternate pathname. It does in nvi, for consistency. * * 2: However, if any ex command sets the current pathname, e.g. the * ":e file" or ":rew" commands succeed, then the alternate pathname * is set to the previous file's current pathname, if it had one. * This includes the ":file" command and excludes the ":e" command. * So, by rule #1 and rule #2, if ":edit foo" fails, the alternate * pathname will be "foo", if it succeeds, the alternate pathname will * be the previous current pathname. The ":e" command will not set * the alternate or current pathnames regardless. * * 3: However, if it's a read or write command with a file argument and * the current pathname has not yet been set, the file name becomes * the current pathname, and the alternate pathname is unchanged. * * If the user edits a temporary file, there may be times when there is no * alternative file name. A name argument of NULL turns it off. * * PUBLIC: void set_alt_name(SCR *, char *); */ void set_alt_name(SCR *sp, char *name) { free(sp->alt_name); if (name == NULL) sp->alt_name = NULL; else if ((sp->alt_name = strdup(name)) == NULL) msgq(sp, M_SYSERR, NULL); } /* * file_lock -- * Get an exclusive lock on a file. * * PUBLIC: lockr_t file_lock(SCR *, char *, int, int); */ lockr_t file_lock(SCR *sp, char *name, int fd, int iswrite) { if (!O_ISSET(sp, O_LOCKFILES)) return (LOCK_SUCCESS); /* * !!! * We need to distinguish a lock not being available for the file * from the file system not supporting locking. Flock is documented * as returning EWOULDBLOCK; add EAGAIN for good measure, and assume * they are the former. There's no portable way to do this. */ errno = 0; if (!flock(fd, LOCK_EX | LOCK_NB)) { fcntl(fd, F_SETFD, 1); return (LOCK_SUCCESS); } return (errno == EAGAIN #ifdef EWOULDBLOCK || errno == EWOULDBLOCK #endif ? LOCK_UNAVAIL : LOCK_FAILED); } Index: vendor/nvi/dist/common/key.c =================================================================== --- vendor/nvi/dist/common/key.c (revision 366306) +++ vendor/nvi/dist/common/key.c (revision 366307) @@ -1,839 +1,839 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" static int v_event_append(SCR *, EVENT *); static int v_event_grow(SCR *, int); static int v_key_cmp(const void *, const void *); static void v_keyval(SCR *, int, scr_keyval_t); static void v_sync(SCR *, int); /* * !!! * Historic vi always used: * * ^D: autoindent deletion * ^H: last character deletion * ^W: last word deletion * ^Q: quote the next character (if not used in flow control). * ^V: quote the next character * * regardless of the user's choices for these characters. The user's erase * and kill characters worked in addition to these characters. Nvi wires * down the above characters, but in addition permits the VEOF, VERASE, VKILL * and VWERASE characters described by the user's termios structure. * * Ex was not consistent with this scheme, as it historically ran in tty * cooked mode. This meant that the scroll command and autoindent erase * characters were mapped to the user's EOF character, and the character * and word deletion characters were the user's tty character and word * deletion characters. This implementation makes it all consistent, as * described above for vi. * * !!! * This means that all screens share a special key set. */ KEYLIST keylist[] = { {K_BACKSLASH, '\\'}, /* \ */ {K_CARAT, '^'}, /* ^ */ {K_CNTRLD, '\004'}, /* ^D */ {K_CNTRLR, '\022'}, /* ^R */ {K_CNTRLT, '\024'}, /* ^T */ {K_CNTRLZ, '\032'}, /* ^Z */ {K_COLON, ':'}, /* : */ {K_CR, '\r'}, /* \r */ {K_ESCAPE, '\033'}, /* ^[ */ {K_FORMFEED, '\f'}, /* \f */ {K_HEXCHAR, '\030'}, /* ^X */ {K_NL, '\n'}, /* \n */ {K_RIGHTBRACE, '}'}, /* } */ {K_RIGHTPAREN, ')'}, /* ) */ {K_TAB, '\t'}, /* \t */ {K_VERASE, '\b'}, /* \b */ {K_VKILL, '\025'}, /* ^U */ {K_VLNEXT, '\021'}, /* ^Q */ {K_VLNEXT, '\026'}, /* ^V */ {K_VWERASE, '\027'}, /* ^W */ {K_ZERO, '0'}, /* 0 */ #define ADDITIONAL_CHARACTERS 4 {K_NOTUSED, 0}, /* VEOF, VERASE, VKILL, VWERASE */ {K_NOTUSED, 0}, {K_NOTUSED, 0}, {K_NOTUSED, 0}, }; static int nkeylist = (sizeof(keylist) / sizeof(keylist[0])) - ADDITIONAL_CHARACTERS; /* * v_key_init -- * Initialize the special key lookup table. * * PUBLIC: int v_key_init(SCR *); */ int v_key_init(SCR *sp) { int ch; GS *gp; KEYLIST *kp; int cnt; gp = sp->gp; v_key_ilookup(sp); v_keyval(sp, K_CNTRLD, KEY_VEOF); v_keyval(sp, K_VERASE, KEY_VERASE); v_keyval(sp, K_VKILL, KEY_VKILL); v_keyval(sp, K_VWERASE, KEY_VWERASE); /* Sort the special key list. */ qsort(keylist, nkeylist, sizeof(keylist[0]), v_key_cmp); /* Initialize the fast lookup table. */ for (kp = keylist, cnt = nkeylist; cnt--; ++kp) gp->special_key[kp->ch] = kp->value; /* Find a non-printable character to use as a message separator. */ for (ch = 1; ch <= UCHAR_MAX; ++ch) if (!isprint(ch)) { gp->noprint = ch; break; } if (ch != gp->noprint) { msgq(sp, M_ERR, "079|No non-printable character found"); return (1); } return (0); } /* * v_keyval -- * Set key values. * * We've left some open slots in the keylist table, and if these values exist, * we put them into place. Note, they may reset (or duplicate) values already * in the table, so we check for that first. */ static void v_keyval(SCR *sp, int val, scr_keyval_t name) { KEYLIST *kp; CHAR_T ch; int dne; /* Get the key's value from the screen. */ if (sp->gp->scr_keyval(sp, name, &ch, &dne)) return; if (dne) return; /* Check for duplication. */ for (kp = keylist; kp->value != K_NOTUSED; ++kp) if (kp->ch == ch) { kp->value = val; return; } /* Add a new entry. */ if (kp->value == K_NOTUSED) { keylist[nkeylist].ch = ch; keylist[nkeylist].value = val; ++nkeylist; } } /* * v_key_ilookup -- * Build the fast-lookup key display array. * * PUBLIC: void v_key_ilookup(SCR *); */ void v_key_ilookup(SCR *sp) { UCHAR_T ch; char *p, *t; GS *gp; size_t len; for (gp = sp->gp, ch = 0;; ++ch) { for (p = gp->cname[ch].name, t = v_key_name(sp, ch), len = gp->cname[ch].len = sp->clen; len--;) *p++ = *t++; if (ch == MAX_FAST_KEY) break; } } /* * v_key_len -- * Return the length of the string that will display the key. * This routine is the backup for the KEY_LEN() macro. * * PUBLIC: size_t v_key_len(SCR *, ARG_CHAR_T); */ size_t v_key_len(SCR *sp, ARG_CHAR_T ch) { (void)v_key_name(sp, ch); return (sp->clen); } /* * v_key_name -- * Return the string that will display the key. This routine * is the backup for the KEY_NAME() macro. * * PUBLIC: char *v_key_name(SCR *, ARG_CHAR_T); */ char * v_key_name(SCR *sp, ARG_CHAR_T ach) { static const char hexdigit[] = "0123456789abcdef"; static const char octdigit[] = "01234567"; int ch; size_t len; char *chp; /* * Cache the last checked character. It won't be a problem * since nvi will rescan the mapping when settings changed. */ if (ach && sp->lastc == ach) return (sp->cname); sp->lastc = ach; #ifdef USE_WIDECHAR len = wctomb(sp->cname, ach); if (len > MB_CUR_MAX) #endif sp->cname[(len = 1)-1] = (u_char)ach; ch = (u_char)sp->cname[0]; sp->cname[len] = '\0'; /* See if the character was explicitly declared printable or not. */ if ((chp = O_STR(sp, O_PRINT)) != NULL) if (strstr(chp, sp->cname) != NULL) goto done; if ((chp = O_STR(sp, O_NOPRINT)) != NULL) if (strstr(chp, sp->cname) != NULL) goto nopr; /* * Historical (ARPA standard) mappings. Printable characters are left * alone. Control characters less than 0x20 are represented as '^' * followed by the character offset from the '@' character in the ASCII * character set. Del (0x7f) is represented as '^' followed by '?'. * * XXX * The following code depends on the current locale being identical to * the ASCII map from 0x40 to 0x5f (since 0x1f + 0x40 == 0x5f). I'm * told that this is a reasonable assumption... * * XXX * The code prints non-printable wide characters in 4 or 5 digits * Unicode escape sequences, so only supports plane 0 to 15. */ if (CAN_PRINT(sp, ach)) goto done; nopr: if (iscntrl(ch) && (ch < 0x20 || ch == 0x7f)) { sp->cname[0] = '^'; sp->cname[1] = ch == 0x7f ? '?' : '@' + ch; len = 2; goto done; } #ifdef USE_WIDECHAR if (INTISWIDE(ach)) { int uc = -1; if (!strcmp(codeset(), "UTF-8")) uc = decode_utf8(sp->cname); #ifdef USE_ICONV else { char buf[sizeof(sp->cname)] = ""; size_t left = sizeof(sp->cname); char *in = sp->cname; char *out = buf; iconv(sp->conv.id[IC_IE_TO_UTF16], (iconv_src_t)&in, &len, &out, &left); iconv(sp->conv.id[IC_IE_TO_UTF16], NULL, NULL, NULL, NULL); uc = decode_utf16(buf, 1); } #endif if (uc >= 0) { len = snprintf(sp->cname, sizeof(sp->cname), uc < 0x10000 ? "\\u%04x" : "\\U%05X", uc); goto done; } } #endif if (O_ISSET(sp, O_OCTAL)) { sp->cname[0] = '\\'; sp->cname[1] = octdigit[(ch & 0300) >> 6]; sp->cname[2] = octdigit[(ch & 070) >> 3]; sp->cname[3] = octdigit[ ch & 07 ]; } else { sp->cname[0] = '\\'; sp->cname[1] = 'x'; sp->cname[2] = hexdigit[(ch & 0xf0) >> 4]; sp->cname[3] = hexdigit[ ch & 0x0f ]; } len = 4; done: sp->cname[sp->clen = len] = '\0'; return (sp->cname); } /* * v_key_val -- * Fill in the value for a key. This routine is the backup * for the KEY_VAL() macro. * * PUBLIC: e_key_t v_key_val(SCR *, ARG_CHAR_T); */ e_key_t v_key_val(SCR *sp, ARG_CHAR_T ch) { KEYLIST k, *kp; k.ch = ch; kp = bsearch(&k, keylist, nkeylist, sizeof(keylist[0]), v_key_cmp); return (kp == NULL ? K_NOTUSED : kp->value); } /* * v_event_push -- * Push events/keys onto the front of the buffer. * * There is a single input buffer in ex/vi. Characters are put onto the * end of the buffer by the terminal input routines, and pushed onto the * front of the buffer by various other functions in ex/vi. Each key has * an associated flag value, which indicates if it has already been quoted, * and if it is the result of a mapping or an abbreviation. * * PUBLIC: int v_event_push(SCR *, EVENT *, CHAR_T *, size_t, u_int); */ int v_event_push(SCR *sp, EVENT *p_evp, /* Push event. */ CHAR_T *p_s, /* Push characters. */ size_t nitems, /* Number of items to push. */ u_int flags) /* CH_* flags. */ { EVENT *evp; GS *gp; size_t total; /* If we have room, stuff the items into the buffer. */ gp = sp->gp; if (nitems <= gp->i_next || (gp->i_event != NULL && gp->i_cnt == 0 && nitems <= gp->i_nelem)) { if (gp->i_cnt != 0) gp->i_next -= nitems; goto copy; } /* * If there are currently items in the queue, shift them up, * leaving some extra room. Get enough space plus a little * extra. */ #define TERM_PUSH_SHIFT 30 total = gp->i_cnt + gp->i_next + nitems + TERM_PUSH_SHIFT; if (total >= gp->i_nelem && v_event_grow(sp, MAX(total, 64))) return (1); if (gp->i_cnt) memmove(gp->i_event + TERM_PUSH_SHIFT + nitems, gp->i_event + gp->i_next, gp->i_cnt * sizeof(EVENT)); gp->i_next = TERM_PUSH_SHIFT; /* Put the new items into the queue. */ copy: gp->i_cnt += nitems; for (evp = gp->i_event + gp->i_next; nitems--; ++evp) { if (p_evp != NULL) *evp = *p_evp++; else { evp->e_event = E_CHARACTER; evp->e_c = *p_s++; evp->e_value = KEY_VAL(sp, evp->e_c); F_INIT(&evp->e_ch, flags); } } return (0); } /* * v_event_append -- * Append events onto the tail of the buffer. */ static int v_event_append(SCR *sp, EVENT *argp) { CHAR_T *s; /* Characters. */ EVENT *evp; GS *gp; size_t nevents; /* Number of events. */ /* Grow the buffer as necessary. */ nevents = argp->e_event == E_STRING ? argp->e_len : 1; gp = sp->gp; if (gp->i_event == NULL || nevents > gp->i_nelem - (gp->i_next + gp->i_cnt)) v_event_grow(sp, MAX(nevents, 64)); evp = gp->i_event + gp->i_next + gp->i_cnt; gp->i_cnt += nevents; /* Transform strings of characters into single events. */ if (argp->e_event == E_STRING) for (s = argp->e_csp; nevents--; ++evp) { evp->e_event = E_CHARACTER; evp->e_c = *s++; evp->e_value = KEY_VAL(sp, evp->e_c); evp->e_flags = 0; } else *evp = *argp; return (0); } /* Remove events from the queue. */ -#define QREM(len) { \ +#define QREM(len) do { \ if ((gp->i_cnt -= len) == 0) \ gp->i_next = 0; \ else \ gp->i_next += len; \ -} +} while (0) /* * v_event_get -- * Return the next event. * * !!! * The flag EC_NODIGIT probably needs some explanation. First, the idea of * mapping keys is that one or more keystrokes act like a function key. * What's going on is that vi is reading a number, and the character following * the number may or may not be mapped (EC_MAPCOMMAND). For example, if the * user is entering the z command, a valid command is "z40+", and we don't want * to map the '+', i.e. if '+' is mapped to "xxx", we don't want to change it * into "z40xxx". However, if the user enters "35x", we want to put all of the * characters through the mapping code. * * Historical practice is a bit muddled here. (Surprise!) It always permitted * mapping digits as long as they weren't the first character of the map, e.g. * ":map ^A1 xxx" was okay. It also permitted the mapping of the digits 1-9 * (the digit 0 was a special case as it doesn't indicate the start of a count) * as the first character of the map, but then ignored those mappings. While * it's probably stupid to map digits, vi isn't your mother. * * The way this works is that the EC_MAPNODIGIT causes term_key to return the * end-of-digit without "looking" at the next character, i.e. leaving it as the * user entered it. Presumably, the next term_key call will tell us how the * user wants it handled. * * There is one more complication. Users might map keys to digits, and, as * it's described above, the commands: * * :map g 1G * d2g * * would return the keys "d21G", when the user probably wanted * "d21G". So, if a map starts off with a digit we continue as * before, otherwise, we pretend we haven't mapped the character, and return * . * * Now that that's out of the way, let's talk about Energizer Bunny macros. * It's easy to create macros that expand to a loop, e.g. map x 3x. It's * fairly easy to detect this example, because it's all internal to term_key. * If we're expanding a macro and it gets big enough, at some point we can * assume it's looping and kill it. The examples that are tough are the ones * where the parser is involved, e.g. map x "ayyx"byy. We do an expansion * on 'x', and get "ayyx"byy. We then return the first 4 characters, and then * find the looping macro again. There is no way that we can detect this * without doing a full parse of the command, because the character that might * cause the loop (in this case 'x') may be a literal character, e.g. the map * map x "ayy"xyy"byy is perfectly legal and won't cause a loop. * * Historic vi tried to detect looping macros by disallowing obvious cases in * the map command, maps that that ended with the same letter as they started * (which wrongly disallowed "map x 'x"), and detecting macros that expanded * too many times before keys were returned to the command parser. It didn't * get many (most?) of the tricky cases right, however, and it was certainly * possible to create macros that ran forever. And, even if it did figure out * what was going on, the user was usually tossed into ex mode. Finally, any * changes made before vi realized that the macro was recursing were left in * place. We recover gracefully, but the only recourse the user has in an * infinite macro loop is to interrupt. * * !!! * It is historic practice that mapping characters to themselves as the first * part of the mapped string was legal, and did not cause infinite loops, i.e. * ":map! { {^M^T" and ":map n nz." were known to work. The initial, matching * characters were returned instead of being remapped. * * !!! * It is also historic practice that the macro "map ] ]]^" caused a single ] * keypress to behave as the command ]] (the ^ got the map past the vi check * for "tail recursion"). Conversely, the mapping "map n nn^" went recursive. * What happened was that, in the historic vi, maps were expanded as the keys * were retrieved, but not all at once and not centrally. So, the keypress ] * pushed ]]^ on the stack, and then the first ] from the stack was passed to * the ]] command code. The ]] command then retrieved a key without entering * the mapping code. This could bite us anytime a user has a map that depends * on secondary keys NOT being mapped. I can't see any possible way to make * this work in here without the complete abandonment of Rationality Itself. * * XXX * The final issue is recovery. It would be possible to undo all of the work * that was done by the macro if we entered a record into the log so that we * knew when the macro started, and, in fact, this might be worth doing at some * point. Given that this might make the log grow unacceptably (consider that * cursor keys are done with maps), for now we leave any changes made in place. * * PUBLIC: int v_event_get(SCR *, EVENT *, int, u_int32_t); */ int v_event_get(SCR *sp, EVENT *argp, int timeout, u_int32_t flags) { EVENT *evp, ev; GS *gp; SEQ *qp; int init_nomap, ispartial, istimeout, remap_cnt; gp = sp->gp; /* If simply checking for interrupts, argp may be NULL. */ if (argp == NULL) argp = &ev; retry: istimeout = remap_cnt = 0; /* * If the queue isn't empty and we're timing out for characters, * return immediately. */ if (gp->i_cnt != 0 && LF_ISSET(EC_TIMEOUT)) return (0); /* * If the queue is empty, we're checking for interrupts, or we're * timing out for characters, get more events. */ if (gp->i_cnt == 0 || LF_ISSET(EC_INTERRUPT | EC_TIMEOUT)) { /* * If we're reading new characters, check any scripting * windows for input. */ if (F_ISSET(gp, G_SCRWIN) && sscr_input(sp)) return (1); loop: if (gp->scr_event(sp, argp, LF_ISSET(EC_INTERRUPT | EC_QUOTED | EC_RAW), timeout)) return (1); switch (argp->e_event) { case E_ERR: case E_SIGHUP: case E_SIGTERM: /* * Fatal conditions cause the file to be synced to * disk immediately. */ v_sync(sp, RCV_ENDSESSION | RCV_PRESERVE | (argp->e_event == E_SIGTERM ? 0: RCV_EMAIL)); return (1); case E_TIMEOUT: istimeout = 1; break; case E_INTERRUPT: /* Set the global interrupt flag. */ F_SET(sp->gp, G_INTERRUPTED); /* * If the caller was interested in interrupts, return * immediately. */ if (LF_ISSET(EC_INTERRUPT)) return (0); goto append; default: append: if (v_event_append(sp, argp)) return (1); break; } } /* * If the caller was only interested in interrupts or timeouts, return * immediately. (We may have gotten characters, and that's okay, they * were queued up for later use.) */ if (LF_ISSET(EC_INTERRUPT | EC_TIMEOUT)) return (0); newmap: evp = &gp->i_event[gp->i_next]; /* * If the next event in the queue isn't a character event, return * it, we're done. */ if (evp->e_event != E_CHARACTER) { *argp = *evp; QREM(1); return (0); } /* * If the key isn't mappable because: * * + ... the timeout has expired * + ... it's not a mappable key * + ... neither the command or input map flags are set * + ... there are no maps that can apply to it * * return it forthwith. */ if (istimeout || F_ISSET(&evp->e_ch, CH_NOMAP) || !LF_ISSET(EC_MAPCOMMAND | EC_MAPINPUT) || ((evp->e_c & ~MAX_BIT_SEQ) == 0 && !bit_test(gp->seqb, evp->e_c))) goto nomap; /* Search the map. */ qp = seq_find(sp, NULL, evp, NULL, gp->i_cnt, LF_ISSET(EC_MAPCOMMAND) ? SEQ_COMMAND : SEQ_INPUT, &ispartial); /* * If get a partial match, get more characters and retry the map. * If time out without further characters, return the characters * unmapped. * * !!! * characters are a problem. Cursor keys start with * characters, so there's almost always a map in place that begins with * an character. If we timeout keys in the same way * that we timeout other keys, the user will get a noticeable pause as * they enter to terminate input mode. If key timeout is set * for a slow link, users will get an even longer pause. Nvi used to * simply timeout characters at 1/10th of a second, but this * loses over PPP links where the latency is greater than 100Ms. */ if (ispartial) { if (O_ISSET(sp, O_TIMEOUT)) timeout = (evp->e_value == K_ESCAPE ? O_VAL(sp, O_ESCAPETIME) : O_VAL(sp, O_KEYTIME)) * 100; else timeout = 0; goto loop; } /* If no map, return the character. */ if (qp == NULL) { nomap: if (!ISDIGIT(evp->e_c) && LF_ISSET(EC_MAPNODIGIT)) goto not_digit; *argp = *evp; QREM(1); return (0); } /* * If looking for the end of a digit string, and the first character * of the map is it, pretend we haven't seen the character. */ if (LF_ISSET(EC_MAPNODIGIT) && qp->output != NULL && !ISDIGIT(qp->output[0])) { not_digit: argp->e_c = CH_NOT_DIGIT; argp->e_value = K_NOTUSED; argp->e_event = E_CHARACTER; F_INIT(&argp->e_ch, 0); return (0); } /* Find out if the initial segments are identical. */ init_nomap = !e_memcmp(qp->output, &gp->i_event[gp->i_next], qp->ilen); /* Delete the mapped characters from the queue. */ QREM(qp->ilen); /* If keys mapped to nothing, go get more. */ if (qp->output == NULL) goto retry; /* If remapping characters... */ if (O_ISSET(sp, O_REMAP)) { /* * Periodically check for interrupts. Always check the first * time through, because it's possible to set up a map that * will return a character every time, but will expand to more, * e.g. "map! a aaaa" will always return a 'a', but we'll never * get anywhere useful. */ if ((++remap_cnt == 1 || remap_cnt % 10 == 0) && (gp->scr_event(sp, &ev, EC_INTERRUPT, 0) || ev.e_event == E_INTERRUPT)) { F_SET(sp->gp, G_INTERRUPTED); argp->e_event = E_INTERRUPT; return (0); } /* * If an initial part of the characters mapped, they are not * further remapped -- return the first one. Push the rest * of the characters, or all of the characters if no initial * part mapped, back on the queue. */ if (init_nomap) { if (v_event_push(sp, NULL, qp->output + qp->ilen, qp->olen - qp->ilen, CH_MAPPED)) return (1); if (v_event_push(sp, NULL, qp->output, qp->ilen, CH_NOMAP | CH_MAPPED)) return (1); evp = &gp->i_event[gp->i_next]; goto nomap; } if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED)) return (1); goto newmap; } /* Else, push the characters on the queue and return one. */ if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED | CH_NOMAP)) return (1); goto nomap; } /* * v_sync -- * Walk the screen lists, sync'ing files to their backup copies. */ static void v_sync(SCR *sp, int flags) { GS *gp; gp = sp->gp; TAILQ_FOREACH(sp, gp->dq, q) rcv_sync(sp, flags); TAILQ_FOREACH(sp, gp->hq, q) rcv_sync(sp, flags); } /* * v_event_err -- * Unexpected event. * * PUBLIC: void v_event_err(SCR *, EVENT *); */ void v_event_err(SCR *sp, EVENT *evp) { switch (evp->e_event) { case E_CHARACTER: msgq(sp, M_ERR, "276|Unexpected character event"); break; case E_EOF: msgq(sp, M_ERR, "277|Unexpected end-of-file event"); break; case E_INTERRUPT: msgq(sp, M_ERR, "279|Unexpected interrupt event"); break; case E_REPAINT: msgq(sp, M_ERR, "281|Unexpected repaint event"); break; case E_STRING: msgq(sp, M_ERR, "285|Unexpected string event"); break; case E_TIMEOUT: msgq(sp, M_ERR, "286|Unexpected timeout event"); break; case E_WRESIZE: msgq(sp, M_ERR, "316|Unexpected resize event"); break; /* * Theoretically, none of these can occur, as they're handled at the * top editor level. */ case E_ERR: case E_SIGHUP: case E_SIGTERM: default: abort(); } /* Free any allocated memory. */ free(evp->e_asp); } /* * v_event_flush -- * Flush any flagged keys, returning if any keys were flushed. * * PUBLIC: int v_event_flush(SCR *, u_int); */ int v_event_flush(SCR *sp, u_int flags) { GS *gp; int rval; for (rval = 0, gp = sp->gp; gp->i_cnt != 0 && F_ISSET(&gp->i_event[gp->i_next].e_ch, flags); rval = 1) QREM(1); return (rval); } /* * v_event_grow -- * Grow the terminal queue. */ static int v_event_grow(SCR *sp, int add) { GS *gp; size_t new_nelem, olen; gp = sp->gp; new_nelem = gp->i_nelem + add; olen = gp->i_nelem * sizeof(gp->i_event[0]); BINC_RET(sp, EVENT, gp->i_event, olen, new_nelem * sizeof(gp->i_event[0])); gp->i_nelem = olen / sizeof(gp->i_event[0]); return (0); } /* * v_key_cmp -- * Compare two keys for sorting. */ static int v_key_cmp(const void *ap, const void *bp) { return (((KEYLIST *)ap)->ch - ((KEYLIST *)bp)->ch); } Index: vendor/nvi/dist/common/log.c =================================================================== --- vendor/nvi/dist/common/log.c (revision 366306) +++ vendor/nvi/dist/common/log.c (revision 366307) @@ -1,736 +1,736 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" /* * The log consists of records, each containing a type byte and a variable * length byte string, as follows: * * LOG_CURSOR_INIT MARK * LOG_CURSOR_END MARK * LOG_LINE_APPEND recno_t char * * LOG_LINE_DELETE recno_t char * * LOG_LINE_INSERT recno_t char * * LOG_LINE_RESET_F recno_t char * * LOG_LINE_RESET_B recno_t char * * LOG_MARK LMARK * * We do before image physical logging. This means that the editor layer * MAY NOT modify records in place, even if simply deleting or overwriting * characters. Since the smallest unit of logging is a line, we're using * up lots of space. This may eventually have to be reduced, probably by * doing logical logging, which is a much cooler database phrase. * * The implementation of the historic vi 'u' command, using roll-forward and * roll-back, is simple. Each set of changes has a LOG_CURSOR_INIT record, * followed by a number of other records, followed by a LOG_CURSOR_END record. * LOG_LINE_RESET records come in pairs. The first is a LOG_LINE_RESET_B * record, and is the line before the change. The second is LOG_LINE_RESET_F, * and is the line after the change. Roll-back is done by backing up to the * first LOG_CURSOR_INIT record before a change. Roll-forward is done in a * similar fashion. * * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END * record for a line different from the current one. It should be noted that * this means that a subsequent 'u' command will make a change based on the * new position of the log's cursor. This is okay, and, in fact, historic vi * behaved that way. */ static int log_cursor1(SCR *, int); static void log_err(SCR *, char *, int); #if defined(DEBUG) && 0 static void log_trace(SCR *, char *, recno_t, u_char *); #endif static int apply_with(int (*)(SCR *, recno_t, CHAR_T *, size_t), SCR *, recno_t, u_char *, size_t); /* Try and restart the log on failure, i.e. if we run out of memory. */ -#define LOG_ERR { \ +#define LOG_ERR do { \ log_err(sp, __FILE__, __LINE__); \ return (1); \ -} +} while (0) /* offset of CHAR_T string in log needs to be aligned on some systems * because it is passed to db_set as a string */ typedef struct { char data[sizeof(u_char) /* type */ + sizeof(recno_t)]; CHAR_T str[1]; } log_t; #define CHAR_T_OFFSET ((char *)(((log_t*)0)->str) - (char *)0) /* * log_init -- * Initialize the logging subsystem. * * PUBLIC: int log_init(SCR *, EXF *); */ int log_init(SCR *sp, EXF *ep) { /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * * Initialize the buffer. The logging subsystem has its own * buffers because the global ones are almost by definition * going to be in use when the log runs. */ ep->l_lp = NULL; ep->l_len = 0; ep->l_cursor.lno = 1; /* XXX Any valid recno. */ ep->l_cursor.cno = 0; ep->l_high = ep->l_cur = 1; ep->log = dbopen(NULL, O_CREAT | O_NONBLOCK | O_RDWR, S_IRUSR | S_IWUSR, DB_RECNO, NULL); if (ep->log == NULL) { msgq(sp, M_SYSERR, "009|Log file"); F_SET(ep, F_NOLOG); return (1); } return (0); } /* * log_end -- * Close the logging subsystem. * * PUBLIC: int log_end(SCR *, EXF *); */ int log_end(SCR *sp, EXF *ep) { /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. */ if (ep->log != NULL) { (void)(ep->log->close)(ep->log); ep->log = NULL; } free(ep->l_lp); ep->l_lp = NULL; ep->l_len = 0; ep->l_cursor.lno = 1; /* XXX Any valid recno. */ ep->l_cursor.cno = 0; ep->l_high = ep->l_cur = 1; return (0); } /* * log_cursor -- * Log the current cursor position, starting an event. * * PUBLIC: int log_cursor(SCR *); */ int log_cursor(SCR *sp) { EXF *ep; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) return (0); /* * If any changes were made since the last cursor init, * put out the ending cursor record. */ if (ep->l_cursor.lno == OOBLNO) { ep->l_cursor.lno = sp->lno; ep->l_cursor.cno = sp->cno; return (log_cursor1(sp, LOG_CURSOR_END)); } ep->l_cursor.lno = sp->lno; ep->l_cursor.cno = sp->cno; return (0); } /* * log_cursor1 -- * Actually push a cursor record out. */ static int log_cursor1(SCR *sp, int type) { DBT data, key; EXF *ep; ep = sp->ep; BINC_RETC(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(MARK)); ep->l_lp[0] = type; memmove(ep->l_lp + sizeof(u_char), &ep->l_cursor, sizeof(MARK)); key.data = &ep->l_cur; key.size = sizeof(recno_t); data.data = ep->l_lp; data.size = sizeof(u_char) + sizeof(MARK); if (ep->log->put(ep->log, &key, &data, 0) == -1) LOG_ERR; #if defined(DEBUG) && 0 TRACE(sp, "%lu: %s: %u/%u\n", ep->l_cur, type == LOG_CURSOR_INIT ? "log_cursor_init" : "log_cursor_end", sp->lno, sp->cno); #endif /* Reset high water mark. */ ep->l_high = ++ep->l_cur; return (0); } /* * log_line -- * Log a line change. * * PUBLIC: int log_line(SCR *, recno_t, u_int); */ int log_line(SCR *sp, recno_t lno, u_int action) { DBT data, key; EXF *ep; size_t len; CHAR_T *lp; recno_t lcur; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) return (0); /* * XXX * * Kluge for vi. Clear the EXF undo flag so that the * next 'u' command does a roll-back, regardless. */ F_CLR(ep, F_UNDO); /* Put out one initial cursor record per set of changes. */ if (ep->l_cursor.lno != OOBLNO) { if (log_cursor1(sp, LOG_CURSOR_INIT)) return (1); ep->l_cursor.lno = OOBLNO; } /* * Put out the changes. If it's a LOG_LINE_RESET_B call, it's a * special case, avoid the caches. Also, if it fails and it's * line 1, it just means that the user started with an empty file, * so fake an empty length line. */ if (action == LOG_LINE_RESET_B) { if (db_get(sp, lno, DBG_NOCACHE, &lp, &len)) { if (lno != 1) { db_err(sp, lno); return (1); } len = 0; lp = L(""); } } else if (db_get(sp, lno, DBG_FATAL, &lp, &len)) return (1); BINC_RETC(sp, ep->l_lp, ep->l_len, len * sizeof(CHAR_T) + CHAR_T_OFFSET); ep->l_lp[0] = action; memmove(ep->l_lp + sizeof(u_char), &lno, sizeof(recno_t)); memmove(ep->l_lp + CHAR_T_OFFSET, lp, len * sizeof(CHAR_T)); lcur = ep->l_cur; key.data = &lcur; key.size = sizeof(recno_t); data.data = ep->l_lp; data.size = len * sizeof(CHAR_T) + CHAR_T_OFFSET; if (ep->log->put(ep->log, &key, &data, 0) == -1) LOG_ERR; #if defined(DEBUG) && 0 switch (action) { case LOG_LINE_APPEND: TRACE(sp, "%lu: log_line: append: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_DELETE: TRACE(sp, "%lu: log_line: delete: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_INSERT: TRACE(sp, "%lu: log_line: insert: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_RESET_F: TRACE(sp, "%lu: log_line: reset_f: %lu {%u}\n", ep->l_cur, lno, len); break; case LOG_LINE_RESET_B: TRACE(sp, "%lu: log_line: reset_b: %lu {%u}\n", ep->l_cur, lno, len); break; } #endif /* Reset high water mark. */ ep->l_high = ++ep->l_cur; return (0); } /* * log_mark -- * Log a mark position. For the log to work, we assume that there * aren't any operations that just put out a log record -- this * would mean that undo operations would only reset marks, and not * cause any other change. * * PUBLIC: int log_mark(SCR *, LMARK *); */ int log_mark(SCR *sp, LMARK *lmp) { DBT data, key; EXF *ep; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) return (0); /* Put out one initial cursor record per set of changes. */ if (ep->l_cursor.lno != OOBLNO) { if (log_cursor1(sp, LOG_CURSOR_INIT)) return (1); ep->l_cursor.lno = OOBLNO; } BINC_RETC(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(LMARK)); ep->l_lp[0] = LOG_MARK; memmove(ep->l_lp + sizeof(u_char), lmp, sizeof(LMARK)); key.data = &ep->l_cur; key.size = sizeof(recno_t); data.data = ep->l_lp; data.size = sizeof(u_char) + sizeof(LMARK); if (ep->log->put(ep->log, &key, &data, 0) == -1) LOG_ERR; #if defined(DEBUG) && 0 TRACE(sp, "%lu: mark %c: %lu/%u\n", ep->l_cur, lmp->name, lmp->lno, lmp->cno); #endif /* Reset high water mark. */ ep->l_high = ++ep->l_cur; return (0); } /* * Log_backward -- * Roll the log backward one operation. * * PUBLIC: int log_backward(SCR *, MARK *); */ int log_backward(SCR *sp, MARK *rp) { DBT key, data; EXF *ep; LMARK lm; MARK m; recno_t lno; int didop; u_char *p; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) { msgq(sp, M_ERR, "010|Logging not being performed, undo not possible"); return (1); } if (ep->l_cur == 1) { msgq(sp, M_BERR, "011|No changes to undo"); return (1); } F_SET(ep, F_NOLOG); /* Turn off logging. */ key.data = &ep->l_cur; /* Initialize db request. */ key.size = sizeof(recno_t); for (didop = 0;;) { --ep->l_cur; if (ep->log->get(ep->log, &key, &data, 0)) LOG_ERR; #if defined(DEBUG) && 0 log_trace(sp, "log_backward", ep->l_cur, data.data); #endif switch (*(p = (u_char *)data.data)) { case LOG_CURSOR_INIT: if (didop) { memmove(rp, p + sizeof(u_char), sizeof(MARK)); F_CLR(ep, F_NOLOG); return (0); } break; case LOG_CURSOR_END: break; case LOG_LINE_APPEND: case LOG_LINE_INSERT: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (db_delete(sp, lno)) goto err; ++sp->rptlines[L_DELETED]; break; case LOG_LINE_DELETE: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (apply_with(db_insert, sp, lno, p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET)) goto err; ++sp->rptlines[L_ADDED]; break; case LOG_LINE_RESET_F: break; case LOG_LINE_RESET_B: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (apply_with(db_set, sp, lno, p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } break; case LOG_MARK: didop = 1; memmove(&lm, p + sizeof(u_char), sizeof(LMARK)); m.lno = lm.lno; m.cno = lm.cno; if (mark_set(sp, lm.name, &m, 0)) goto err; break; default: abort(); } } err: F_CLR(ep, F_NOLOG); return (1); } /* * Log_setline -- * Reset the line to its original appearance. * * XXX * There's a bug in this code due to our not logging cursor movements * unless a change was made. If you do a change, move off the line, * then move back on and do a 'U', the line will be restored to the way * it was before the original change. * * PUBLIC: int log_setline(SCR *); */ int log_setline(SCR *sp) { DBT key, data; EXF *ep; LMARK lm; MARK m; recno_t lno; u_char *p; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) { msgq(sp, M_ERR, "012|Logging not being performed, undo not possible"); return (1); } if (ep->l_cur == 1) return (1); F_SET(ep, F_NOLOG); /* Turn off logging. */ key.data = &ep->l_cur; /* Initialize db request. */ key.size = sizeof(recno_t); for (;;) { --ep->l_cur; if (ep->log->get(ep->log, &key, &data, 0)) LOG_ERR; #if defined(DEBUG) && 0 log_trace(sp, "log_setline", ep->l_cur, data.data); #endif switch (*(p = (u_char *)data.data)) { case LOG_CURSOR_INIT: memmove(&m, p + sizeof(u_char), sizeof(MARK)); if (m.lno != sp->lno || ep->l_cur == 1) { F_CLR(ep, F_NOLOG); return (0); } break; case LOG_CURSOR_END: memmove(&m, p + sizeof(u_char), sizeof(MARK)); if (m.lno != sp->lno) { ++ep->l_cur; F_CLR(ep, F_NOLOG); return (0); } break; case LOG_LINE_APPEND: case LOG_LINE_INSERT: case LOG_LINE_DELETE: case LOG_LINE_RESET_F: break; case LOG_LINE_RESET_B: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (lno == sp->lno && apply_with(db_set, sp, lno, p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } case LOG_MARK: memmove(&lm, p + sizeof(u_char), sizeof(LMARK)); m.lno = lm.lno; m.cno = lm.cno; if (mark_set(sp, lm.name, &m, 0)) goto err; break; default: abort(); } } err: F_CLR(ep, F_NOLOG); return (1); } /* * Log_forward -- * Roll the log forward one operation. * * PUBLIC: int log_forward(SCR *, MARK *); */ int log_forward(SCR *sp, MARK *rp) { DBT key, data; EXF *ep; LMARK lm; MARK m; recno_t lno; int didop; u_char *p; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) { msgq(sp, M_ERR, "013|Logging not being performed, roll-forward not possible"); return (1); } if (ep->l_cur == ep->l_high) { msgq(sp, M_BERR, "014|No changes to re-do"); return (1); } F_SET(ep, F_NOLOG); /* Turn off logging. */ key.data = &ep->l_cur; /* Initialize db request. */ key.size = sizeof(recno_t); for (didop = 0;;) { ++ep->l_cur; if (ep->log->get(ep->log, &key, &data, 0)) LOG_ERR; #if defined(DEBUG) && 0 log_trace(sp, "log_forward", ep->l_cur, data.data); #endif switch (*(p = (u_char *)data.data)) { case LOG_CURSOR_END: if (didop) { ++ep->l_cur; memmove(rp, p + sizeof(u_char), sizeof(MARK)); F_CLR(ep, F_NOLOG); return (0); } break; case LOG_CURSOR_INIT: break; case LOG_LINE_APPEND: case LOG_LINE_INSERT: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (apply_with(db_insert, sp, lno, p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET)) goto err; ++sp->rptlines[L_ADDED]; break; case LOG_LINE_DELETE: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (db_delete(sp, lno)) goto err; ++sp->rptlines[L_DELETED]; break; case LOG_LINE_RESET_B: break; case LOG_LINE_RESET_F: didop = 1; memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); if (apply_with(db_set, sp, lno, p + CHAR_T_OFFSET, data.size - CHAR_T_OFFSET)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } break; case LOG_MARK: didop = 1; memmove(&lm, p + sizeof(u_char), sizeof(LMARK)); m.lno = lm.lno; m.cno = lm.cno; if (mark_set(sp, lm.name, &m, 0)) goto err; break; default: abort(); } } err: F_CLR(ep, F_NOLOG); return (1); } /* * log_err -- * Try and restart the log on failure, i.e. if we run out of memory. */ static void log_err(SCR *sp, char *file, int line) { EXF *ep; msgq(sp, M_SYSERR, "015|%s/%d: log put error", basename(file), line); ep = sp->ep; (void)ep->log->close(ep->log); if (!log_init(sp, ep)) msgq(sp, M_ERR, "267|Log restarted"); } #if defined(DEBUG) && 0 static void log_trace(SCR *sp, char *msg, recno_t rno, u_char *p) { LMARK lm; MARK m; recno_t lno; switch (*p) { case LOG_CURSOR_INIT: memmove(&m, p + sizeof(u_char), sizeof(MARK)); TRACE(sp, "%lu: %s: C_INIT: %u/%u\n", rno, msg, m.lno, m.cno); break; case LOG_CURSOR_END: memmove(&m, p + sizeof(u_char), sizeof(MARK)); TRACE(sp, "%lu: %s: C_END: %u/%u\n", rno, msg, m.lno, m.cno); break; case LOG_LINE_APPEND: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); TRACE(sp, "%lu: %s: APPEND: %lu\n", rno, msg, lno); break; case LOG_LINE_INSERT: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); TRACE(sp, "%lu: %s: INSERT: %lu\n", rno, msg, lno); break; case LOG_LINE_DELETE: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); TRACE(sp, "%lu: %s: DELETE: %lu\n", rno, msg, lno); break; case LOG_LINE_RESET_F: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); TRACE(sp, "%lu: %s: RESET_F: %lu\n", rno, msg, lno); break; case LOG_LINE_RESET_B: memmove(&lno, p + sizeof(u_char), sizeof(recno_t)); TRACE(sp, "%lu: %s: RESET_B: %lu\n", rno, msg, lno); break; case LOG_MARK: memmove(&lm, p + sizeof(u_char), sizeof(LMARK)); TRACE(sp, "%lu: %s: MARK: %u/%u\n", rno, msg, lm.lno, lm.cno); break; default: abort(); } } #endif /* * apply_with -- * Apply a realigned line from the log db to the file db. */ static int apply_with(int (*db_func)(SCR *, recno_t, CHAR_T *, size_t), SCR *sp, recno_t lno, u_char *p, size_t len) { #ifdef USE_WIDECHAR typedef unsigned long nword; static size_t blen; static nword *bp; nword *lp = (nword *)((uintptr_t)p / sizeof(nword) * sizeof(nword)); if (lp != (nword *)p) { int offl = ((uintptr_t)p - (uintptr_t)lp) << 3; int offr = (sizeof(nword) << 3) - offl; size_t i, cnt = (len + sizeof(nword) / 2) / sizeof(nword); if (len > blen) { blen = p2roundup(MAX(len, 512)); REALLOC(sp, bp, nword *, blen); if (bp == NULL) return (1); } for (i = 0; i < cnt; ++i) #if BYTE_ORDER == BIG_ENDIAN bp[i] = (lp[i] << offl) ^ (lp[i+1] >> offr); #else bp[i] = (lp[i] >> offl) ^ (lp[i+1] << offr); #endif p = (u_char *)bp; } #endif return db_func(sp, lno, (CHAR_T *)p, len / sizeof(CHAR_T)); } Index: vendor/nvi/dist/common/main.c =================================================================== --- vendor/nvi/dist/common/main.c (revision 366306) +++ vendor/nvi/dist/common/main.c (revision 366307) @@ -1,575 +1,576 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" #include "pathnames.h" static void attach(GS *); static int v_obsolete(char *[]); /* * editor -- * Main editor routine. * * PUBLIC: int editor(GS *, int, char *[]); */ int editor(GS *gp, int argc, char *argv[]) { extern int optind; extern char *optarg; const char *p; EVENT ev; FREF *frp; SCR *sp; size_t len; u_int flags; int ch, flagchk, lflag, secure, startup, readonly, rval, silent; - char *tag_f, *wsizearg, path[256]; - CHAR_T *w; + char *tag_f, *wsizearg; + CHAR_T *w, path[256]; size_t wlen; /* Initialize the busy routine, if not defined by the screen. */ if (gp->scr_busy == NULL) gp->scr_busy = vs_busy; /* Initialize the message routine, if not defined by the screen. */ if (gp->scr_msg == NULL) gp->scr_msg = vs_msg; gp->catd = (nl_catd)-1; /* Common global structure initialization. */ TAILQ_INIT(gp->dq); TAILQ_INIT(gp->hq); SLIST_INIT(gp->ecq); SLIST_INSERT_HEAD(gp->ecq, &gp->excmd, q); gp->noprint = DEFAULT_NOPRINT; /* Structures shared by screens so stored in the GS structure. */ TAILQ_INIT(gp->frefq); TAILQ_INIT(gp->dcb_store.textq); SLIST_INIT(gp->cutq); SLIST_INIT(gp->seqq); /* Set initial screen type and mode based on the program name. */ readonly = 0; if (!strcmp(getprogname(), "ex") || !strcmp(getprogname(), "nex")) LF_INIT(SC_EX); else { /* Nview, view are readonly. */ if (!strcmp(getprogname(), "nview") || !strcmp(getprogname(), "view")) readonly = 1; /* Vi is the default. */ LF_INIT(SC_VI); } /* Convert old-style arguments into new-style ones. */ if (v_obsolete(argv)) return (1); /* Parse the arguments. */ flagchk = '\0'; tag_f = wsizearg = NULL; lflag = secure = silent = 0; startup = 1; /* Set the file snapshot flag. */ F_SET(gp, G_SNAPSHOT); #ifdef DEBUG while ((ch = getopt(argc, argv, "c:D:eFlRrSsT:t:vw:")) != EOF) #else while ((ch = getopt(argc, argv, "c:eFlRrSst:vw:")) != EOF) #endif switch (ch) { case 'c': /* Run the command. */ /* * XXX * We should support multiple -c options. */ if (gp->c_option != NULL) { warnx("only one -c command may be specified."); return (1); } gp->c_option = optarg; break; #ifdef DEBUG case 'D': switch (optarg[0]) { case 's': startup = 0; break; case 'w': attach(gp); break; default: warnx("usage: -D requires s or w argument."); return (1); } break; #endif case 'e': /* Ex mode. */ LF_CLR(SC_VI); LF_SET(SC_EX); break; case 'F': /* No snapshot. */ F_CLR(gp, G_SNAPSHOT); break; case 'l': /* Set lisp, showmatch options. */ lflag = 1; break; case 'R': /* Readonly. */ readonly = 1; break; case 'r': /* Recover. */ if (flagchk == 't') { warnx("only one of -r and -t may be specified."); return (1); } flagchk = 'r'; break; case 'S': secure = 1; break; case 's': silent = 1; break; #ifdef DEBUG case 'T': /* Trace. */ if ((gp->tracefp = fopen(optarg, "w")) == NULL) { warn("%s", optarg); goto err; } (void)fprintf(gp->tracefp, "\n===\ntrace: open %s\n", optarg); break; #endif case 't': /* Tag. */ if (flagchk == 'r') { warnx("only one of -r and -t may be specified."); return (1); } if (flagchk == 't') { warnx("only one tag file may be specified."); return (1); } flagchk = 't'; tag_f = optarg; break; case 'v': /* Vi mode. */ LF_CLR(SC_EX); LF_SET(SC_VI); break; case 'w': wsizearg = optarg; break; case '?': default: (void)gp->scr_usage(); return (1); } argc -= optind; argv += optind; /* * -s option is only meaningful to ex. * * If not reading from a terminal, it's like -s was specified. */ if (silent && !LF_ISSET(SC_EX)) { warnx("-s option is only applicable to ex."); goto err; } if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED)) silent = 1; /* * Build and initialize the first/current screen. This is a bit * tricky. If an error is returned, we may or may not have a * screen structure. If we have a screen structure, put it on a * display queue so that the error messages get displayed. * * !!! * Everything we do until we go interactive is done in ex mode. */ if (screen_init(gp, NULL, &sp)) { if (sp != NULL) TAILQ_INSERT_HEAD(gp->dq, sp, q); goto err; } F_SET(sp, SC_EX); TAILQ_INSERT_HEAD(gp->dq, sp, q); if (v_key_init(sp)) /* Special key initialization. */ goto err; { int oargs[5], *oargp = oargs; if (lflag) { /* Command-line options. */ *oargp++ = O_LISP; *oargp++ = O_SHOWMATCH; } if (readonly) *oargp++ = O_READONLY; if (secure) *oargp++ = O_SECURE; *oargp = -1; /* Options initialization. */ if (opts_init(sp, oargs)) goto err; } if (wsizearg != NULL) { ARGS *av[2], a, b; - (void)snprintf(path, sizeof(path), "window=%s", wsizearg); + (void)SPRINTF(path, SIZE(path), L("window=%s"), wsizearg); a.bp = (CHAR_T *)path; - a.len = strlen(path); + a.len = SIZE(path); b.bp = NULL; b.len = 0; av[0] = &a; av[1] = &b; (void)opts_set(sp, av, NULL); } if (silent) { /* Ex batch mode option values. */ O_CLR(sp, O_AUTOPRINT); O_CLR(sp, O_PROMPT); O_CLR(sp, O_VERBOSE); O_CLR(sp, O_WARN); F_SET(sp, SC_EX_SILENT); } sp->rows = O_VAL(sp, O_LINES); /* Make ex formatting work. */ sp->cols = O_VAL(sp, O_COLUMNS); if (!silent && startup) { /* Read EXINIT, exrc files. */ if (ex_exrc(sp)) goto err; if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } /* * List recovery files if -r specified without file arguments. * Note, options must be initialized and startup information * read before doing this. */ if (flagchk == 'r' && argv[0] == NULL) { if (rcv_list(sp)) goto err; if (screen_end(sp)) goto err; goto done; } /* * !!! * Initialize the default ^D, ^U scrolling value here, after the * user has had every opportunity to set the window option. * * It's historic practice that changing the value of the window * option did not alter the default scrolling value, only giving * a count to ^D/^U did that. */ sp->defscroll = (O_VAL(sp, O_WINDOW) + 1) / 2; /* * If we don't have a command-line option, switch into the right * editor now, so that we position default files correctly, and * so that any tags file file-already-locked messages are in the * vi screen, not the ex screen. * * XXX * If we have a command-line option, the error message can end * up in the wrong place, but I think that the combination is * unlikely. */ if (gp->c_option == NULL) { F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI)); } /* Open a tag file if specified. */ if (tag_f != NULL) { CHAR2INT(sp, tag_f, strlen(tag_f) + 1, w, wlen); if (ex_tag_first(sp, w)) goto err; } /* * Append any remaining arguments as file names. Files are recovery * files if -r specified. If the tag option or ex startup commands * loaded a file, then any file arguments are going to come after it. */ if (*argv != NULL) { if (sp->frp != NULL) { /* Cheat -- we know we have an extra argv slot. */ *--argv = strdup(sp->frp->name); if (*argv == NULL) { warn(NULL); goto err; } } sp->argv = sp->cargv = argv; F_SET(sp, SC_ARGNOFREE); if (flagchk == 'r') F_SET(sp, SC_ARGRECOVER); } /* * If the ex startup commands and or/the tag option haven't already * created a file, create one. If no command-line files were given, * use a temporary file. */ if (sp->frp == NULL) { if (sp->argv == NULL) { if ((frp = file_add(sp, NULL)) == NULL) goto err; } else { if ((frp = file_add(sp, sp->argv[0])) == NULL) goto err; if (F_ISSET(sp, SC_ARGRECOVER)) F_SET(frp, FR_RECOVER); } if (file_init(sp, frp, NULL, 0)) goto err; if (EXCMD_RUNNING(gp)) { (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } } /* * Check to see if we need to wait for ex. If SC_SCR_EX is set, ex * was forced to initialize the screen during startup. We'd like to * wait for a single character from the user, but we can't because * we're not in raw mode. We can't switch to raw mode because the * vi initialization will switch to xterm's alternate screen, causing * us to lose the messages we're pausing to make sure the user read. * So, wait for a complete line. */ if (F_ISSET(sp, SC_SCR_EX)) { p = msg_cmsg(sp, CMSG_CONT_R, &len); (void)write(STDOUT_FILENO, p, len); for (;;) { if (v_event_get(sp, &ev, 0, 0)) goto err; if (ev.e_event == E_INTERRUPT || (ev.e_event == E_CHARACTER && (ev.e_value == K_CR || ev.e_value == K_NL))) break; (void)gp->scr_bell(sp); } } /* Switch into the right editor, regardless. */ F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI) | SC_STATUS_CNT); /* * Main edit loop. Vi handles split screens itself, we only return * here when switching editor modes or restarting the screen. */ while (sp != NULL) if (F_ISSET(sp, SC_EX) ? ex(&sp) : vi(&sp)) goto err; done: rval = 0; if (0) err: rval = 1; /* Clean out the global structure. */ v_end(gp); return (rval); } /* * v_end -- * End the program, discarding screens and most of the global area. * * PUBLIC: void v_end(GS *); */ void v_end(gp) GS *gp; { MSGS *mp; SCR *sp; /* If there are any remaining screens, kill them off. */ if (gp->ccl_sp != NULL) { (void)file_end(gp->ccl_sp, NULL, 1); (void)screen_end(gp->ccl_sp); } while ((sp = TAILQ_FIRST(gp->dq)) != NULL) (void)screen_end(sp); while ((sp = TAILQ_FIRST(gp->hq)) != NULL) (void)screen_end(sp); #if defined(DEBUG) || defined(PURIFY) { FREF *frp; /* Free FREF's. */ while ((frp = TAILQ_FIRST(gp->frefq)) != NULL) { TAILQ_REMOVE(gp->frefq, frp, q); free(frp->name); free(frp->tname); free(frp); } } /* Free key input queue. */ free(gp->i_event); /* Free cut buffers. */ cut_close(gp); /* Free map sequences. */ seq_close(gp); /* Free default buffer storage. */ (void)text_lfree(gp->dcb_store.textq); /* Close message catalogs. */ msg_close(gp); #endif /* Ring the bell if scheduled. */ if (F_ISSET(gp, G_BELLSCHED)) (void)fprintf(stderr, "\07"); /* \a */ /* * Flush any remaining messages. If a message is here, it's almost * certainly the message about the event that killed us (although * it's possible that the user is sourcing a file that exits from the * editor). */ while ((mp = SLIST_FIRST(gp->msgq)) != NULL) { (void)fprintf(stderr, "%s%.*s", mp->mtype == M_ERR ? "ex/vi: " : "", (int)mp->len, mp->buf); SLIST_REMOVE_HEAD(gp->msgq, q); #if defined(DEBUG) || defined(PURIFY) free(mp->buf); free(mp); #endif } #if defined(DEBUG) || defined(PURIFY) /* Free any temporary space. */ free(gp->tmp_bp); #if defined(DEBUG) /* Close debugging file descriptor. */ if (gp->tracefp != NULL) (void)fclose(gp->tracefp); #endif #endif } /* * v_obsolete -- * Convert historic arguments into something getopt(3) will like. */ static int v_obsolete(char *argv[]) { size_t len; char *p; /* * Translate old style arguments into something getopt will like. * Make sure it's not text space memory, because ex modifies the * strings. * Change "+" into "-c$". * Change "+" into "-c". * Change "-" into "-s" * The c, T, t and w options take arguments so they can't be * special arguments. * * Stop if we find "--" as an argument, the user may want to edit * a file named "+foo". */ while (*++argv && strcmp(argv[0], "--")) if (argv[0][0] == '+') { if (argv[0][1] == '\0') { argv[0] = strdup("-c$"); if (argv[0] == NULL) goto nomem; } else { p = argv[0]; len = strlen(argv[0]); argv[0] = malloc(len + 2); if (argv[0] == NULL) goto nomem; argv[0][0] = '-'; argv[0][1] = 'c'; (void)strlcpy(argv[0] + 2, p + 1, len); } - } else if (argv[0][0] == '-') + } else if (argv[0][0] == '-') { if (argv[0][1] == '\0') { argv[0] = strdup("-s"); if (argv[0] == NULL) { nomem: warn(NULL); return (1); } } else if ((argv[0][1] == 'c' || argv[0][1] == 'T' || argv[0][1] == 't' || argv[0][1] == 'w') && argv[0][2] == '\0') ++argv; + } return (0); } #ifdef DEBUG static void attach(GS *gp) { int fd; char ch; if ((fd = open(_PATH_TTY, O_RDONLY, 0)) < 0) { warn("%s", _PATH_TTY); return; } (void)printf("process %lu waiting, enter to continue: ", (u_long)getpid()); (void)fflush(stdout); do { if (read(fd, &ch, 1) != 1) { (void)close(fd); return; } } while (ch != '\n' && ch != '\r'); (void)close(fd); } #endif Index: vendor/nvi/dist/common/mark.c =================================================================== --- vendor/nvi/dist/common/mark.c (revision 366306) +++ vendor/nvi/dist/common/mark.c (revision 366307) @@ -1,256 +1,257 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include "common.h" static LMARK *mark_find(SCR *, ARG_CHAR_T); /* * Marks are maintained in a key sorted singly linked list. We can't * use arrays because we have no idea how big an index key could be. * The underlying assumption is that users don't have more than, say, * 10 marks at any one time, so this will be is fast enough. * * Marks are fixed, and modifications to the line don't update the mark's * position in the line. This can be hard. If you add text to the line, * place a mark in that text, undo the addition and use ` to move to the * mark, the location will have disappeared. It's tempting to try to adjust * the mark with the changes in the line, but this is hard to do, especially * if we've given the line to v_ntext.c:v_ntext() for editing. Historic vi * would move to the first non-blank on the line when the mark location was * past the end of the line. This can be complicated by deleting to a mark * that has disappeared using the ` command. Historic vi treated this as * a line-mode motion and deleted the line. This implementation complains to * the user. * * In historic vi, marks returned if the operation was undone, unless the * mark had been subsequently reset. Tricky. This is hard to start with, * but in the presence of repeated undo it gets nasty. When a line is * deleted, we delete (and log) any marks on that line. An undo will create * the mark. Any mark creations are noted as to whether the user created * it or if it was created by an undo. The former cannot be reset by another * undo, but the latter may. * * All of these routines translate ABSMARK2 to ABSMARK1. Setting either of * the absolute mark locations sets both, so that "m'" and "m`" work like * they, ah, for lack of a better word, "should". */ /* * mark_init -- * Set up the marks. * * PUBLIC: int mark_init(SCR *, EXF *); */ int mark_init(SCR *sp, EXF *ep) { /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * * Set up the marks. */ SLIST_INIT(ep->marks); return (0); } /* * mark_end -- * Free up the marks. * * PUBLIC: int mark_end(SCR *, EXF *); */ int mark_end(SCR *sp, EXF *ep) { LMARK *lmp; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. */ while ((lmp = SLIST_FIRST(ep->marks)) != NULL) { SLIST_REMOVE_HEAD(ep->marks, q); free(lmp); } return (0); } /* * mark_get -- * Get the location referenced by a mark. * * PUBLIC: int mark_get(SCR *, ARG_CHAR_T, MARK *, mtype_t); */ int mark_get(SCR *sp, ARG_CHAR_T key, MARK *mp, mtype_t mtype) { LMARK *lmp; if (key == ABSMARK2) key = ABSMARK1; lmp = mark_find(sp, key); if (lmp == NULL || lmp->name != key) { msgq(sp, mtype, "017|Mark %s: not set", KEY_NAME(sp, key)); return (1); } if (F_ISSET(lmp, MARK_DELETED)) { msgq(sp, mtype, "018|Mark %s: the line was deleted", KEY_NAME(sp, key)); return (1); } /* * !!! * The absolute mark is initialized to lno 1/cno 0, and historically * you could use it in an empty file. Make such a mark always work. */ if ((lmp->lno != 1 || lmp->cno != 0) && !db_exist(sp, lmp->lno)) { msgq(sp, mtype, "019|Mark %s: cursor position no longer exists", KEY_NAME(sp, key)); return (1); } mp->lno = lmp->lno; mp->cno = lmp->cno; return (0); } /* * mark_set -- * Set the location referenced by a mark. * * PUBLIC: int mark_set(SCR *, ARG_CHAR_T, MARK *, int); */ int mark_set(SCR *sp, ARG_CHAR_T key, MARK *value, int userset) { LMARK *lmp, *lmt; if (key == ABSMARK2) key = ABSMARK1; /* * The rules are simple. If the user is setting a mark (if it's a * new mark this is always true), it always happens. If not, it's * an undo, and we set it if it's not already set or if it was set * by a previous undo. */ lmp = mark_find(sp, key); if (lmp == NULL || lmp->name != key) { MALLOC_RET(sp, lmt, sizeof(LMARK)); if (lmp == NULL) { SLIST_INSERT_HEAD(sp->ep->marks, lmt, q); } else SLIST_INSERT_AFTER(lmp, lmt, q); lmp = lmt; } else if (!userset && !F_ISSET(lmp, MARK_DELETED) && F_ISSET(lmp, MARK_USERSET)) return (0); lmp->lno = value->lno; lmp->cno = value->cno; lmp->name = key; lmp->flags = userset ? MARK_USERSET : 0; return (0); } /* * mark_find -- * Find the requested mark, or, the slot immediately before * where it would go. */ static LMARK * mark_find(SCR *sp, ARG_CHAR_T key) { LMARK *lmp, *lastlmp = NULL; /* * Return the requested mark or the slot immediately before * where it should go. */ SLIST_FOREACH(lmp, sp->ep->marks, q) { if (lmp->name >= key) return (lmp->name == key ? lmp : lastlmp); lastlmp = lmp; } return (lastlmp); } /* * mark_insdel -- * Update the marks based on an insertion or deletion. * * PUBLIC: int mark_insdel(SCR *, lnop_t, recno_t); */ int mark_insdel(SCR *sp, lnop_t op, recno_t lno) { LMARK *lmp; recno_t lline; switch (op) { case LINE_APPEND: /* All insert/append operations are done as inserts. */ abort(); case LINE_DELETE: SLIST_FOREACH(lmp, sp->ep->marks, q) - if (lmp->lno >= lno) + if (lmp->lno >= lno) { if (lmp->lno == lno) { F_SET(lmp, MARK_DELETED); (void)log_mark(sp, lmp); } else --lmp->lno; + } break; case LINE_INSERT: /* * XXX * Very nasty special case. If the file was empty, then we're * adding the first line, which is a replacement. So, we don't * modify the marks. This is a hack to make: * * mz:r!echo foo'z * * work, i.e. historically you could mark the "line" in an empty * file and replace it, and continue to use the mark. Insane, * well, yes, I know, but someone complained. * * Check for line #2 before going to the end of the file. */ if (!db_exist(sp, 2)) { if (db_last(sp, &lline)) return (1); if (lline == 1) return (0); } SLIST_FOREACH(lmp, sp->ep->marks, q) if (lmp->lno >= lno) ++lmp->lno; break; case LINE_RESET: break; } return (0); } Index: vendor/nvi/dist/common/mem.h =================================================================== --- vendor/nvi/dist/common/mem.h (revision 366306) +++ vendor/nvi/dist/common/mem.h (revision 366307) @@ -1,217 +1,217 @@ /*- * 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. */ #ifdef DEBUG #define CHECK_TYPE(type, var) \ type L__lp __attribute__((unused)) = var; #else #define CHECK_TYPE(type, var) #endif /* Increase the size of a malloc'd buffer. Two versions, one that * returns, one that jumps to an error label. */ -#define BINC_GOTO(sp, type, lp, llen, nlen) { \ +#define BINC_GOTO(sp, type, lp, llen, nlen) do { \ CHECK_TYPE(type *, lp) \ void *L__bincp; \ if ((nlen) > llen) { \ if ((L__bincp = binc(sp, lp, &(llen), nlen)) == NULL) \ goto alloc_err; \ /* \ * !!! \ * Possible pointer conversion. \ */ \ lp = L__bincp; \ } \ -} +} while (0) #define BINC_GOTOC(sp, lp, llen, nlen) \ BINC_GOTO(sp, char, lp, llen, nlen) #define BINC_GOTOW(sp, lp, llen, nlen) \ BINC_GOTO(sp, CHAR_T, lp, llen, (nlen) * sizeof(CHAR_T)) -#define BINC_RET(sp, type, lp, llen, nlen) { \ +#define BINC_RET(sp, type, lp, llen, nlen) do { \ CHECK_TYPE(type *, lp) \ void *L__bincp; \ if ((nlen) > llen) { \ if ((L__bincp = binc(sp, lp, &(llen), nlen)) == NULL) \ return (1); \ /* \ * !!! \ * Possible pointer conversion. \ */ \ lp = L__bincp; \ } \ -} +} while (0) #define BINC_RETC(sp, lp, llen, nlen) \ BINC_RET(sp, char, lp, llen, nlen) #define BINC_RETW(sp, lp, llen, nlen) \ BINC_RET(sp, CHAR_T, lp, llen, (nlen) * sizeof(CHAR_T)) /* * Get some temporary space, preferably from the global temporary buffer, * from a malloc'd buffer otherwise. Two versions, one that returns, one * that jumps to an error label. */ -#define GET_SPACE_GOTO(sp, type, bp, blen, nlen) { \ +#define GET_SPACE_GOTO(sp, type, bp, blen, nlen) do { \ CHECK_TYPE(type *, bp) \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp == NULL || F_ISSET(L__gp, G_TMP_INUSE)) { \ bp = NULL; \ blen = 0; \ BINC_GOTO(sp, type, bp, blen, nlen); \ } else { \ BINC_GOTOC(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \ bp = (type *) L__gp->tmp_bp; \ blen = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } \ -} +} while (0) #define GET_SPACE_GOTOC(sp, bp, blen, nlen) \ GET_SPACE_GOTO(sp, char, bp, blen, nlen) #define GET_SPACE_GOTOW(sp, bp, blen, nlen) \ GET_SPACE_GOTO(sp, CHAR_T, bp, blen, (nlen) * sizeof(CHAR_T)) -#define GET_SPACE_RET(sp, type, bp, blen, nlen) { \ +#define GET_SPACE_RET(sp, type, bp, blen, nlen) do { \ CHECK_TYPE(type *, bp) \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp == NULL || F_ISSET(L__gp, G_TMP_INUSE)) { \ bp = NULL; \ blen = 0; \ BINC_RET(sp, type, bp, blen, nlen); \ } else { \ BINC_RETC(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \ bp = (type *) L__gp->tmp_bp; \ blen = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } \ -} +} while (0) #define GET_SPACE_RETC(sp, bp, blen, nlen) \ GET_SPACE_RET(sp, char, bp, blen, nlen) #define GET_SPACE_RETW(sp, bp, blen, nlen) \ GET_SPACE_RET(sp, CHAR_T, bp, blen, (nlen) * sizeof(CHAR_T)) /* * Add space to a GET_SPACE returned buffer. Two versions, one that * returns, one that jumps to an error label. */ -#define ADD_SPACE_GOTO(sp, type, bp, blen, nlen) { \ +#define ADD_SPACE_GOTO(sp, type, bp, blen, nlen) do { \ CHECK_TYPE(type *, bp) \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp == NULL || bp == (type *)L__gp->tmp_bp) { \ F_CLR(L__gp, G_TMP_INUSE); \ BINC_GOTOC(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \ bp = (type *) L__gp->tmp_bp; \ blen = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } else \ BINC_GOTO(sp, type, bp, blen, nlen); \ -} +} while (0) #define ADD_SPACE_GOTOC(sp, bp, blen, nlen) \ ADD_SPACE_GOTO(sp, char, bp, blen, nlen) #define ADD_SPACE_GOTOW(sp, bp, blen, nlen) \ ADD_SPACE_GOTO(sp, CHAR_T, bp, blen, (nlen) * sizeof(CHAR_T)) -#define ADD_SPACE_RET(sp, type, bp, blen, nlen) { \ +#define ADD_SPACE_RET(sp, type, bp, blen, nlen) do { \ CHECK_TYPE(type *, bp) \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp == NULL || bp == (type *)L__gp->tmp_bp) { \ F_CLR(L__gp, G_TMP_INUSE); \ BINC_RETC(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \ bp = (type *) L__gp->tmp_bp; \ blen = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } else \ BINC_RET(sp, type, bp, blen, nlen); \ -} +} while (0) #define ADD_SPACE_RETC(sp, bp, blen, nlen) \ ADD_SPACE_RET(sp, char, bp, blen, nlen) #define ADD_SPACE_RETW(sp, bp, blen, nlen) \ ADD_SPACE_RET(sp, CHAR_T, bp, blen, (nlen) * sizeof(CHAR_T)) /* Free a GET_SPACE returned buffer. */ -#define FREE_SPACE(sp, bp, blen) { \ +#define FREE_SPACE(sp, bp, blen) do { \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp != NULL && bp == L__gp->tmp_bp) \ F_CLR(L__gp, G_TMP_INUSE); \ else \ free(bp); \ -} -#define FREE_SPACEW(sp, bp, blen) { \ +} while (0) +#define FREE_SPACEW(sp, bp, blen) do { \ CHECK_TYPE(CHAR_T *, bp) \ FREE_SPACE(sp, (char *)bp, blen); \ -} +} while (0) /* * Malloc a buffer, casting the return pointer. Various versions. */ -#define CALLOC(sp, p, nmemb, size) { \ +#define CALLOC(sp, p, nmemb, size) do { \ if ((p = calloc(nmemb, size)) == NULL) \ msgq(sp, M_SYSERR, NULL); \ -} -#define CALLOC_GOTO(sp, p, nmemb, size) { \ +} while (0) +#define CALLOC_GOTO(sp, p, nmemb, size) do { \ if ((p = calloc(nmemb, size)) == NULL) \ goto alloc_err; \ -} -#define CALLOC_RET(sp, p, nmemb, size) { \ +} while (0) +#define CALLOC_RET(sp, p, nmemb, size) do { \ if ((p = calloc(nmemb, size)) == NULL) { \ msgq(sp, M_SYSERR, NULL); \ return (1); \ } \ -} +} while (0) -#define MALLOC(sp, p, size) { \ +#define MALLOC(sp, p, size) do { \ if ((p = malloc(size)) == NULL) \ msgq(sp, M_SYSERR, NULL); \ -} -#define MALLOC_GOTO(sp, p, size) { \ +} while (0) +#define MALLOC_GOTO(sp, p, size) do { \ if ((p = malloc(size)) == NULL) \ goto alloc_err; \ -} -#define MALLOC_RET(sp, p, size) { \ +} while (0) +#define MALLOC_RET(sp, p, size) do { \ if ((p = malloc(size)) == NULL) { \ msgq(sp, M_SYSERR, NULL); \ return (1); \ } \ -} +} while (0) /* * Resize a buffer, free any already held memory if we can't get more. * FreeBSD's reallocf(3) does the same thing, but it's not portable yet. */ -#define REALLOC(sp, p, cast, size) { \ +#define REALLOC(sp, p, cast, size) do { \ cast newp; \ if ((newp = realloc(p, size)) == NULL) { \ free(p); \ msgq(sp, M_SYSERR, NULL); \ } \ p = newp; \ -} +} while (0) /* * p2roundup -- * Get next power of 2; convenient for realloc. * * Reference: FreeBSD /usr/src/lib/libc/stdio/getdelim.c */ static __inline size_t p2roundup(size_t n) { n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; #if SIZE_T_MAX > 0xffffffffU n |= n >> 32; #endif n++; return (n); } /* Additional TAILQ helper. */ #define TAILQ_ENTRY_ISVALID(elm, field) \ ((elm)->field.tqe_prev != NULL) Index: vendor/nvi/dist/common/msg.c =================================================================== --- vendor/nvi/dist/common/msg.c (revision 366306) +++ vendor/nvi/dist/common/msg.c (revision 366307) @@ -1,883 +1,887 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" /* * msgq -- * Display a message. * * PUBLIC: void msgq(SCR *, mtype_t, const char *, ...); */ void msgq(SCR *sp, mtype_t mt, const char *fmt, ...) { #ifndef NL_ARGMAX #define __NL_ARGMAX 20 /* Set to 9 by System V. */ struct { const char *str; /* String pointer. */ size_t arg; /* Argument number. */ size_t prefix; /* Prefix string length. */ size_t skip; /* Skipped string length. */ size_t suffix; /* Suffix string length. */ } str[__NL_ARGMAX]; #endif static int reenter; /* STATIC: Re-entrancy check. */ GS *gp; size_t blen, len, mlen, nlen; const char *p; char *bp, *mp; va_list ap; #ifndef NL_ARGMAX int ch; char *rbp, *s_rbp; const char *t, *u; size_t cnt1, cnt2, soff; #endif /* * !!! * It's possible to enter msg when there's no screen to hold the * message. If sp is NULL, ignore the special cases and put the * message out to stderr. */ if (sp == NULL) { gp = NULL; if (mt == M_BERR) mt = M_ERR; else if (mt == M_VINFO) mt = M_INFO; } else { gp = sp->gp; switch (mt) { case M_BERR: if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) { F_SET(gp, G_BELLSCHED); return; } mt = M_ERR; break; case M_VINFO: if (!O_ISSET(sp, O_VERBOSE)) return; mt = M_INFO; /* FALLTHROUGH */ case M_INFO: if (F_ISSET(sp, SC_EX_SILENT)) return; break; case M_ERR: case M_SYSERR: break; default: abort(); } } /* * It's possible to reenter msg when it allocates space. We're * probably dead anyway, but there's no reason to drop core. * * XXX * Yes, there's a race, but it should only be two instructions. */ if (reenter++) return; /* Get space for the message. */ nlen = 1024; if (0) { retry: FREE_SPACE(sp, bp, blen); nlen *= 2; } bp = NULL; blen = 0; GET_SPACE_GOTOC(sp, bp, blen, nlen); /* * Error prefix. * * mp: pointer to the current next character to be written * mlen: length of the already written characters * blen: total length of the buffer */ #define REM (blen - mlen) mp = bp; mlen = 0; if (mt == M_SYSERR) { p = msg_cat(sp, "020|Error: ", &len); if (REM < len) goto retry; memcpy(mp, p, len); mp += len; mlen += len; } /* * If we're running an ex command that the user didn't enter, display * the file name and line number prefix. */ if ((mt == M_ERR || mt == M_SYSERR) && sp != NULL && gp != NULL && gp->if_name != NULL) { CHAR_T *wp; size_t wlen; CHAR2INT(sp, gp->if_name, strlen(gp->if_name) + 1, wp, wlen); for (; *wp != '\0'; ++wp) { len = snprintf(mp, REM, "%s", KEY_NAME(sp, *wp)); mp += len; if ((mlen += len) > blen) goto retry; } len = snprintf(mp, REM, ", %d: ", gp->if_lno); mp += len; if ((mlen += len) > blen) goto retry; } /* If nothing to format, we're done. */ if (fmt == NULL) goto nofmt; fmt = msg_cat(sp, fmt, NULL); #ifndef NL_ARGMAX /* * Nvi should run on machines that don't support the numbered argument * specifications (%[digit]*$). We do this by reformatting the string * so that we can hand it to vsprintf(3) and it will use the arguments * in the right order. When vsprintf returns, we put the string back * into the right order. It's undefined, according to SVID III, to mix * numbered argument specifications with the standard style arguments, * so this should be safe. * * In addition, we also need a character that is known to not occur in * any vi message, for separating the parts of the string. As callers * of msgq are responsible for making sure that all the non-printable * characters are formatted for printing before calling msgq, we use a * random non-printable character selected at terminal initialization * time. This code isn't fast by any means, but as messages should be * relatively short and normally have only a few arguments, it won't be * too bad. Regardless, nobody has come up with any other solution. * * The result of this loop is an array of pointers into the message * string, with associated lengths and argument numbers. The array * is in the "correct" order, and the arg field contains the argument * order. */ for (p = fmt, soff = 0; soff < __NL_ARGMAX;) { for (t = p; *p != '\0' && *p != '%'; ++p); if (*p == '\0') break; ++p; if (!isdigit((u_char)*p)) { if (*p == '%') ++p; continue; } for (u = p; *++p != '\0' && isdigit((u_char)*p);); if (*p != '$') continue; /* Up to, and including the % character. */ str[soff].str = t; str[soff].prefix = u - t; /* Up to, and including the $ character. */ str[soff].arg = atoi(u); str[soff].skip = (p - u) + 1; if (str[soff].arg >= __NL_ARGMAX) goto ret; /* Up to, and including the conversion character. */ for (u = p; (ch = *++p) != '\0';) if (isalpha(ch) && strchr("diouxXfeEgGcspn", ch) != NULL) break; str[soff].suffix = p - u; if (ch != '\0') ++p; ++soff; } /* If no magic strings, we're done. */ if (soff == 0) goto format; /* Get space for the reordered strings. */ if ((rbp = malloc(nlen)) == NULL) goto ret; s_rbp = rbp; /* * Reorder the strings into the message string based on argument * order. * * !!! * We ignore arguments that are out of order, i.e. if we don't find * an argument, we continue. Assume (almost certainly incorrectly) * that whoever created the string knew what they were doing. * * !!! * Brute force "sort", but since we don't expect more than one or two * arguments in a string, the setup cost of a fast sort will be more * expensive than the loop. */ for (cnt1 = 1; cnt1 <= soff; ++cnt1) for (cnt2 = 0; cnt2 < soff; ++cnt2) if (cnt1 == str[cnt2].arg) { memmove(s_rbp, str[cnt2].str, str[cnt2].prefix); memmove(s_rbp + str[cnt2].prefix, str[cnt2].str + str[cnt2].prefix + str[cnt2].skip, str[cnt2].suffix); s_rbp += str[cnt2].prefix + str[cnt2].suffix; *s_rbp++ = gp == NULL ? DEFAULT_NOPRINT : gp->noprint; break; } *s_rbp = '\0'; fmt = rbp; #endif #ifndef NL_ARGMAX format: /* Format the arguments into the string. */ #endif va_start(ap, fmt); len = vsnprintf(mp, REM, fmt, ap); va_end(ap); if (len >= nlen) goto retry; #ifndef NL_ARGMAX if (soff == 0) goto nofmt; /* * Go through the resulting string, and, for each separator character * separated string, enter its new starting position and length in the * array. */ for (p = t = mp, cnt1 = 1, ch = gp == NULL ? DEFAULT_NOPRINT : gp->noprint; *p != '\0'; ++p) if (*p == ch) { for (cnt2 = 0; cnt2 < soff; ++cnt2) if (str[cnt2].arg == cnt1) break; str[cnt2].str = t; str[cnt2].prefix = p - t; t = p + 1; ++cnt1; } /* * Reorder the strings once again, putting them back into the * message buffer. * * !!! * Note, the length of the message gets decremented once for * each substring, when we discard the separator character. */ for (s_rbp = rbp, cnt1 = 0; cnt1 < soff; ++cnt1) { memmove(rbp, str[cnt1].str, str[cnt1].prefix); rbp += str[cnt1].prefix; --len; } memmove(mp, s_rbp, rbp - s_rbp); /* Free the reordered string memory. */ free(s_rbp); #endif nofmt: mp += len; if ((mlen += len) > blen) goto retry; if (mt == M_SYSERR) { len = snprintf(mp, REM, ": %s", strerror(errno)); mp += len; if ((mlen += len) > blen) goto retry; mt = M_ERR; } /* Add trailing newline. */ if ((mlen += 1) > blen) goto retry; *mp = '\n'; if (sp != NULL) (void)ex_fflush(sp); if (gp != NULL) gp->scr_msg(sp, mt, bp, mlen); else (void)fprintf(stderr, "%.*s", (int)mlen, bp); /* Cleanup. */ #ifndef NL_ARGMAX ret: #endif FREE_SPACE(sp, bp, blen); alloc_err: reenter = 0; } /* * msgq_wstr -- * Display a message with an embedded string. * * PUBLIC: void msgq_wstr(SCR *, mtype_t, const CHAR_T *, const char *); */ void msgq_wstr(SCR *sp, mtype_t mtype, const CHAR_T *str, const char *fmt) { size_t nlen; CONST char *nstr; if (str == NULL) { msgq(sp, mtype, "%s", fmt); return; } INT2CHAR(sp, str, STRLEN(str) + 1, nstr, nlen); msgq_str(sp, mtype, nstr, fmt); } /* * msgq_str -- * Display a message with an embedded string. * * PUBLIC: void msgq_str(SCR *, mtype_t, const char *, const char *); */ void msgq_str(SCR *sp, mtype_t mtype, const char *str, const char *fmt) { int nf, sv_errno; char *p; if (str == NULL) { msgq(sp, mtype, "%s", fmt); return; } sv_errno = errno; p = msg_print(sp, str, &nf); errno = sv_errno; msgq(sp, mtype, fmt, p); if (nf) FREE_SPACE(sp, p, 0); } /* * mod_rpt -- * Report on the lines that changed. * * !!! * Historic vi documentation (USD:15-8) claimed that "The editor will also * always tell you when a change you make affects text which you cannot see." * This wasn't true -- edit a large file and do "100d|1". We don't implement * this semantic since it requires tracking each line that changes during a * command instead of just keeping count. * * Line counts weren't right in historic vi, either. For example, given the * file: * abc * def * the command 2d}, from the 'b' would report that two lines were deleted, * not one. * * PUBLIC: void mod_rpt(SCR *); */ void mod_rpt(SCR *sp) { static char * const action[] = { "293|added", "294|changed", "295|deleted", "296|joined", "297|moved", "298|shifted", "299|yanked", }; static char * const lines[] = { "300|line", "301|lines", }; recno_t total; u_long rptval; int first, cnt; size_t blen, len, tlen; const char *t; char * const *ap; char *bp, *p; /* Change reports are turned off in batch mode. */ if (F_ISSET(sp, SC_EX_SILENT)) return; /* Reset changing line number. */ sp->rptlchange = OOBLNO; /* * Don't build a message if not enough changed. * * !!! * And now, a vi clone test. Historically, vi reported if the number * of changed lines was > than the value, not >=, unless it was a yank * command, which used >=. No lie. Furthermore, an action was never * reported for a single line action. This is consistent for actions * other than yank, but yank didn't report single line actions even if * the report edit option was set to 1. In addition, setting report to * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an * unknown reason (this bug was fixed in System III/V at some point). * I got complaints, so nvi conforms to System III/V historic practice * except that we report a yank of 1 line if report is set to 1. */ #define ARSIZE(a) sizeof(a) / sizeof (*a) #define MAXNUM 25 rptval = O_VAL(sp, O_REPORT); for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt) total += sp->rptlines[cnt]; if (total == 0) return; if (total <= rptval && sp->rptlines[L_YANKED] < rptval) { for (cnt = 0; cnt < ARSIZE(action); ++cnt) sp->rptlines[cnt] = 0; return; } /* Build and display the message. */ GET_SPACE_GOTOC(sp, bp, blen, sizeof(action) * MAXNUM + 1); for (p = bp, first = 1, tlen = 0, ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt) if (sp->rptlines[cnt] != 0) { if (first) first = 0; else { *p++ = ';'; *p++ = ' '; tlen += 2; } len = snprintf(p, MAXNUM, "%lu ", (u_long)sp->rptlines[cnt]); p += len; tlen += len; t = msg_cat(sp, lines[sp->rptlines[cnt] == 1 ? 0 : 1], &len); memcpy(p, t, len); p += len; tlen += len; *p++ = ' '; ++tlen; t = msg_cat(sp, *ap, &len); memcpy(p, t, len); p += len; tlen += len; sp->rptlines[cnt] = 0; } /* Add trailing newline. */ *p = '\n'; ++tlen; (void)ex_fflush(sp); sp->gp->scr_msg(sp, M_INFO, bp, tlen); FREE_SPACE(sp, bp, blen); alloc_err: return; #undef ARSIZE #undef MAXNUM } /* * msgq_status -- * Report on the file's status. * * PUBLIC: void msgq_status(SCR *, recno_t, u_int); */ void msgq_status(SCR *sp, recno_t lno, u_int flags) { recno_t last; size_t blen, len; int cnt, needsep; const char *t; char **ap, *bp, *np, *p, *s, *ep; CHAR_T *wp; size_t wlen; /* Get sufficient memory. */ len = strlen(sp->frp->name); GET_SPACE_GOTOC(sp, bp, blen, len * MAX_CHARACTER_COLUMNS + 128); p = bp; ep = bp + blen; /* Convert the filename. */ CHAR2INT(sp, sp->frp->name, len + 1, wp, wlen); /* Copy in the filename. */ for (; *wp != '\0'; ++wp) { len = KEY_LEN(sp, *wp); memcpy(p, KEY_NAME(sp, *wp), len); p += len; } np = p; *p++ = ':'; *p++ = ' '; /* Copy in the argument count. */ if (F_ISSET(sp, SC_STATUS_CNT) && sp->argv != NULL) { for (cnt = 0, ap = sp->argv; *ap != NULL; ++ap, ++cnt); if (cnt > 1) { (void)snprintf(p, ep - p, msg_cat(sp, "317|%d files to edit", NULL), cnt); p += strlen(p); *p++ = ':'; *p++ = ' '; } F_CLR(sp, SC_STATUS_CNT); } /* * See nvi/exf.c:file_init() for a description of how and when the * read-only bit is set. * * !!! * The historic display for "name changed" was "[Not edited]". */ needsep = 0; if (F_ISSET(sp->frp, FR_NEWFILE)) { F_CLR(sp->frp, FR_NEWFILE); t = msg_cat(sp, "021|new file", &len); memcpy(p, t, len); p += len; needsep = 1; } else { if (F_ISSET(sp->frp, FR_NAMECHANGE)) { t = msg_cat(sp, "022|name changed", &len); memcpy(p, t, len); p += len; needsep = 1; } if (needsep) { *p++ = ','; *p++ = ' '; } if (F_ISSET(sp->ep, F_MODIFIED)) t = msg_cat(sp, "023|modified", &len); else t = msg_cat(sp, "024|unmodified", &len); memcpy(p, t, len); p += len; needsep = 1; } if (F_ISSET(sp->frp, FR_UNLOCKED)) { if (needsep) { *p++ = ','; *p++ = ' '; } t = msg_cat(sp, "025|UNLOCKED", &len); memcpy(p, t, len); p += len; needsep = 1; } if (O_ISSET(sp, O_READONLY)) { if (needsep) { *p++ = ','; *p++ = ' '; } t = msg_cat(sp, "026|readonly", &len); memcpy(p, t, len); p += len; needsep = 1; } if (needsep) { *p++ = ':'; *p++ = ' '; } if (LF_ISSET(MSTAT_SHOWLAST)) { if (db_last(sp, &last)) return; if (last == 0) { t = msg_cat(sp, "028|empty file", &len); memcpy(p, t, len); p += len; } else { t = msg_cat(sp, "027|line %lu of %lu [%ld%%]", &len); (void)snprintf(p, ep - p, t, (u_long)lno, (u_long)last, ((u_long)lno * 100) / last); p += strlen(p); } } else { t = msg_cat(sp, "029|line %lu", &len); (void)snprintf(p, ep - p, t, (u_long)lno); p += strlen(p); } #ifdef DEBUG (void)snprintf(p, ep - p, " (pid %lu)", (u_long)getpid()); p += strlen(p); #endif *p++ = '\n'; len = p - bp; /* * There's a nasty problem with long path names. Cscope and tags files * can result in long paths and vi will request a continuation key from * the user as soon as it starts the screen. Unfortunately, the user * has already typed ahead, and chaos results. If we assume that the * characters in the filenames and informational messages only take a * single screen column each, we can trim the filename. * * XXX * Status lines get put up at fairly awkward times. For example, when * you do a filter read (e.g., :read ! echo foo) in the top screen of a * split screen, we have to repaint the status lines for all the screens * below the top screen. We don't want users having to enter continue * characters for those screens. Make it really hard to screw this up. */ s = bp; if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) { for (; s < np && (*s != '/' || (p - s) > sp->cols - 3); ++s); if (s == np) { s = p - (sp->cols - 5); *--s = ' '; } *--s = '.'; *--s = '.'; *--s = '.'; len = p - s; } /* Flush any waiting ex messages. */ (void)ex_fflush(sp); sp->gp->scr_msg(sp, M_INFO, s, len); FREE_SPACE(sp, bp, blen); alloc_err: return; } /* * msg_open -- * Open the message catalogs. * * PUBLIC: int msg_open(SCR *, char *); */ int msg_open(SCR *sp, char *file) { /* * !!! * Assume that the first file opened is the system default, and that * all subsequent ones user defined. Only display error messages * if we can't open the user defined ones -- it's useful to know if * the system one wasn't there, but if nvi is being shipped with an * installed system, the file will be there, if it's not, then the * message will be repeated every time nvi is started up. */ static int first = 1; nl_catd catd; char *p; int rval = 0; if ((p = strrchr(file, '/')) != NULL && p[1] == '\0') { /* Confirms to XPG4. */ if ((p = join(file, setlocale(LC_MESSAGES, NULL))) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } } else { /* Make sure it's recognized as a path by catopen(3). */ if ((p = join(".", file)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } } errno = 0; if ((catd = catopen(p, NL_CAT_LOCALE)) == (nl_catd)-1) { if (first) { first = 0; rval = 1; goto ret; } /* * POSIX.1-2008 gives no instruction on how to report a * corrupt catalog file. Errno == 0 is not rare; add * EFTYPE, which is seen on FreeBSD, for a good measure. */ +#ifdef EFTYPE if (errno == 0 || errno == EFTYPE) +#else + if (errno == 0) +#endif msgq_str(sp, M_ERR, p, "030|The file %s is not a message catalog"); else msgq_str(sp, M_SYSERR, p, "%s"); rval = 1; goto ret; } first = 0; msg_close(sp->gp); sp->gp->catd = catd; ret: free(p); return (rval); } /* * msg_close -- * Close the message catalogs. * * PUBLIC: void msg_close(GS *); */ void msg_close(GS *gp) { if (gp->catd != (nl_catd)-1) (void)catclose(gp->catd); } /* * msg_cont -- * Return common continuation messages. * * PUBLIC: const char *msg_cmsg(SCR *, cmsg_t, size_t *); */ const char * msg_cmsg(SCR *sp, cmsg_t which, size_t *lenp) { switch (which) { case CMSG_CONF: return (msg_cat(sp, "268|confirm? [ynq]", lenp)); case CMSG_CONT: return (msg_cat(sp, "269|Press any key to continue: ", lenp)); case CMSG_CONT_EX: return (msg_cat(sp, "270|Press any key to continue [: to enter more ex commands]: ", lenp)); case CMSG_CONT_R: return (msg_cat(sp, "161|Press Enter to continue: ", lenp)); case CMSG_CONT_S: return (msg_cat(sp, "275| cont?", lenp)); case CMSG_CONT_Q: return (msg_cat(sp, "271|Press any key to continue [q to quit]: ", lenp)); default: abort(); } /* NOTREACHED */ } /* * msg_cat -- * Return a single message from the catalog, plus its length. * * !!! * Only a single catalog message can be accessed at a time, if multiple * ones are needed, they must be copied into local memory. * * PUBLIC: const char *msg_cat(SCR *, const char *, size_t *); */ const char * msg_cat(SCR *sp, const char *str, size_t *lenp) { GS *gp; char *p; int msgno; /* * If it's not a catalog message, i.e. has doesn't have a leading * number and '|' symbol, we're done. */ if (isdigit((u_char)str[0]) && isdigit((u_char)str[1]) && isdigit((u_char)str[2]) && str[3] == '|') { msgno = atoi(str); str = &str[4]; gp = sp == NULL ? NULL : sp->gp; if (gp != NULL && gp->catd != (nl_catd)-1 && (p = catgets(gp->catd, 1, msgno, str)) != NULL) { if (lenp != NULL) *lenp = strlen(p); return (p); } } if (lenp != NULL) *lenp = strlen(str); return (str); } /* * msg_print -- * Return a printable version of a string, in allocated memory. * * PUBLIC: char *msg_print(SCR *, const char *, int *); */ char * msg_print(SCR *sp, const char *s, int *needfree) { size_t blen, nlen; char *bp, *ep, *p, *t; CHAR_T *wp, *cp; size_t wlen; *needfree = 0; /* XXX Not good for debugging ex_read & ex_filter.*/ CHAR2INT5(sp, EXP(sp)->ibcw, (char *)s, strlen(s) + 1, wp, wlen); for (cp = wp; *cp != '\0'; ++cp) if (!ISPRINT(*cp)) break; if (*cp == '\0') return ((char *)s); /* SAFE: needfree set to 0. */ nlen = 0; if (0) { retry: if (sp == NULL) free(bp); else FREE_SPACE(sp, bp, blen); *needfree = 0; } nlen += 256; if (sp == NULL) { if ((bp = malloc(nlen)) == NULL) goto alloc_err; } else GET_SPACE_GOTOC(sp, bp, blen, nlen); if (0) { alloc_err: return (""); } *needfree = 1; for (p = bp, ep = (bp + blen) - 1; *wp != '\0' && p < ep; ++wp) for (t = KEY_NAME(sp, *wp); *t != '\0' && p < ep; *p++ = *t++); if (p == ep) goto retry; *p = '\0'; return (bp); } Index: vendor/nvi/dist/common/options.c =================================================================== --- vendor/nvi/dist/common/options.c (revision 366306) +++ vendor/nvi/dist/common/options.c (revision 366307) @@ -1,1158 +1,1166 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include +#include #include #include "common.h" #include "../vi/vi.h" #include "pathnames.h" static int opts_abbcmp(const void *, const void *); static int opts_cmp(const void *, const void *); static int opts_print(SCR *, OPTLIST const *); #ifdef USE_WIDECHAR #define OPT_WC 0 #else #define OPT_WC (OPT_NOSAVE | OPT_NDISP) #endif /* * O'Reilly noted options and abbreviations are from "Learning the VI Editor", * Fifth Edition, May 1992. There's no way of knowing what systems they are * actually from. * * HPUX noted options and abbreviations are from "The Ultimate Guide to the * VI and EX Text Editors", 1990. */ OPTLIST const optlist[] = { /* O_ALTWERASE 4.4BSD */ {L("altwerase"), f_altwerase, OPT_0BOOL, 0}, /* O_AUTOINDENT 4BSD */ {L("autoindent"), NULL, OPT_0BOOL, 0}, /* O_AUTOPRINT 4BSD */ {L("autoprint"), NULL, OPT_1BOOL, 0}, /* O_AUTOWRITE 4BSD */ {L("autowrite"), NULL, OPT_0BOOL, 0}, /* O_BACKUP 4.4BSD */ {L("backup"), NULL, OPT_STR, 0}, /* O_BEAUTIFY 4BSD */ {L("beautify"), NULL, OPT_0BOOL, 0}, /* O_CDPATH 4.4BSD */ {L("cdpath"), NULL, OPT_STR, 0}, /* O_CEDIT 4.4BSD */ {L("cedit"), NULL, OPT_STR, 0}, /* O_COLUMNS 4.4BSD */ {L("columns"), f_columns, OPT_NUM, OPT_NOSAVE}, /* O_COMBINED */ {L("combined"), NULL, OPT_0BOOL, OPT_NOSET|OPT_WC}, /* O_COMMENT 4.4BSD */ {L("comment"), NULL, OPT_0BOOL, 0}, /* O_TMPDIR 4BSD */ {L("directory"), NULL, OPT_STR, 0}, /* O_EDCOMPATIBLE 4BSD */ {L("edcompatible"),NULL, OPT_0BOOL, 0}, /* O_ERRORBELLS 4BSD */ {L("errorbells"), NULL, OPT_0BOOL, 0}, /* O_ESCAPETIME 4.4BSD */ {L("escapetime"), NULL, OPT_NUM, 0}, /* O_EXPANDTAB NetBSD 5.0 */ {L("expandtab"), NULL, OPT_0BOOL, 0}, /* O_EXRC System V (undocumented) */ {L("exrc"), NULL, OPT_0BOOL, 0}, /* O_EXTENDED 4.4BSD */ {L("extended"), f_recompile, OPT_0BOOL, 0}, /* O_FILEC 4.4BSD */ {L("filec"), NULL, OPT_STR, 0}, /* O_FILEENCODING */ {L("fileencoding"),f_encoding, OPT_STR, OPT_WC}, /* O_FLASH HPUX */ {L("flash"), NULL, OPT_1BOOL, 0}, /* O_HARDTABS 4BSD */ {L("hardtabs"), NULL, OPT_NUM, 0}, /* O_ICLOWER 4.4BSD */ {L("iclower"), f_recompile, OPT_0BOOL, 0}, /* O_IGNORECASE 4BSD */ {L("ignorecase"), f_recompile, OPT_0BOOL, 0}, /* O_INPUTENCODING */ {L("inputencoding"),f_encoding, OPT_STR, OPT_WC}, /* O_KEYTIME 4.4BSD */ {L("keytime"), NULL, OPT_NUM, 0}, /* O_LEFTRIGHT 4.4BSD */ {L("leftright"), f_reformat, OPT_0BOOL, 0}, /* O_LINES 4.4BSD */ {L("lines"), f_lines, OPT_NUM, OPT_NOSAVE}, /* O_LISP 4BSD * XXX * When the lisp option is implemented, delete the OPT_NOSAVE flag, * so that :mkexrc dumps it. */ {L("lisp"), f_lisp, OPT_0BOOL, OPT_NOSAVE}, /* O_LIST 4BSD */ {L("list"), f_reformat, OPT_0BOOL, 0}, /* O_LOCKFILES 4.4BSD * XXX * Locking isn't reliable enough over NFS to require it, in addition, * it's a serious startup performance problem over some remote links. */ {L("lock"), NULL, OPT_1BOOL, 0}, /* O_MAGIC 4BSD */ {L("magic"), NULL, OPT_1BOOL, 0}, /* O_MATCHCHARS NetBSD 2.0 */ {L("matchchars"), NULL, OPT_STR, OPT_PAIRS}, /* O_MATCHTIME 4.4BSD */ {L("matchtime"), NULL, OPT_NUM, 0}, /* O_MESG 4BSD */ {L("mesg"), NULL, OPT_1BOOL, 0}, /* O_MODELINE 4BSD * !!! * This has been documented in historical systems as both "modeline" * and as "modelines". Regardless of the name, this option represents * a security problem of mammoth proportions, not to mention a stunning * example of what your intro CS professor referred to as the perils of * mixing code and data. Don't add it, or I will kill you. */ {L("modeline"), NULL, OPT_0BOOL, OPT_NOSET}, /* O_MSGCAT 4.4BSD */ {L("msgcat"), f_msgcat, OPT_STR, 0}, /* O_NOPRINT 4.4BSD */ {L("noprint"), f_print, OPT_STR, 0}, /* O_NUMBER 4BSD */ {L("number"), f_reformat, OPT_0BOOL, 0}, /* O_OCTAL 4.4BSD */ {L("octal"), f_print, OPT_0BOOL, 0}, /* O_OPEN 4BSD */ {L("open"), NULL, OPT_1BOOL, 0}, /* O_OPTIMIZE 4BSD */ {L("optimize"), NULL, OPT_1BOOL, 0}, /* O_PARAGRAPHS 4BSD */ {L("paragraphs"), NULL, OPT_STR, OPT_PAIRS}, /* O_PATH 4.4BSD */ {L("path"), NULL, OPT_STR, 0}, /* O_PRINT 4.4BSD */ {L("print"), f_print, OPT_STR, 0}, /* O_PROMPT 4BSD */ {L("prompt"), NULL, OPT_1BOOL, 0}, /* O_READONLY 4BSD (undocumented) */ {L("readonly"), f_readonly, OPT_0BOOL, OPT_ALWAYS}, /* O_RECDIR 4.4BSD */ {L("recdir"), NULL, OPT_STR, 0}, /* O_REDRAW 4BSD */ {L("redraw"), NULL, OPT_0BOOL, 0}, /* O_REMAP 4BSD */ {L("remap"), NULL, OPT_1BOOL, 0}, /* O_REPORT 4BSD */ {L("report"), NULL, OPT_NUM, 0}, /* O_RULER 4.4BSD */ {L("ruler"), NULL, OPT_0BOOL, 0}, /* O_SCROLL 4BSD */ {L("scroll"), NULL, OPT_NUM, 0}, /* O_SEARCHINCR 4.4BSD */ {L("searchincr"), NULL, OPT_0BOOL, 0}, /* O_SECTIONS 4BSD */ {L("sections"), NULL, OPT_STR, OPT_PAIRS}, /* O_SECURE 4.4BSD */ {L("secure"), NULL, OPT_0BOOL, OPT_NOUNSET}, /* O_SHELL 4BSD */ {L("shell"), NULL, OPT_STR, 0}, /* O_SHELLMETA 4.4BSD */ {L("shellmeta"), NULL, OPT_STR, 0}, /* O_SHIFTWIDTH 4BSD */ {L("shiftwidth"), NULL, OPT_NUM, OPT_NOZERO}, /* O_SHOWMATCH 4BSD */ {L("showmatch"), NULL, OPT_0BOOL, 0}, /* O_SHOWMODE 4.4BSD */ {L("showmode"), NULL, OPT_0BOOL, 0}, /* O_SIDESCROLL 4.4BSD */ {L("sidescroll"), NULL, OPT_NUM, OPT_NOZERO}, /* O_SLOWOPEN 4BSD */ {L("slowopen"), NULL, OPT_0BOOL, 0}, /* O_SOURCEANY 4BSD (undocumented) * !!! * Historic vi, on startup, source'd $HOME/.exrc and ./.exrc, if they * were owned by the user. The sourceany option was an undocumented * feature of historic vi which permitted the startup source'ing of * .exrc files the user didn't own. This is an obvious security problem, * and we ignore the option. */ {L("sourceany"), NULL, OPT_0BOOL, OPT_NOSET}, /* O_TABSTOP 4BSD */ {L("tabstop"), f_reformat, OPT_NUM, OPT_NOZERO}, /* O_TAGLENGTH 4BSD */ {L("taglength"), NULL, OPT_NUM, 0}, /* O_TAGS 4BSD */ {L("tags"), NULL, OPT_STR, 0}, /* O_TERM 4BSD * !!! * By default, the historic vi always displayed information about two * options, redraw and term. Term seems sufficient. */ {L("term"), NULL, OPT_STR, OPT_ADISP|OPT_NOSAVE}, /* O_TERSE 4BSD */ {L("terse"), NULL, OPT_0BOOL, 0}, /* O_TILDEOP 4.4BSD */ {L("tildeop"), NULL, OPT_0BOOL, 0}, /* O_TIMEOUT 4BSD (undocumented) */ {L("timeout"), NULL, OPT_1BOOL, 0}, /* O_TTYWERASE 4.4BSD */ {L("ttywerase"), f_ttywerase, OPT_0BOOL, 0}, /* O_VERBOSE 4.4BSD */ {L("verbose"), NULL, OPT_0BOOL, 0}, /* O_W1200 4BSD */ {L("w1200"), f_w1200, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_W300 4BSD */ {L("w300"), f_w300, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_W9600 4BSD */ {L("w9600"), f_w9600, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_WARN 4BSD */ {L("warn"), NULL, OPT_1BOOL, 0}, /* O_WINDOW 4BSD */ {L("window"), f_window, OPT_NUM, 0}, /* O_WINDOWNAME 4BSD */ {L("windowname"), NULL, OPT_0BOOL, 0}, /* O_WRAPLEN 4.4BSD */ {L("wraplen"), NULL, OPT_NUM, 0}, /* O_WRAPMARGIN 4BSD */ {L("wrapmargin"), NULL, OPT_NUM, 0}, /* O_WRAPSCAN 4BSD */ {L("wrapscan"), NULL, OPT_1BOOL, 0}, /* O_WRITEANY 4BSD */ {L("writeany"), NULL, OPT_0BOOL, 0}, {NULL}, }; typedef struct abbrev { CHAR_T *name; int offset; } OABBREV; static OABBREV const abbrev[] = { {L("ai"), O_AUTOINDENT}, /* 4BSD */ {L("ap"), O_AUTOPRINT}, /* 4BSD */ {L("aw"), O_AUTOWRITE}, /* 4BSD */ {L("bf"), O_BEAUTIFY}, /* 4BSD */ {L("co"), O_COLUMNS}, /* 4.4BSD */ {L("dir"), O_TMPDIR}, /* 4BSD */ {L("eb"), O_ERRORBELLS}, /* 4BSD */ {L("ed"), O_EDCOMPATIBLE}, /* 4BSD */ {L("et"), O_EXPANDTAB}, /* NetBSD 5.0 */ {L("ex"), O_EXRC}, /* System V (undocumented) */ {L("fe"), O_FILEENCODING}, {L("ht"), O_HARDTABS}, /* 4BSD */ {L("ic"), O_IGNORECASE}, /* 4BSD */ {L("ie"), O_INPUTENCODING}, {L("li"), O_LINES}, /* 4.4BSD */ {L("modelines"), O_MODELINE}, /* HPUX */ {L("nu"), O_NUMBER}, /* 4BSD */ {L("opt"), O_OPTIMIZE}, /* 4BSD */ {L("para"), O_PARAGRAPHS}, /* 4BSD */ {L("re"), O_REDRAW}, /* O'Reilly */ {L("ro"), O_READONLY}, /* 4BSD (undocumented) */ {L("scr"), O_SCROLL}, /* 4BSD (undocumented) */ {L("sect"), O_SECTIONS}, /* O'Reilly */ {L("sh"), O_SHELL}, /* 4BSD */ {L("slow"), O_SLOWOPEN}, /* 4BSD */ {L("sm"), O_SHOWMATCH}, /* 4BSD */ {L("smd"), O_SHOWMODE}, /* 4BSD */ {L("sw"), O_SHIFTWIDTH}, /* 4BSD */ {L("tag"), O_TAGS}, /* 4BSD (undocumented) */ {L("tl"), O_TAGLENGTH}, /* 4BSD */ {L("to"), O_TIMEOUT}, /* 4BSD (undocumented) */ {L("ts"), O_TABSTOP}, /* 4BSD */ {L("tty"), O_TERM}, /* 4BSD (undocumented) */ {L("ttytype"), O_TERM}, /* 4BSD (undocumented) */ {L("w"), O_WINDOW}, /* O'Reilly */ {L("wa"), O_WRITEANY}, /* 4BSD */ {L("wi"), O_WINDOW}, /* 4BSD (undocumented) */ {L("wl"), O_WRAPLEN}, /* 4.4BSD */ {L("wm"), O_WRAPMARGIN}, /* 4BSD */ {L("ws"), O_WRAPSCAN}, /* 4BSD */ {NULL}, }; /* * opts_init -- * Initialize some of the options. * * PUBLIC: int opts_init(SCR *, int *); */ int opts_init(SCR *sp, int *oargs) { ARGS *argv[2], a, b; OPTLIST const *op; u_long v; int cnt, optindx = 0; char *s; CHAR_T b2[1024]; a.bp = b2; b.bp = NULL; a.len = b.len = 0; argv[0] = &a; argv[1] = &b; /* Set numeric and string default values. */ -#define OI(indx, str) { \ +#define OI(indx, str) do { \ a.len = STRLEN(str); \ if ((CHAR_T*)str != b2) /* GCC puts strings in text-space. */ \ (void)MEMCPY(b2, str, a.len+1); \ if (opts_set(sp, argv, NULL)) { \ optindx = indx; \ goto err; \ } \ -} +} while (0) /* * Indirect global options to global space. Specifically, set up * terminal, lines, columns first, they're used by other options. * Note, don't set the flags until we've set up the indirection. */ if (o_set(sp, O_TERM, 0, NULL, GO_TERM)) goto err; F_SET(&sp->opts[O_TERM], OPT_GLOBAL); if (o_set(sp, O_LINES, 0, NULL, GO_LINES)) goto err; F_SET(&sp->opts[O_LINES], OPT_GLOBAL); if (o_set(sp, O_COLUMNS, 0, NULL, GO_COLUMNS)) goto err; F_SET(&sp->opts[O_COLUMNS], OPT_GLOBAL); if (o_set(sp, O_SECURE, 0, NULL, GO_SECURE)) goto err; F_SET(&sp->opts[O_SECURE], OPT_GLOBAL); /* Initialize string values. */ (void)SPRINTF(b2, SIZE(b2), L("cdpath=%s"), (s = getenv("CDPATH")) == NULL ? ":" : s); OI(O_CDPATH, b2); OI(O_CEDIT, L("cedit=\033")); /* * !!! * Vi historically stored temporary files in /var/tmp. We store them * in /tmp by default, hoping it's a memory based file system. There * are two ways to change this -- the user can set either the directory * option or the TMPDIR environmental variable. */ (void)SPRINTF(b2, SIZE(b2), L("directory=%s"), (s = getenv("TMPDIR")) == NULL ? _PATH_TMP : s); OI(O_TMPDIR, b2); OI(O_ESCAPETIME, L("escapetime=6")); OI(O_FILEC, L("filec=\t")); OI(O_KEYTIME, L("keytime=6")); OI(O_MATCHCHARS, L("matchchars=()[]{}")); OI(O_MATCHTIME, L("matchtime=7")); (void)SPRINTF(b2, SIZE(b2), L("msgcat=%s"), _PATH_MSGCAT); OI(O_MSGCAT, b2); OI(O_REPORT, L("report=5")); OI(O_PARAGRAPHS, L("paragraphs=IPLPPPQPP LIpplpipbp")); (void)SPRINTF(b2, SIZE(b2), L("path=%s"), ""); OI(O_PATH, b2); - (void)SPRINTF(b2, SIZE(b2), L("recdir=%s"), _PATH_PRESERVE); + (void)SPRINTF(b2, SIZE(b2), L("recdir=%s"), NVI_PATH_PRESERVE); OI(O_RECDIR, b2); OI(O_SECTIONS, L("sections=NHSHH HUnhsh")); (void)SPRINTF(b2, SIZE(b2), L("shell=%s"), (s = getenv("SHELL")) == NULL ? _PATH_BSHELL : s); OI(O_SHELL, b2); OI(O_SHELLMETA, L("shellmeta=~{[*?$`'\"\\")); OI(O_SHIFTWIDTH, L("shiftwidth=8")); OI(O_SIDESCROLL, L("sidescroll=16")); OI(O_TABSTOP, L("tabstop=8")); (void)SPRINTF(b2, SIZE(b2), L("tags=%s"), _PATH_TAGS); OI(O_TAGS, b2); /* * XXX * Initialize O_SCROLL here, after term; initializing term should * have created a LINES/COLUMNS value. */ if ((v = (O_VAL(sp, O_LINES) - 1) / 2) == 0) v = 1; (void)SPRINTF(b2, SIZE(b2), L("scroll=%ld"), v); OI(O_SCROLL, b2); /* * The default window option values are: * 8 if baud rate <= 600 * 16 if baud rate <= 1200 * LINES - 1 if baud rate > 1200 * * Note, the windows option code will correct any too-large value * or when the O_LINES value is 1. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v <= 600) v = 8; else if (v <= 1200) v = 16; else if ((v = O_VAL(sp, O_LINES) - 1) == 0) v = 1; (void)SPRINTF(b2, SIZE(b2), L("window=%lu"), v); OI(O_WINDOW, b2); /* * Set boolean default values, and copy all settings into the default * information. OS_NOFREE is set, we're copying, not replacing. */ for (op = optlist, cnt = 0; op->name != NULL; ++op, ++cnt) { if (F_ISSET(op, OPT_GLOBAL)) continue; switch (op->type) { case OPT_0BOOL: break; case OPT_1BOOL: O_SET(sp, cnt); O_D_SET(sp, cnt); break; case OPT_NUM: o_set(sp, cnt, OS_DEF, NULL, O_VAL(sp, cnt)); break; case OPT_STR: if (O_STR(sp, cnt) != NULL && o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STRDUP, O_STR(sp, cnt), 0)) goto err; break; default: abort(); } } /* * !!! * Some options can be initialized by the command name or the * command-line arguments. They don't set the default values, * it's historic practice. */ for (; *oargs != -1; ++oargs) OI(*oargs, optlist[*oargs].name); #undef OI return (0); err: msgq_wstr(sp, M_ERR, optlist[optindx].name, "031|Unable to set default %s option"); return (1); } /* * opts_set -- * Change the values of one or more options. * * PUBLIC: int opts_set(SCR *, ARGS *[], char *); */ int opts_set(SCR *sp, ARGS *argv[], char *usage) { enum optdisp disp; enum nresult nret; OPTLIST const *op; OPTION *spo; u_long isset, turnoff, value; int ch, equals, nf, nf2, offset, qmark, rval; CHAR_T *endp, *name, *p, *sep; char *p2, *t2; char *np; size_t nlen; disp = NO_DISPLAY; for (rval = 0; argv[0]->len != 0; ++argv) { /* * The historic vi dumped the options for each occurrence of * "all" in the set list. Puhleeze. */ if (!STRCMP(argv[0]->bp, L("all"))) { disp = ALL_DISPLAY; continue; } /* Find equals sign or question mark. */ for (sep = NULL, equals = qmark = 0, p = name = argv[0]->bp; (ch = *p) != '\0'; ++p) if (ch == '=' || ch == '?') { if (p == name) { if (usage != NULL) msgq(sp, M_ERR, "032|Usage: %s", usage); return (1); } sep = p; if (ch == '=') equals = 1; else qmark = 1; break; } turnoff = 0; op = NULL; if (sep != NULL) *sep++ = '\0'; /* Search for the name, then name without any leading "no". */ if ((op = opts_search(name)) == NULL && name[0] == 'n' && name[1] == 'o') { turnoff = 1; name += 2; op = opts_search(name); } if (op == NULL) { opts_nomatch(sp, name); rval = 1; continue; } /* Find current option values. */ offset = op - optlist; spo = sp->opts + offset; /* * !!! * Historically, the question mark could be a separate * argument. */ if (!equals && !qmark && argv[1]->len == 1 && argv[1]->bp[0] == '?') { ++argv; qmark = 1; } /* Set name, value. */ switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: /* Some options may not be reset. */ if (F_ISSET(op, OPT_NOUNSET) && turnoff) { msgq_wstr(sp, M_ERR, name, "291|set: the %s option may not be turned off"); rval = 1; break; } /* Some options may not be set. */ if (F_ISSET(op, OPT_NOSET) && !turnoff) { msgq_wstr(sp, M_ERR, name, "313|set: the %s option may never be turned on"); rval = 1; break; } if (equals) { msgq_wstr(sp, M_ERR, name, "034|set: [no]%s option doesn't take a value"); rval = 1; break; } if (qmark) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ isset = !turnoff; - if (!F_ISSET(op, OPT_ALWAYS)) + if (!F_ISSET(op, OPT_ALWAYS)) { if (isset) { if (O_ISSET(sp, offset)) break; } else if (!O_ISSET(sp, offset)) break; + } /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, NULL, &isset)) || ex_optchange(sp, offset, NULL, &isset) || v_optchange(sp, offset, NULL, &isset) || sp->gp->scr_optchange(sp, offset, NULL, &isset)) { rval = 1; break; } /* Set the value. */ if (isset) O_SET(sp, offset); else O_CLR(sp, offset); break; case OPT_NUM: if (turnoff) { msgq_wstr(sp, M_ERR, name, "035|set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } if (!ISDIGIT(sep[0])) goto badnum; if ((nret = nget_uslong(&value, sep, &endp, 10)) != NUM_OK) { INT2CHAR(sp, name, STRLEN(name) + 1, np, nlen); p2 = msg_print(sp, np, &nf); INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); t2 = msg_print(sp, np, &nf2); switch (nret) { case NUM_ERR: msgq(sp, M_SYSERR, "036|set: %s option: %s", p2, t2); break; case NUM_OVER: msgq(sp, M_ERR, "037|set: %s option: %s: value overflow", p2, t2); break; case NUM_OK: case NUM_UNDER: abort(); } if (nf) FREE_SPACE(sp, p2, 0); if (nf2) FREE_SPACE(sp, t2, 0); rval = 1; break; } if (*endp && !cmdskip(*endp)) { badnum: INT2CHAR(sp, name, STRLEN(name) + 1, np, nlen); p2 = msg_print(sp, np, &nf); INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); t2 = msg_print(sp, np, &nf2); msgq(sp, M_ERR, "038|set: %s option: %s is an illegal number", p2, t2); if (nf) FREE_SPACE(sp, p2, 0); if (nf2) FREE_SPACE(sp, t2, 0); rval = 1; break; } /* Some options may never be set to zero. */ if (F_ISSET(op, OPT_NOZERO) && value == 0) { msgq_wstr(sp, M_ERR, name, "314|set: the %s option may never be set to 0"); rval = 1; break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ if (!F_ISSET(op, OPT_ALWAYS) && O_VAL(sp, offset) == value) break; /* Report to subsystems. */ INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); if ((op->func != NULL && op->func(sp, spo, np, &value)) || ex_optchange(sp, offset, np, &value) || v_optchange(sp, offset, np, &value) || sp->gp->scr_optchange(sp, offset, np, &value)) { rval = 1; break; } /* Set the value. */ if (o_set(sp, offset, 0, NULL, value)) rval = 1; break; case OPT_STR: if (turnoff) { msgq_wstr(sp, M_ERR, name, "039|set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* Check for strings that must have even length. */ if (F_ISSET(op, OPT_PAIRS) && STRLEN(sep) & 1) { msgq_wstr(sp, M_ERR, name, "047|The %s option must be in two character groups"); rval = 1; break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); if (!F_ISSET(op, OPT_ALWAYS) && O_STR(sp, offset) != NULL && !strcmp(O_STR(sp, offset), np)) break; /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, np, NULL)) || ex_optchange(sp, offset, np, NULL) || v_optchange(sp, offset, np, NULL) || sp->gp->scr_optchange(sp, offset, np, NULL)) { rval = 1; break; } /* Set the value. */ if (o_set(sp, offset, OS_STRDUP, np, 0)) rval = 1; break; default: abort(); } } if (disp != NO_DISPLAY) opts_dump(sp, disp); return (rval); } /* * o_set -- * Set an option's value. * * PUBLIC: int o_set(SCR *, int, u_int, char *, u_long); */ int o_set(SCR *sp, int opt, u_int flags, char *str, u_long val) { OPTION *op; /* Set a pointer to the options area. */ op = F_ISSET(&sp->opts[opt], OPT_GLOBAL) ? &sp->gp->opts[sp->opts[opt].o_cur.val] : &sp->opts[opt]; /* Copy the string, if requested. */ if (LF_ISSET(OS_STRDUP) && (str = strdup(str)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* Free the previous string, if requested, and set the value. */ if LF_ISSET(OS_DEF) if (LF_ISSET(OS_STR | OS_STRDUP)) { if (!LF_ISSET(OS_NOFREE)) free(op->o_def.str); op->o_def.str = str; } else op->o_def.val = val; else if (LF_ISSET(OS_STR | OS_STRDUP)) { if (!LF_ISSET(OS_NOFREE)) free(op->o_cur.str); op->o_cur.str = str; } else op->o_cur.val = val; return (0); } /* * opts_empty -- * Return 1 if the string option is invalid, 0 if it's OK. * * PUBLIC: int opts_empty(SCR *, int, int); */ int opts_empty(SCR *sp, int off, int silent) { char *p; if ((p = O_STR(sp, off)) == NULL || p[0] == '\0') { if (!silent) msgq_wstr(sp, M_ERR, optlist[off].name, "305|No %s edit option specified"); return (1); } return (0); } /* * opts_dump -- * List the current values of selected options. * * PUBLIC: void opts_dump(SCR *, enum optdisp); */ void opts_dump(SCR *sp, enum optdisp type) { OPTLIST const *op; int base, b_num, cnt, col, colwidth, curlen, s_num; int numcols, numrows, row; int b_op[O_OPTIONCOUNT], s_op[O_OPTIONCOUNT]; char nbuf[20]; /* * Options are output in two groups -- those that fit in a column and * those that don't. Output is done on 6 character "tab" boundaries * for no particular reason. (Since we don't output tab characters, * we can ignore the terminal's tab settings.) Ignore the user's tab * setting because we have no idea how reasonable it is. * * Find a column width we can live with, testing from 10 columns to 1. */ for (numcols = 10; numcols > 1; --numcols) { colwidth = sp->cols / numcols & ~(STANDARD_TAB - 1); if (colwidth >= 10) { colwidth = (colwidth + STANDARD_TAB) & ~(STANDARD_TAB - 1); numcols = sp->cols / colwidth; break; } colwidth = 0; } /* * Get the set of options to list, entering them into * the column list or the overflow list. */ for (b_num = s_num = 0, op = optlist; op->name != NULL; ++op) { cnt = op - optlist; /* If OPT_NDISP set, it's never displayed. */ if (F_ISSET(op, OPT_NDISP)) continue; switch (type) { case ALL_DISPLAY: /* Display all. */ break; case CHANGED_DISPLAY: /* Display changed. */ /* If OPT_ADISP set, it's always "changed". */ if (F_ISSET(op, OPT_ADISP)) break; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: case OPT_NUM: if (O_VAL(sp, cnt) == O_D_VAL(sp, cnt)) continue; break; case OPT_STR: if (O_STR(sp, cnt) == O_D_STR(sp, cnt) || (O_D_STR(sp, cnt) != NULL && !strcmp(O_STR(sp, cnt), O_D_STR(sp, cnt)))) continue; break; } break; case SELECT_DISPLAY: /* Display selected. */ if (!F_ISSET(&sp->opts[cnt], OPT_SELECTED)) continue; break; default: case NO_DISPLAY: abort(); } F_CLR(&sp->opts[cnt], OPT_SELECTED); curlen = STRLEN(op->name); switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: if (!O_ISSET(sp, cnt)) curlen += 2; break; case OPT_NUM: (void)snprintf(nbuf, sizeof(nbuf), "%ld", O_VAL(sp, cnt)); curlen += strlen(nbuf); break; case OPT_STR: if (O_STR(sp, cnt) != NULL) curlen += strlen(O_STR(sp, cnt)); curlen += 3; break; } /* Offset by 2 so there's a gap. */ if (curlen <= colwidth - 2) s_op[s_num++] = cnt; else b_op[b_num++] = cnt; } if (s_num > 0) { /* Figure out the number of rows. */ if (s_num > numcols) { numrows = s_num / numcols; if (s_num % numcols) ++numrows; } else numrows = 1; /* Display the options in sorted order. */ for (row = 0; row < numrows;) { for (base = row, col = 0; col < numcols; ++col) { cnt = opts_print(sp, &optlist[s_op[base]]); if ((base += numrows) >= s_num) break; (void)ex_printf(sp, "%*s", (int)(colwidth - cnt), ""); } if (++row < numrows || b_num) (void)ex_puts(sp, "\n"); } } for (row = 0; row < b_num;) { (void)opts_print(sp, &optlist[b_op[row]]); if (++row < b_num) (void)ex_puts(sp, "\n"); } (void)ex_puts(sp, "\n"); } /* * opts_print -- * Print out an option. */ static int opts_print(SCR *sp, OPTLIST const *op) { int curlen, offset; + const char *p; curlen = 0; offset = op - optlist; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: curlen += ex_printf(sp, "%s"WS, O_ISSET(sp, offset) ? "" : "no", op->name); break; case OPT_NUM: curlen += ex_printf(sp, WS"=%ld", op->name, O_VAL(sp, offset)); break; case OPT_STR: - curlen += ex_printf(sp, WS"=\"%s\"", op->name, - O_STR(sp, offset) == NULL ? "" : O_STR(sp, offset)); + curlen += ex_printf(sp, WS"=\"", op->name); + p = O_STR(sp, offset); + /* Keep correct count for unprintable character sequences */ + if (p != NULL) + for (; *p != '\0'; ++p) + curlen += ex_puts(sp, unctrl(*p)); + curlen += ex_puts(sp, "\""); break; } return (curlen); } /* * opts_save -- * Write the current configuration to a file. * * PUBLIC: int opts_save(SCR *, FILE *); */ int opts_save(SCR *sp, FILE *fp) { OPTLIST const *op; CHAR_T ch, *p; char nch, *np; int cnt; for (op = optlist; op->name != NULL; ++op) { if (F_ISSET(op, OPT_NOSAVE)) continue; cnt = op - optlist; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: if (O_ISSET(sp, cnt)) (void)fprintf(fp, "set "WS"\n", op->name); else (void)fprintf(fp, "set no"WS"\n", op->name); break; case OPT_NUM: (void)fprintf(fp, "set "WS"=%-3ld\n", op->name, O_VAL(sp, cnt)); break; case OPT_STR: if (O_STR(sp, cnt) == NULL) break; (void)fprintf(fp, "set "); for (p = op->name; (ch = *p) != '\0'; ++p) { if (cmdskip(ch) || ch == '\\') (void)putc('\\', fp); fprintf(fp, WC, ch); } (void)putc('=', fp); for (np = O_STR(sp, cnt); (nch = *np) != '\0'; ++np) { if (cmdskip(nch) || nch == '\\') (void)putc('\\', fp); (void)putc(nch, fp); } (void)putc('\n', fp); break; } if (ferror(fp)) { msgq(sp, M_SYSERR, NULL); return (1); } } return (0); } /* * opts_search -- * Search for an option. * * PUBLIC: OPTLIST const *opts_search(CHAR_T *); */ OPTLIST const * opts_search(CHAR_T *name) { OPTLIST const *op, *found; OABBREV atmp, *ap; OPTLIST otmp; size_t len; /* Check list of abbreviations. */ atmp.name = name; if ((ap = bsearch(&atmp, abbrev, sizeof(abbrev) / sizeof(OABBREV) - 1, sizeof(OABBREV), opts_abbcmp)) != NULL) return (optlist + ap->offset); /* Check list of options. */ otmp.name = name; if ((op = bsearch(&otmp, optlist, sizeof(optlist) / sizeof(OPTLIST) - 1, sizeof(OPTLIST), opts_cmp)) != NULL) return (op); /* * Check to see if the name is the prefix of one (and only one) * option. If so, return the option. */ len = STRLEN(name); for (found = NULL, op = optlist; op->name != NULL; ++op) { if (op->name[0] < name[0]) continue; if (op->name[0] > name[0]) break; if (!MEMCMP(op->name, name, len)) { if (found != NULL) return (NULL); found = op; } } return (found); } /* * opts_nomatch -- * Standard nomatch error message for options. * * PUBLIC: void opts_nomatch(SCR *, CHAR_T *); */ void opts_nomatch(SCR *sp, CHAR_T *name) { msgq_wstr(sp, M_ERR, name, "033|set: no %s option: 'set all' gives all option values"); } static int opts_abbcmp(const void *a, const void *b) { return(STRCMP(((OABBREV *)a)->name, ((OABBREV *)b)->name)); } static int opts_cmp(const void *a, const void *b) { return(STRCMP(((OPTLIST *)a)->name, ((OPTLIST *)b)->name)); } /* * opts_copy -- * Copy a screen's OPTION array. * * PUBLIC: int opts_copy(SCR *, SCR *); */ int opts_copy(SCR *orig, SCR *sp) { int cnt, rval; /* Copy most everything without change. */ memcpy(sp->opts, orig->opts, sizeof(orig->opts)); /* Copy the string edit options. */ for (cnt = rval = 0; cnt < O_OPTIONCOUNT; ++cnt) { if (optlist[cnt].type != OPT_STR || F_ISSET(&sp->opts[cnt], OPT_GLOBAL)) continue; /* * If never set, or already failed, NULL out the entries -- * have to continue after failure, otherwise would have two * screens referencing the same memory. */ if (rval || O_STR(sp, cnt) == NULL) { o_set(sp, cnt, OS_NOFREE | OS_STR, NULL, 0); o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0); continue; } /* Copy the current string. */ if (o_set(sp, cnt, OS_NOFREE | OS_STRDUP, O_STR(sp, cnt), 0)) { o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0); goto nomem; } /* Copy the default string. */ if (O_D_STR(sp, cnt) != NULL && o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STRDUP, O_D_STR(sp, cnt), 0)) { nomem: msgq(orig, M_SYSERR, NULL); rval = 1; } } return (rval); } /* * opts_free -- * Free all option strings * * PUBLIC: void opts_free(SCR *); */ void opts_free(SCR *sp) { int cnt; for (cnt = 0; cnt < O_OPTIONCOUNT; ++cnt) { if (optlist[cnt].type != OPT_STR || F_ISSET(&sp->opts[cnt], OPT_GLOBAL)) continue; free(O_STR(sp, cnt)); free(O_D_STR(sp, cnt)); } } Index: vendor/nvi/dist/common/put.c =================================================================== --- vendor/nvi/dist/common/put.c (revision 366306) +++ vendor/nvi/dist/common/put.c (revision 366307) @@ -1,223 +1,224 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include "common.h" /* * put -- * Put text buffer contents into the file. * * PUBLIC: int put(SCR *, CB *, CHAR_T *, MARK *, MARK *, int); */ int put(SCR *sp, CB *cbp, CHAR_T *namep, MARK *cp, MARK *rp, int append) { CHAR_T name; TEXT *ltp, *tp; recno_t lno; size_t blen, clen, len; int rval; CHAR_T *bp, *t; CHAR_T *p; - if (cbp == NULL) + if (cbp == NULL) { if (namep == NULL) { cbp = sp->gp->dcbp; if (cbp == NULL) { msgq(sp, M_ERR, "053|The default buffer is empty"); return (1); } } else { name = *namep; CBNAME(sp, cbp, name); if (cbp == NULL) { msgq(sp, M_ERR, "054|Buffer %s is empty", KEY_NAME(sp, name)); return (1); } } + } tp = TAILQ_FIRST(cbp->textq); /* * It's possible to do a put into an empty file, meaning that the cut * buffer simply becomes the file. It's a special case so that we can * ignore it in general. * * !!! * Historically, pasting into a file with no lines in vi would preserve * the single blank line. This is surely a result of the fact that the * historic vi couldn't deal with a file that had no lines in it. This * implementation treats that as a bug, and does not retain the blank * line. * * Historical practice is that the cursor ends at the first character * in the file. */ if (cp->lno == 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { for (; tp != NULL; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) return (1); rp->lno = 1; rp->cno = 0; return (0); } } /* If a line mode buffer, append each new line into the file. */ if (F_ISSET(cbp, CB_LMODE)) { lno = append ? cp->lno : cp->lno - 1; rp->lno = lno + 1; for (; tp != NULL; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) return (1); rp->cno = 0; (void)nonblank(sp, rp->lno, &rp->cno); return (0); } /* * If buffer was cut in character mode, replace the current line with * one built from the portion of the first line to the left of the * split plus the first line in the CB. Append each intermediate line * in the CB. Append a line built from the portion of the first line * to the right of the split plus the last line in the CB. * * Get the first line. */ lno = cp->lno; if (db_get(sp, lno, DBG_FATAL, &p, &len)) return (1); GET_SPACE_RETW(sp, bp, blen, tp->len + len + 1); t = bp; /* Original line, left of the split. */ if (len > 0 && (clen = cp->cno + (append ? 1 : 0)) > 0) { MEMCPY(bp, p, clen); p += clen; t += clen; } /* First line from the CB. */ if (tp->len != 0) { MEMCPY(t, tp->lb, tp->len); t += tp->len; } /* Calculate length left in the original line. */ clen = len == 0 ? 0 : len - (cp->cno + (append ? 1 : 0)); /* * !!! * In the historical 4BSD version of vi, character mode puts within * a single line have two cursor behaviors: if the put is from the * unnamed buffer, the cursor moves to the character inserted which * appears last in the file. If the put is from a named buffer, * the cursor moves to the character inserted which appears first * in the file. In System III/V, it was changed at some point and * the cursor always moves to the first character. In both versions * of vi, character mode puts that cross line boundaries leave the * cursor on the first character. Nvi implements the System III/V * behavior, and expect POSIX.2 to do so as well. */ rp->lno = lno; rp->cno = len == 0 ? 0 : sp->cno + (append && tp->len ? 1 : 0); /* * If no more lines in the CB, append the rest of the original * line and quit. Otherwise, build the last line before doing * the intermediate lines, because the line changes will lose * the cached line. */ if (TAILQ_NEXT(tp, q) == NULL) { if (clen > 0) { MEMCPY(t, p, clen); t += clen; } if (db_set(sp, lno, bp, t - bp)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } } else { /* * Have to build both the first and last lines of the * put before doing any sets or we'll lose the cached * line. Build both the first and last lines in the * same buffer, so we don't have to have another buffer * floating around. * * Last part of original line; check for space, reset * the pointer into the buffer. */ ltp = TAILQ_LAST(cbp->textq, _texth); len = t - bp; ADD_SPACE_RETW(sp, bp, blen, ltp->len + clen); t = bp + len; /* Add in last part of the CB. */ MEMCPY(t, ltp->lb, ltp->len); if (clen) MEMCPY(t + ltp->len, p, clen); clen += ltp->len; /* * Now: bp points to the first character of the first * line, t points to the last character of the last * line, t - bp is the length of the first line, and * clen is the length of the last. Just figured you'd * want to know. * * Output the line replacing the original line. */ if (db_set(sp, lno, bp, t - bp)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* Output any intermediate lines in the CB. */ for (tp = TAILQ_NEXT(tp, q); TAILQ_NEXT(tp, q) != NULL; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) goto err; if (db_append(sp, 1, lno, t, clen)) goto err; ++sp->rptlines[L_ADDED]; } rval = 0; if (0) err: rval = 1; FREE_SPACEW(sp, bp, blen); return (rval); } Index: vendor/nvi/dist/common/recover.c =================================================================== --- vendor/nvi/dist/common/recover.c (revision 366306) +++ vendor/nvi/dist/common/recover.c (revision 366307) @@ -1,941 +1,940 @@ /*- * 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" #include #include #include /* * We include , because the open #defines were found there * on historical systems. We also include because the open(2) * #defines are found there on newer systems. */ #include #include #include #include #include #include #include #include /* Required by resolv.h. */ #include #include #include #include #include #include #include "../ex/version.h" #include "common.h" #include "pathnames.h" /* * Recovery code. * * The basic scheme is as follows. In the EXF structure, we maintain full * paths of a b+tree file and a mail recovery file. The former is the file * used as backing store by the DB package. The latter is the file that * contains an email message to be sent to the user if we crash. The two * simple states of recovery are: * * + first starting the edit session: * the b+tree file exists and is mode 700, the mail recovery * file doesn't exist. * + after the file has been modified: * the b+tree file exists and is mode 600, the mail recovery * file exists, and is exclusively locked. * * In the EXF structure we maintain a file descriptor that is the locked * file descriptor for the mail recovery file. * * To find out if a recovery file/backing file pair are in use, try to get * a lock on the recovery file. * * To find out if a backing file can be deleted at boot time, check for an * owner execute bit. (Yes, I know it's ugly, but it's either that or put * special stuff into the backing file itself, or correlate the files at * boot time, neither of which looks like fun.) Note also that there's a * window between when the file is created and the X bit is set. It's small, * but it's there. To fix the window, check for 0 length files as well. * * To find out if a file can be recovered, check the F_RCV_ON bit. Note, * this DOES NOT mean that any initialization has been done, only that we * haven't yet failed at setting up or doing recovery. * * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit. * If that bit is not set when ending a file session: * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL, * they are unlink(2)'d, and free(3)'d. * If the EXF file descriptor (rcv_fd) is not -1, it is closed. * * The backing b+tree file is set up when a file is first edited, so that * the DB package can use it for on-disk caching and/or to snapshot the * file. When the file is first modified, the mail recovery file is created, * the backing file permissions are updated, the file is sync(2)'d to disk, * and the timer is started. Then, at RCV_PERIOD second intervals, the * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which * means that the data structures (SCR, EXF, the underlying tree structures) * must be consistent when the signal arrives. * * The recovery mail file contains normal mail headers, with two additional * * X-vi-data: ; * * MIME headers; the folding character is limited to ' '. * * Btree files are named "vi.XXXXXX" and recovery files are named * "recover.XXXXXX". */ #define VI_DHEADER "X-vi-data:" static int rcv_copy(SCR *, int, char *); static void rcv_email(SCR *, char *); static int rcv_mailfile(SCR *, int, char *); static int rcv_mktemp(SCR *, char *, char *); static int rcv_dlnwrite(SCR *, const char *, const char *, FILE *); static int rcv_dlnread(SCR *, char **, char **, FILE *); /* * rcv_tmp -- * Build a file name that will be used as the recovery file. * * PUBLIC: int rcv_tmp(SCR *, EXF *, char *); */ int rcv_tmp(SCR *sp, EXF *ep, char *name) { struct stat sb; int fd; char *dp, *path; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * * * If the recovery directory doesn't exist, try and create it. As * the recovery files are themselves protected from reading/writing * by other than the owner, the worst that can happen is that a user * would have permission to remove other user's recovery files. If * the sticky bit has the BSD semantics, that too will be impossible. */ if (opts_empty(sp, O_RECDIR, 0)) goto err; dp = O_STR(sp, O_RECDIR); if (stat(dp, &sb)) { if (errno != ENOENT || mkdir(dp, 0)) { msgq(sp, M_SYSERR, "%s", dp); goto err; } (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX); } if ((path = join(dp, "vi.XXXXXX")) == NULL) goto err; if ((fd = rcv_mktemp(sp, path, dp)) == -1) { free(path); goto err; } (void)fchmod(fd, S_IRWXU); (void)close(fd); ep->rcv_path = path; if (0) { err: msgq(sp, M_ERR, "056|Modifications not recoverable if the session fails"); return (1); } /* We believe the file is recoverable. */ F_SET(ep, F_RCV_ON); return (0); } /* * rcv_init -- * Force the file to be snapshotted for recovery. * * PUBLIC: int rcv_init(SCR *); */ int rcv_init(SCR *sp) { EXF *ep; recno_t lno; ep = sp->ep; /* Only do this once. */ F_CLR(ep, F_FIRSTMODIFY); /* If we already know the file isn't recoverable, we're done. */ if (!F_ISSET(ep, F_RCV_ON)) return (0); /* Turn off recoverability until we figure out if this will work. */ F_CLR(ep, F_RCV_ON); /* Test if we're recovering a file, not editing one. */ if (ep->rcv_mpath == NULL) { /* Build a file to mail to the user. */ if (rcv_mailfile(sp, 0, NULL)) goto err; /* Force a read of the entire file. */ if (db_last(sp, &lno)) goto err; /* Turn on a busy message, and sync it to backing store. */ sp->gp->scr_busy(sp, "057|Copying file for recovery...", BUSY_ON); if (ep->db->sync(ep->db, R_RECNOSYNC)) { msgq_str(sp, M_SYSERR, ep->rcv_path, "058|Preservation failed: %s"); sp->gp->scr_busy(sp, NULL, BUSY_OFF); goto err; } sp->gp->scr_busy(sp, NULL, BUSY_OFF); } /* Turn off the owner execute bit. */ (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); /* We believe the file is recoverable. */ F_SET(ep, F_RCV_ON); return (0); err: msgq(sp, M_ERR, "059|Modifications not recoverable if the session fails"); return (1); } /* * rcv_sync -- * Sync the file, optionally: * flagging the backup file to be preserved * snapshotting the backup file and send email to the user * sending email to the user if the file was modified * ending the file session * * PUBLIC: int rcv_sync(SCR *, u_int); */ int rcv_sync(SCR *sp, u_int flags) { EXF *ep; int fd, rval; char *dp, *buf; /* Make sure that there's something to recover/sync. */ ep = sp->ep; if (ep == NULL || !F_ISSET(ep, F_RCV_ON)) return (0); /* Sync the file if it's been modified. */ if (F_ISSET(ep, F_MODIFIED)) { if (ep->db->sync(ep->db, R_RECNOSYNC)) { F_CLR(ep, F_RCV_ON | F_RCV_NORM); msgq_str(sp, M_SYSERR, ep->rcv_path, "060|File backup failed: %s"); return (1); } /* REQUEST: don't remove backing file on exit. */ if (LF_ISSET(RCV_PRESERVE)) F_SET(ep, F_RCV_NORM); /* REQUEST: send email. */ if (LF_ISSET(RCV_EMAIL)) rcv_email(sp, ep->rcv_mpath); } /* * !!! * Each time the user exec's :preserve, we have to snapshot all of * the recovery information, i.e. it's like the user re-edited the * file. We copy the DB(3) backing file, and then create a new mail * recovery file, it's simpler than exiting and reopening all of the * underlying files. * * REQUEST: snapshot the file. */ rval = 0; if (LF_ISSET(RCV_SNAPSHOT)) { if (opts_empty(sp, O_RECDIR, 0)) goto err; dp = O_STR(sp, O_RECDIR); if ((buf = join(dp, "vi.XXXXXX")) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } if ((fd = rcv_mktemp(sp, buf, dp)) == -1) { free(buf); goto err; } sp->gp->scr_busy(sp, "061|Copying file for recovery...", BUSY_ON); if (rcv_copy(sp, fd, ep->rcv_path) || close(fd) || rcv_mailfile(sp, 1, buf)) { (void)unlink(buf); (void)close(fd); rval = 1; } free(buf); sp->gp->scr_busy(sp, NULL, BUSY_OFF); } if (0) { err: rval = 1; } /* REQUEST: end the file session. */ if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1)) rval = 1; return (rval); } /* * rcv_mailfile -- * Build the file to mail to the user. */ static int rcv_mailfile(SCR *sp, int issync, char *cp_path) { EXF *ep; GS *gp; struct passwd *pw; int len; time_t now; uid_t uid; int fd; FILE *fp; char *dp, *p, *t, *qt, *buf, *mpath; char *t1, *t2, *t3; int st; /* * XXX * MAXHOSTNAMELEN/HOST_NAME_MAX are deprecated. We try sysconf(3) * first, then fallback to _POSIX_HOST_NAME_MAX. */ char *host; long hostmax = sysconf(_SC_HOST_NAME_MAX); if (hostmax < 0) hostmax = _POSIX_HOST_NAME_MAX; gp = sp->gp; if ((pw = getpwuid(uid = getuid())) == NULL) { msgq(sp, M_ERR, "062|Information on user id %u not found", uid); return (1); } if (opts_empty(sp, O_RECDIR, 0)) return (1); dp = O_STR(sp, O_RECDIR); if ((mpath = join(dp, "recover.XXXXXX")) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } if ((fd = rcv_mktemp(sp, mpath, dp)) == -1) { free(mpath); return (1); } if ((fp = fdopen(fd, "w")) == NULL) { free(mpath); close(fd); return (1); } /* * XXX * We keep an open lock on the file so that the recover option can * distinguish between files that are live and those that need to * be recovered. There's an obvious window between the mkstemp call * and the lock, but it's pretty small. */ ep = sp->ep; if (file_lock(sp, NULL, fd, 1) != LOCK_SUCCESS) msgq(sp, M_SYSERR, "063|Unable to lock recovery file"); if (!issync) { /* Save the recover file descriptor, and mail path. */ ep->rcv_fd = dup(fd); ep->rcv_mpath = mpath; cp_path = ep->rcv_path; } t = sp->frp->name; if ((p = strrchr(t, '/')) == NULL) p = t; else ++p; (void)time(&now); if ((st = rcv_dlnwrite(sp, "file", t, fp))) { if (st == 1) goto werr; goto err; } if ((st = rcv_dlnwrite(sp, "path", cp_path, fp))) { if (st == 1) goto werr; goto err; } MALLOC(sp, host, hostmax + 1); if (host == NULL) goto err; (void)gethostname(host, hostmax + 1); len = fprintf(fp, "%s%s%s\n%s%s%s%s\n%s%.40s\n%s\n\n", "From: root@", host, " (Nvi recovery program)", "To: ", pw->pw_name, "@", host, "Subject: Nvi saved the file ", p, "Precedence: bulk"); /* For vacation(1). */ if (len < 0) { free(host); goto werr; } if ((qt = quote(t)) == NULL) { free(host); msgq(sp, M_SYSERR, NULL); goto err; } len = asprintf(&buf, "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", "On ", ctime(&now), ", the user ", pw->pw_name, " was editing a file named ", t, " on the machine ", host, ", when it was saved for recovery. ", "You can recover most, if not all, of the changes ", "to this file using the -r option to ", getprogname(), ":\n\n\t", getprogname(), " -r ", qt); free(qt); free(host); - if (buf == NULL) { + if (len == -1) { msgq(sp, M_SYSERR, NULL); goto err; } /* * Format the message. (Yes, I know it's silly.) * Requires that the message end in a . */ #define FMTCOLS 60 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { /* Check for a short length. */ if (len <= FMTCOLS) { t2 = t1 + (len - 1); goto wout; } /* Check for a required . */ t2 = strchr(t1, '\n'); if (t2 - t1 <= FMTCOLS) goto wout; /* Find the closest space, if any. */ for (t3 = t2; t2 > t1; --t2) if (*t2 == ' ') { if (t2 - t1 <= FMTCOLS) goto wout; t3 = t2; } t2 = t3; /* t2 points to the last character to display. */ wout: *t2++ = '\n'; /* t2 points one after the last character to display. */ if (fwrite(t1, 1, t2 - t1, fp) != t2 - t1) { free(buf); goto werr; } } if (issync) { fflush(fp); rcv_email(sp, mpath); free(mpath); } if (fclose(fp)) { free(buf); werr: msgq(sp, M_SYSERR, "065|Recovery file"); goto err; } free(buf); return (0); err: if (!issync) ep->rcv_fd = -1; if (fp != NULL) (void)fclose(fp); return (1); } /* * people making love * never exactly the same * just like a snowflake * * rcv_list -- * List the files that can be recovered by this user. * * PUBLIC: int rcv_list(SCR *); */ int rcv_list(SCR *sp) { struct dirent *dp; struct stat sb; DIR *dirp; FILE *fp; int found; char *p, *file, *path; char *dtype, *data; int st; /* Open the recovery directory for reading. */ if (opts_empty(sp, O_RECDIR, 0)) return (1); p = O_STR(sp, O_RECDIR); if (chdir(p) || (dirp = opendir(".")) == NULL) { msgq_str(sp, M_SYSERR, p, "recdir: %s"); return (1); } /* Read the directory. */ for (found = 0; (dp = readdir(dirp)) != NULL;) { if (strncmp(dp->d_name, "recover.", 8)) continue; /* If it's readable, it's recoverable. */ if ((fp = fopen(dp->d_name, "r")) == NULL) continue; switch (file_lock(sp, NULL, fileno(fp), 1)) { case LOCK_FAILED: /* * XXX * Assume that a lock can't be acquired, but that we * should permit recovery anyway. If this is wrong, * and someone else is using the file, we're going to * die horribly. */ break; case LOCK_SUCCESS: break; case LOCK_UNAVAIL: /* If it's locked, it's live. */ (void)fclose(fp); continue; } /* Check the headers. */ for (file = NULL, path = NULL; file == NULL || path == NULL;) { if ((st = rcv_dlnread(sp, &dtype, &data, fp))) { if (st == 1) msgq_str(sp, M_ERR, dp->d_name, "066|%s: malformed recovery file"); goto next; } if (dtype == NULL) continue; if (!strcmp(dtype, "file")) file = data; else if (!strcmp(dtype, "path")) path = data; else free(data); } /* * If the file doesn't exist, it's an orphaned recovery file, * toss it. * * XXX * This can occur if the backup file was deleted and we crashed * before deleting the email file. */ errno = 0; if (stat(path, &sb) && errno == ENOENT) { (void)unlink(dp->d_name); goto next; } /* Get the last modification time and display. */ (void)fstat(fileno(fp), &sb); (void)printf("%.24s: %s\n", ctime(&sb.st_mtime), file); found = 1; /* Close, discarding lock. */ next: (void)fclose(fp); free(file); free(path); } if (found == 0) (void)printf("%s: No files to recover\n", getprogname()); (void)closedir(dirp); return (0); } /* * rcv_read -- * Start a recovered file as the file to edit. * * PUBLIC: int rcv_read(SCR *, FREF *); */ int rcv_read(SCR *sp, FREF *frp) { struct dirent *dp; struct stat sb; DIR *dirp; FILE *fp; EXF *ep; struct timespec rec_mtim = { 0, 0 }; int found, locked = 0, requested, sv_fd; char *name, *p, *t, *rp, *recp, *pathp; char *file, *path, *recpath; char *dtype, *data; int st; if (opts_empty(sp, O_RECDIR, 0)) return (1); rp = O_STR(sp, O_RECDIR); if ((dirp = opendir(rp)) == NULL) { msgq_str(sp, M_SYSERR, rp, "%s"); return (1); } name = frp->name; sv_fd = -1; recp = pathp = NULL; for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { if (strncmp(dp->d_name, "recover.", 8)) continue; if ((recpath = join(rp, dp->d_name)) == NULL) { msgq(sp, M_SYSERR, NULL); continue; } /* If it's readable, it's recoverable. */ if ((fp = fopen(recpath, "r")) == NULL) { free(recpath); continue; } switch (file_lock(sp, NULL, fileno(fp), 1)) { case LOCK_FAILED: /* * XXX * Assume that a lock can't be acquired, but that we * should permit recovery anyway. If this is wrong, * and someone else is using the file, we're going to * die horribly. */ locked = 0; break; case LOCK_SUCCESS: locked = 1; break; case LOCK_UNAVAIL: /* If it's locked, it's live. */ (void)fclose(fp); continue; } /* Check the headers. */ for (file = NULL, path = NULL; file == NULL || path == NULL;) { if ((st = rcv_dlnread(sp, &dtype, &data, fp))) { if (st == 1) msgq_str(sp, M_ERR, dp->d_name, "067|%s: malformed recovery file"); goto next; } if (dtype == NULL) continue; if (!strcmp(dtype, "file")) file = data; else if (!strcmp(dtype, "path")) path = data; else free(data); } ++found; /* * If the file doesn't exist, it's an orphaned recovery file, * toss it. * * XXX * This can occur if the backup file was deleted and we crashed * before deleting the email file. */ errno = 0; if (stat(path, &sb) && errno == ENOENT) { (void)unlink(dp->d_name); goto next; } /* Check the file name. */ if (strcmp(file, name)) goto next; ++requested; /* If we've found more than one, take the most recent. */ (void)fstat(fileno(fp), &sb); if (recp == NULL || - timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) { + timespeccmp(&rec_mtim, &sb.st_mtim, <)) { p = recp; t = pathp; recp = recpath; pathp = path; if (p != NULL) { free(p); free(t); } - rec_mtim = sb.st_mtimespec; + rec_mtim = sb.st_mtim; if (sv_fd != -1) (void)close(sv_fd); sv_fd = dup(fileno(fp)); } else { next: free(recpath); free(path); } (void)fclose(fp); free(file); } (void)closedir(dirp); if (recp == NULL) { msgq_str(sp, M_INFO, name, "068|No files named %s, readable by you, to recover"); return (1); } if (found) { if (requested > 1) msgq(sp, M_INFO, "069|There are older versions of this file for you to recover"); if (found > requested) msgq(sp, M_INFO, "070|There are other files for you to recover"); } /* * Create the FREF structure, start the btree file. * * XXX * file_init() is going to set ep->rcv_path. */ if (file_init(sp, frp, pathp, 0)) { free(recp); free(pathp); (void)close(sv_fd); return (1); } free(pathp); /* * We keep an open lock on the file so that the recover option can * distinguish between files that are live and those that need to * be recovered. The lock is already acquired, just copy it. */ ep = sp->ep; ep->rcv_mpath = recp; ep->rcv_fd = sv_fd; if (!locked) F_SET(frp, FR_UNLOCKED); /* We believe the file is recoverable. */ F_SET(ep, F_RCV_ON); return (0); } /* * rcv_copy -- * Copy a recovery file. */ static int rcv_copy(SCR *sp, int wfd, char *fname) { int nr, nw, off, rfd; char buf[8 * 1024]; if ((rfd = open(fname, O_RDONLY, 0)) == -1) goto err; while ((nr = read(rfd, buf, sizeof(buf))) > 0) for (off = 0; nr; nr -= nw, off += nw) if ((nw = write(wfd, buf + off, nr)) < 0) goto err; if (nr == 0) return (0); err: msgq_str(sp, M_SYSERR, fname, "%s"); return (1); } /* * rcv_mktemp -- * Paranoid make temporary file routine. */ static int rcv_mktemp(SCR *sp, char *path, char *dname) { int fd; if ((fd = mkstemp(path)) == -1) msgq_str(sp, M_SYSERR, dname, "%s"); return (fd); } /* * rcv_email -- * Send email. */ static void rcv_email(SCR *sp, char *fname) { char *buf; - (void)asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname); - if (buf == NULL) { + if (asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname) == -1) { msgq_str(sp, M_ERR, strerror(errno), "071|not sending email: %s"); return; } (void)system(buf); free(buf); } /* * rcv_dlnwrite -- * Encode a string into an X-vi-data line and write it. */ static int rcv_dlnwrite(SCR *sp, const char *dtype, const char *src, FILE *fp) { char *bp = NULL, *p; size_t blen = 0; size_t dlen, len; int plen, xlen; len = strlen(src); dlen = strlen(dtype); GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2); (void)memcpy(bp, dtype, dlen); bp[dlen] = ';'; if ((xlen = b64_ntop((u_char *)src, len, bp + dlen + 1, blen)) == -1) goto err; xlen += dlen + 1; /* Output as an MIME folding header. */ if ((plen = fprintf(fp, VI_DHEADER " %.*s\n", FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0) goto err; plen -= (int)sizeof(VI_DHEADER) + 1; for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) { p += plen; if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0) goto err; plen -= 2; } FREE_SPACE(sp, bp, blen); return (0); err: FREE_SPACE(sp, bp, blen); return (1); alloc_err: msgq(sp, M_SYSERR, NULL); return (-1); } /* * rcv_dlnread -- * Read an X-vi-data line and decode it. */ static int rcv_dlnread(SCR *sp, char **dtypep, char **datap, /* free *datap if != NULL after use. */ FILE *fp) { int ch; char buf[1024]; char *bp = NULL, *p, *src; size_t blen = 0; size_t len, off, dlen; char *dtype, *data; int xlen; if (fgets(buf, sizeof(buf), fp) == NULL) return (1); if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) { *dtypep = NULL; *datap = NULL; return (0); } /* Fetch an MIME folding header. */ len = strlen(buf) - sizeof(VI_DHEADER) + 1; GET_SPACE_GOTOC(sp, bp, blen, len); (void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len); p = bp + len; while ((ch = fgetc(fp)) == ' ') { if (fgets(buf, sizeof(buf), fp) == NULL) goto err; off = strlen(buf); len += off; ADD_SPACE_GOTOC(sp, bp, blen, len); p = bp + len - off; (void)memcpy(p, buf, off); } bp[len] = '\0'; (void)ungetc(ch, fp); for (p = bp; *p == ' ' || *p == '\n'; p++); if ((src = strchr(p, ';')) == NULL) goto err; dlen = src - p; src += 1; len -= src - bp; /* Memory looks like: "\0\0". */ MALLOC(sp, data, dlen + len / 4 * 3 + 2); if (data == NULL) goto err; if ((xlen = (b64_pton(p + dlen + 1, (u_char *)data, len / 4 * 3 + 1))) == -1) { free(data); goto err; } data[xlen] = '\0'; dtype = data + xlen + 1; (void)memcpy(dtype, p, dlen); dtype[dlen] = '\0'; FREE_SPACE(sp, bp, blen); *dtypep = dtype; *datap = data; return (0); err: FREE_SPACE(sp, bp, blen); return (1); alloc_err: msgq(sp, M_SYSERR, NULL); return (-1); } Index: vendor/nvi/dist/common/util.c =================================================================== --- vendor/nvi/dist/common/util.c (revision 366306) +++ vendor/nvi/dist/common/util.c (revision 366307) @@ -1,377 +1,383 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #ifdef __APPLE__ #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include "common.h" /* * binc -- * Increase the size of a buffer. * * PUBLIC: void *binc(SCR *, void *, size_t *, size_t); */ void * binc(SCR *sp, /* sp MAY BE NULL!!! */ void *bp, size_t *bsizep, size_t min) { size_t csize; /* If already larger than the minimum, just return. */ if (min && *bsizep >= min) return (bp); csize = p2roundup(MAX(min, 256)); REALLOC(sp, bp, void *, csize); if (bp == NULL) { *bsizep = 0; return (NULL); } /* * Memory is guaranteed to be zero-filled, various parts of * nvi depend on this. */ memset((char *)bp + *bsizep, 0, csize - *bsizep); *bsizep = csize; return (bp); } /* * nonblank -- * Set the column number of the first non-blank character * including or after the starting column. On error, set * the column to 0, it's safest. * * PUBLIC: int nonblank(SCR *, recno_t, size_t *); */ int nonblank(SCR *sp, recno_t lno, size_t *cnop) { CHAR_T *p; size_t cnt, len, off; int isempty; /* Default. */ off = *cnop; *cnop = 0; /* Get the line, succeeding in an empty file. */ if (db_eget(sp, lno, &p, &len, &isempty)) return (!isempty); /* Set the offset. */ if (len == 0 || off >= len) return (0); for (cnt = off, p = &p[off], len -= off; len && ISBLANK(*p); ++cnt, ++p, --len); /* Set the return. */ *cnop = len ? cnt : cnt - 1; return (0); } /* * join -- * Join two paths; need free. * * PUBLIC: char *join(char *, char *); */ char * join(char *path1, char *path2) { char *p; if (path1[0] == '\0' || path2[0] == '/') return strdup(path2); - (void)asprintf(&p, path1[strlen(path1)-1] == '/' ? - "%s%s" : "%s/%s", path1, path2); + if (asprintf(&p, path1[strlen(path1)-1] == '/' ? + "%s%s" : "%s/%s", path1, path2) == -1) + return NULL; return p; } /* * expanduser -- * Return a "~" or "~user" expanded path; need free. * * PUBLIC: char *expanduser(char *); */ char * expanduser(char *str) { struct passwd *pwd; char *p, *t, *u, *h; /* * This function always expands the content between the * leading '~' and the first '/' or '\0' from the input. * Return NULL whenever we fail to do so. */ if (*str != '~') return (NULL); p = str + 1; for (t = p; *t != '/' && *t != '\0'; ++t) continue; if (t == p) { /* ~ */ +#ifdef __GLIBC__ + extern char *secure_getenv(const char *); + if ((h = secure_getenv("HOME")) == NULL) { +#else if (issetugid() != 0 || (h = getenv("HOME")) == NULL) { +#endif if (((h = getlogin()) != NULL && (pwd = getpwnam(h)) != NULL) || (pwd = getpwuid(getuid())) != NULL) h = pwd->pw_dir; else return (NULL); } } else { /* ~user */ if ((u = strndup(p, t - p)) == NULL) return (NULL); if ((pwd = getpwnam(u)) == NULL) { free(u); return (NULL); } else h = pwd->pw_dir; free(u); } for (; *t == '/' && *t != '\0'; ++t) continue; return (join(h, t)); } /* * quote -- * Return a escaped string for /bin/sh; need free. * * PUBLIC: char *quote(char *); */ char * quote(char *str) { char *p, *t; size_t i = 0, n = 0; int unsafe = 0; for (p = str; *p != '\0'; p++, i++) { if (*p == '\'') n++; if (unsafe) continue; if (isascii((u_char)*p)) { if (isalnum((u_char)*p)) continue; switch (*p) { case '%': case '+': case ',': case '-': case '.': case '/': case ':': case '=': case '@': case '_': continue; } } unsafe = 1; } if (!unsafe) t = strdup(str); #define SQT "'\\''" else if ((p = t = malloc(i + n * (sizeof(SQT) - 2) + 3)) != NULL) { *p++ = '\''; for (; *str != '\0'; str++) { if (*str == '\'') { (void)memcpy(p, SQT, sizeof(SQT) - 1); p += sizeof(SQT) - 1; } else *p++ = *str; } *p++ = '\''; *p = '\0'; } return t; } /* * v_strdup -- * Strdup for 8-bit character strings with an associated length. * * PUBLIC: char *v_strdup(SCR *, const char *, size_t); */ char * v_strdup(SCR *sp, const char *str, size_t len) { char *copy; MALLOC(sp, copy, len + 1); if (copy == NULL) return (NULL); memcpy(copy, str, len); copy[len] = '\0'; return (copy); } /* * v_wstrdup -- * Strdup for wide character strings with an associated length. * * PUBLIC: CHAR_T *v_wstrdup(SCR *, const CHAR_T *, size_t); */ CHAR_T * v_wstrdup(SCR *sp, const CHAR_T *str, size_t len) { CHAR_T *copy; MALLOC(sp, copy, (len + 1) * sizeof(CHAR_T)); if (copy == NULL) return (NULL); MEMCPY(copy, str, len); copy[len] = '\0'; return (copy); } /* * nget_uslong -- * Get an unsigned long, checking for overflow. * * PUBLIC: enum nresult nget_uslong(u_long *, const CHAR_T *, CHAR_T **, int); */ enum nresult nget_uslong(u_long *valp, const CHAR_T *p, CHAR_T **endp, int base) { errno = 0; *valp = STRTOUL(p, endp, base); if (errno == 0) return (NUM_OK); if (errno == ERANGE && *valp == ULONG_MAX) return (NUM_OVER); return (NUM_ERR); } /* * nget_slong -- * Convert a signed long, checking for overflow and underflow. * * PUBLIC: enum nresult nget_slong(long *, const CHAR_T *, CHAR_T **, int); */ enum nresult nget_slong(long *valp, const CHAR_T *p, CHAR_T **endp, int base) { errno = 0; *valp = STRTOL(p, endp, base); if (errno == 0) return (NUM_OK); if (errno == ERANGE) { if (*valp == LONG_MAX) return (NUM_OVER); if (*valp == LONG_MIN) return (NUM_UNDER); } return (NUM_ERR); } /* * timepoint_steady -- * Get a timestamp from a monotonic clock. * * PUBLIC: void timepoint_steady(struct timespec *); */ void timepoint_steady(struct timespec *ts) { #ifdef __APPLE__ static mach_timebase_info_data_t base = { 0 }; uint64_t val; uint64_t ns; if (base.denom == 0) (void)mach_timebase_info(&base); val = mach_absolute_time(); ns = val * base.numer / base.denom; ts->tv_sec = ns / 1000000000; ts->tv_nsec = ns % 1000000000; #else #ifdef CLOCK_MONOTONIC_FAST (void)clock_gettime(CLOCK_MONOTONIC_FAST, ts); #else (void)clock_gettime(CLOCK_MONOTONIC, ts); #endif #endif } /* * timepoint_system -- * Get the current calendar time. * * PUBLIC: void timepoint_system(struct timespec *); */ void timepoint_system(struct timespec *ts) { #ifdef __APPLE__ clock_serv_t clk; mach_timespec_t mts; kern_return_t kr; kr = host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &clk); if (kr != KERN_SUCCESS) return; (void)clock_get_time(clk, &mts); (void)mach_port_deallocate(mach_task_self(), clk); ts->tv_sec = mts.tv_sec; ts->tv_nsec = mts.tv_nsec; #else #ifdef CLOCK_REALTIME_FAST (void)clock_gettime(CLOCK_REALTIME_FAST, ts); #else (void)clock_gettime(CLOCK_REALTIME, ts); #endif #endif } #ifdef DEBUG #include /* * TRACE -- * debugging trace routine. * * PUBLIC: void TRACE(SCR *, const char *, ...); */ void TRACE(SCR *sp, const char *fmt, ...) { FILE *tfp; va_list ap; if ((tfp = sp->gp->tracefp) == NULL) return; va_start(ap, fmt); (void)vfprintf(tfp, fmt, ap); va_end(ap); (void)fflush(tfp); } #endif Index: vendor/nvi/dist/ex/ex.c =================================================================== --- vendor/nvi/dist/ex/ex.c (revision 366306) +++ vendor/nvi/dist/ex/ex.c (revision 366307) @@ -1,2364 +1,2367 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" #if defined(DEBUG) && defined(COMLOG) static void ex_comlog(SCR *, EXCMD *); #endif static EXCMDLIST const * ex_comm_search(CHAR_T *, size_t); static int ex_discard(SCR *); static int ex_line(SCR *, EXCMD *, MARK *, int *, int *); static int ex_load(SCR *); static void ex_unknown(SCR *, CHAR_T *, size_t); /* * ex -- * Main ex loop. * * PUBLIC: int ex(SCR **); */ int ex(SCR **spp) { EX_PRIVATE *exp; GS *gp; MSGS *mp; SCR *sp; TEXT *tp; u_int32_t flags; sp = *spp; gp = sp->gp; exp = EXP(sp); /* Start the ex screen. */ if (ex_init(sp)) return (1); /* Flush any saved messages. */ while ((mp = SLIST_FIRST(gp->msgq)) != NULL) { gp->scr_msg(sp, mp->mtype, mp->buf, mp->len); SLIST_REMOVE_HEAD(gp->msgq, q); free(mp->buf); free(mp); } /* If reading from a file, errors should have name and line info. */ if (F_ISSET(gp, G_SCRIPTED)) { gp->excmd.if_lno = 1; gp->excmd.if_name = "script"; } /* * !!! * Initialize the text flags. The beautify edit option historically * applied to ex command input read from a file. In addition, the * first time a ^H was discarded from the input, there was a message, * "^H discarded", that was displayed. We don't bother. */ LF_INIT(TXT_BACKSLASH | TXT_CNTRLD | TXT_CR); for (;; ++gp->excmd.if_lno) { /* Display status line and flush. */ if (F_ISSET(sp, SC_STATUS)) { if (!F_ISSET(sp, SC_EX_SILENT)) msgq_status(sp, sp->lno, 0); F_CLR(sp, SC_STATUS); } (void)ex_fflush(sp); /* Set the flags the user can reset. */ if (O_ISSET(sp, O_BEAUTIFY)) LF_SET(TXT_BEAUTIFY); if (O_ISSET(sp, O_PROMPT)) LF_SET(TXT_PROMPT); /* Clear any current interrupts, and get a command. */ CLR_INTERRUPT(sp); if (ex_txt(sp, sp->tiq, ':', flags)) return (1); if (INTERRUPTED(sp)) { (void)ex_puts(sp, "\n"); (void)ex_fflush(sp); continue; } /* Initialize the command structure. */ CLEAR_EX_PARSER(&gp->excmd); /* * If the user entered a single carriage return, send * ex_cmd() a separator -- it discards single newlines. */ tp = TAILQ_FIRST(sp->tiq); if (tp->len == 0) { gp->excmd.cp = L(" "); /* __TK__ why not |? */ gp->excmd.clen = 1; } else { gp->excmd.cp = tp->lb; gp->excmd.clen = tp->len; } F_INIT(&gp->excmd, E_NRSEP); if (ex_cmd(sp) && F_ISSET(gp, G_SCRIPTED)) return (1); if (INTERRUPTED(sp)) { CLR_INTERRUPT(sp); msgq(sp, M_ERR, "170|Interrupted"); } /* * If the last command caused a restart, or switched screens * or into vi, return. */ if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_SSWITCH | SC_VI)) { *spp = sp; break; } /* If the last command switched files, we don't care. */ F_CLR(sp, SC_FSWITCH); /* * If we're exiting this screen, move to the next one. By * definition, this means returning into vi, so 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))) return (1); *spp = screen_next(sp); return (screen_end(sp)); } } return (0); } /* * ex_cmd -- * The guts of the ex parser: parse and execute a string containing * ex commands. * * !!! * This code MODIFIES the string that gets passed in, to delete quoting * characters, etc. The string cannot be readonly/text space, nor should * you expect to use it again after ex_cmd() returns. * * !!! * For the fun of it, if you want to see if a vi clone got the ex argument * parsing right, try: * * echo 'foo|bar' > file1; echo 'foo/bar' > file2; * vi * :edit +1|s/|/PIPE/|w file1| e file2|1 | s/\//SLASH/|wq * * or: vi * :set|file|append|set|file * * For extra credit, try them in a startup .exrc file. * * PUBLIC: int ex_cmd(SCR *); */ int ex_cmd(SCR *sp) { enum nresult nret; EX_PRIVATE *exp; EXCMD *ecp; GS *gp; MARK cur; recno_t lno; size_t arg1_len, discard, len; u_int32_t flags; long ltmp; int at_found, gv_found; int cnt, delim, isaddr, namelen; int newscreen, notempty, tmp, vi_address; CHAR_T *arg1, *s, *p, *t; CHAR_T ch = '\0'; CHAR_T *n; char *np; gp = sp->gp; exp = EXP(sp); /* * We always start running the command on the top of the stack. * This means that *everything* must be resolved when we leave * this function for any reason. */ loop: ecp = SLIST_FIRST(gp->ecq); /* If we're reading a command from a file, set up error information. */ if (ecp->if_name != NULL) { gp->if_lno = ecp->if_lno; gp->if_name = ecp->if_name; } /* * If a move to the end of the file is scheduled for this command, * do it now. */ if (F_ISSET(ecp, E_MOVETOEND)) { if (db_last(sp, &sp->lno)) goto rfail; sp->cno = 0; F_CLR(ecp, E_MOVETOEND); } /* If we found a newline, increment the count now. */ if (F_ISSET(ecp, E_NEWLINE)) { ++gp->if_lno; ++ecp->if_lno; F_CLR(ecp, E_NEWLINE); } /* (Re)initialize the EXCMD structure, preserving some flags. */ CLEAR_EX_CMD(ecp); /* Initialize the argument structures. */ if (argv_init(sp, ecp)) goto err; /* Initialize +cmd, saved command information. */ arg1 = NULL; ecp->save_cmdlen = 0; /* Skip s, empty lines. */ for (notempty = 0; ecp->clen > 0; ++ecp->cp, --ecp->clen) if ((ch = *ecp->cp) == '\n') { ++gp->if_lno; ++ecp->if_lno; } else if (cmdskip(ch)) notempty = 1; else break; /* * !!! * Permit extra colons at the start of the line. Historically, * ex/vi allowed a single extra one. It's simpler not to count. * The stripping is done here because, historically, any command * could have preceding colons, e.g. ":g/pattern/:p" worked. */ if (ecp->clen != 0 && ch == ':') { notempty = 1; while (--ecp->clen > 0 && (ch = *++ecp->cp) == ':'); } /* * Command lines that start with a double-quote are comments. * * !!! * Historically, there was no escape or delimiter for a comment, e.g. * :"foo|set was a single comment and nothing was output. Since nvi * permits users to escape characters into command lines, we * have to check for that case. */ if (ecp->clen != 0 && ch == '"') { while (--ecp->clen > 0 && *++ecp->cp != '\n'); if (*ecp->cp == '\n') { F_SET(ecp, E_NEWLINE); ++ecp->cp; --ecp->clen; } goto loop; } /* Skip whitespace. */ for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) { ch = *ecp->cp; if (!cmdskip(ch)) break; } /* * The last point at which an empty line can mean do nothing. * * !!! * Historically, in ex mode, lines containing only characters * were the same as a single , i.e. a default command. * In vi mode, they were ignored. In .exrc files this was a serious * annoyance, as vi kept trying to treat them as print commands. We * ignore backward compatibility in this case, discarding lines that * contain only characters from .exrc files. * * !!! * This is where you end up when you're done a command, i.e. clen has * gone to zero. Continue if there are more commands to run. */ if (ecp->clen == 0 && (!notempty || F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_BLIGNORE))) { if (ex_load(sp)) goto rfail; ecp = SLIST_FIRST(gp->ecq); if (ecp->clen == 0) goto rsuccess; goto loop; } /* * Check to see if this is a command for which we may want to move * the cursor back up to the previous line. (The command :1 * wants a separator, but the command : wants to erase * the command line.) If the line is empty except for s, * or , we'll probably want to move up. I * don't think there's any way to get characters *after* the * command character, but this is the ex parser, and I've been wrong * before. */ if (F_ISSET(ecp, E_NRSEP) && ecp->clen != 0 && (ecp->clen != 1 || ecp->cp[0] != '\004')) F_CLR(ecp, E_NRSEP); /* Parse command addresses. */ if (ex_range(sp, ecp, &tmp)) goto rfail; if (tmp) goto err; /* * Skip s and any more colons (the command :3,5:print * worked, historically). */ for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) { ch = *ecp->cp; if (!cmdskip(ch) && ch != ':') break; } /* * If no command, ex does the last specified of p, l, or #, and vi * moves to the line. Otherwise, determine the length of the command * name by looking for the first non-alphabetic character. (There * are a few non-alphabetic characters in command names, but they're * all single character commands.) This isn't a great test, because * it means that, for the command ":e +cut.c file", we'll report that * the command "cut" wasn't known. However, it makes ":e+35 file" work * correctly. * * !!! * Historically, lines with multiple adjacent (or separated) * command separators were very strange. For example, the command * |||, when the cursor was on line 1, displayed * lines 2, 3 and 5 of the file. In addition, the command " | " * would only display the line after the next line, instead of the * next two lines. No ideas why. It worked reasonably when executed * from vi mode, and displayed lines 2, 3, and 4, so we do a default * command for each separator. */ #define SINGLE_CHAR_COMMANDS L("\004!#&*<=>@~") newscreen = 0; if (ecp->clen != 0 && ecp->cp[0] != '|' && ecp->cp[0] != '\n') { if (STRCHR(SINGLE_CHAR_COMMANDS, *ecp->cp)) { p = ecp->cp; ++ecp->cp; --ecp->clen; namelen = 1; } else { for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!isazAZ(*ecp->cp)) break; if ((namelen = ecp->cp - p) == 0) { msgq(sp, M_ERR, "080|Unknown command name"); goto err; } } /* * !!! * Historic vi permitted flags to immediately follow any * subset of the 'delete' command, but then did not permit * further arguments (flag, buffer, count). Make it work. * Permit further arguments for the few shreds of dignity * it offers. * * Adding commands that start with 'd', and match "delete" * up to a l, p, +, - or # character can break this code. * * !!! * Capital letters beginning the command names ex, edit, * next, previous, tag and visual (in vi mode) indicate the * command should happen in a new screen. */ switch (p[0]) { case 'd': for (s = p, n = cmds[C_DELETE].name; *s == *n; ++s, ++n); if (s[0] == 'l' || s[0] == 'p' || s[0] == '+' || s[0] == '-' || s[0] == '^' || s[0] == '#') { len = (ecp->cp - p) - (s - p); ecp->cp -= len; ecp->clen += len; ecp->rcmd = cmds[C_DELETE]; ecp->rcmd.syntax = "1bca1"; ecp->cmd = &ecp->rcmd; goto skip_srch; } break; case 'E': case 'F': case 'N': case 'P': case 'T': case 'V': newscreen = 1; p[0] = tolower(p[0]); break; } /* * Search the table for the command. * * !!! * Historic vi permitted the mark to immediately follow the * 'k' in the 'k' command. Make it work. * * !!! * Historic vi permitted any flag to follow the s command, e.g. * "s/e/E/|s|sgc3p" was legal. Make the command "sgc" work. * Since the following characters all have to be flags, i.e. * alphabetics, we can let the s command routine return errors * if it was some illegal command string. This code will break * if an "sg" or similar command is ever added. The substitute * code doesn't care if it's a "cgr" flag or a "#lp" flag that * follows the 's', but we limit the choices here to "cgr" so * that we get unknown command messages for wrong combinations. */ if ((ecp->cmd = ex_comm_search(p, namelen)) == NULL) switch (p[0]) { case 'k': if (namelen == 2) { ecp->cp -= namelen - 1; ecp->clen += namelen - 1; ecp->cmd = &cmds[C_K]; break; } goto unknown; case 's': for (s = p + 1, cnt = namelen; --cnt; ++s) if (s[0] != 'c' && s[0] != 'g' && s[0] != 'r') break; if (cnt == 0) { ecp->cp -= namelen - 1; ecp->clen += namelen - 1; ecp->rcmd = cmds[C_SUBSTITUTE]; ecp->rcmd.fn = ex_subagain; ecp->cmd = &ecp->rcmd; break; } /* FALLTHROUGH */ default: unknown: if (newscreen) p[0] = toupper(p[0]); ex_unknown(sp, p, namelen); goto err; } /* * The visual command has a different syntax when called * from ex than when called from a vi colon command. FMH. * Make the change now, before we test for the newscreen * semantic, so that we're testing the right one. */ skip_srch: if (ecp->cmd == &cmds[C_VISUAL_EX] && F_ISSET(sp, SC_VI)) ecp->cmd = &cmds[C_VISUAL_VI]; /* * !!! * Historic vi permitted a capital 'P' at the beginning of * any command that started with 'p'. Probably wanted the * P[rint] command for backward compatibility, and the code * just made Preserve and Put work by accident. Nvi uses * Previous to mean previous-in-a-new-screen, so be careful. */ if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN) && (ecp->cmd == &cmds[C_PRINT] || ecp->cmd == &cmds[C_PRESERVE])) newscreen = 0; /* Test for a newscreen associated with this command. */ if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN)) goto unknown; /* Secure means no shell access. */ if (F_ISSET(ecp->cmd, E_SECURE) && O_ISSET(sp, O_SECURE)) { ex_wemsg(sp, ecp->cmd->name, EXM_SECURE); goto err; } /* * Multiple < and > characters; another "feature". Note, * The string passed to the underlying function may not be * nul terminated in this case. */ if ((ecp->cmd == &cmds[C_SHIFTL] && *p == '<') || (ecp->cmd == &cmds[C_SHIFTR] && *p == '>')) { for (ch = *p; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (*ecp->cp != ch) break; if (argv_exp0(sp, ecp, p, ecp->cp - p)) goto err; } /* Set the format style flags for the next command. */ if (ecp->cmd == &cmds[C_HASH]) exp->fdef = E_C_HASH; else if (ecp->cmd == &cmds[C_LIST]) exp->fdef = E_C_LIST; else if (ecp->cmd == &cmds[C_PRINT]) exp->fdef = E_C_PRINT; F_CLR(ecp, E_USELASTCMD); } else { /* Print is the default command. */ ecp->cmd = &cmds[C_PRINT]; /* Set the saved format flags. */ F_SET(ecp, exp->fdef); /* * !!! * If no address was specified, and it's not a global command, * we up the address by one. (I have no idea why globals are * exempted, but it's (ahem) historic practice.) */ if (ecp->addrcnt == 0 && !F_ISSET(sp, SC_EX_GLOBAL)) { ecp->addrcnt = 1; ecp->addr1.lno = sp->lno + 1; ecp->addr1.cno = sp->cno; } F_SET(ecp, E_USELASTCMD); } /* * !!! * Historically, the number option applied to both ex and vi. One * strangeness was that ex didn't switch display formats until a * command was entered, e.g. 's after the set didn't change to * the new format, but :1p would. */ if (O_ISSET(sp, O_NUMBER)) { F_SET(ecp, E_OPTNUM); FL_SET(ecp->iflags, E_C_HASH); } else F_CLR(ecp, E_OPTNUM); /* Check for ex mode legality. */ if (F_ISSET(sp, SC_EX) && (F_ISSET(ecp->cmd, E_VIONLY) || newscreen)) { msgq_wstr(sp, M_ERR, ecp->cmd->name, "082|%s: command not available in ex mode"); goto err; } /* Add standard command flags. */ F_SET(ecp, ecp->cmd->flags); if (!newscreen) F_CLR(ecp, E_NEWSCREEN); /* * There are three normal termination cases for an ex command. They * are the end of the string (ecp->clen), or unescaped (by characters) or '|' characters. As we're now past * possible addresses, we can determine how long the command is, so we * don't have to look for all the possible terminations. Naturally, * there are some exciting special cases: * * 1: The bang, global, v and the filter versions of the read and * write commands are delimited by s (they can contain * shell pipes). * 2: The ex, edit, next and visual in vi mode commands all take ex * commands as their first arguments. * 3: The s command takes an RE as its first argument, and wants it * to be specially delimited. * * Historically, '|' characters in the first argument of the ex, edit, * next, vi visual, and s commands didn't delimit the command. And, * in the filter cases for read and write, and the bang, global and v * commands, they did not delimit the command at all. * * For example, the following commands were legal: * * :edit +25|s/abc/ABC/ file.c * :s/|/PIPE/ * :read !spell % | columnate * :global/pattern/p|l * * It's not quite as simple as it sounds, however. The command: * * :s/a/b/|s/c/d|set * * was also legal, i.e. the historic ex parser (using the word loosely, * since "parser" implies some regularity of syntax) delimited the RE's * based on its delimiter and not anything so irretrievably vulgar as a * command syntax. * * Anyhow, the following code makes this all work. First, for the * special cases we move past their special argument(s). Then, we * do normal command processing on whatever is left. Barf-O-Rama. */ discard = 0; /* Characters discarded from the command. */ arg1_len = 0; ecp->save_cmd = ecp->cp; if (ecp->cmd == &cmds[C_EDIT] || ecp->cmd == &cmds[C_EX] || ecp->cmd == &cmds[C_NEXT] || ecp->cmd == &cmds[C_VISUAL_VI] || ecp->cmd == &cmds[C_VSPLIT]) { /* * Move to the next non-whitespace character. A '!' * immediately following the command is eaten as a * force flag. */ if (ecp->clen > 0 && *ecp->cp == '!') { ++ecp->cp; --ecp->clen; FL_SET(ecp->iflags, E_C_FORCE); /* Reset, don't reparse. */ ecp->save_cmd = ecp->cp; } for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!cmdskip(*ecp->cp)) break; /* * QUOTING NOTE: * * The historic implementation ignored all escape characters * so there was no way to put a space or newline into the +cmd * field. We do a simplistic job of fixing it by moving to the * first whitespace character that isn't escaped. The escaping * characters are stripped as no longer useful. */ if (ecp->clen > 0 && *ecp->cp == '+') { ++ecp->cp; --ecp->clen; for (arg1 = p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { ++discard; --ecp->clen; ch = *++ecp->cp; } else if (cmdskip(ch)) break; *p++ = ch; } arg1_len = ecp->cp - arg1; /* Reset, so the first argument isn't reparsed. */ ecp->save_cmd = ecp->cp; } } else if (ecp->cmd == &cmds[C_BANG] || ecp->cmd == &cmds[C_GLOBAL] || ecp->cmd == &cmds[C_V]) { /* * QUOTING NOTE: * * We use backslashes to escape characters, although * this wasn't historic practice for the bang command. It was * for the global and v commands, and it's common usage when * doing text insert during the command. Escaping characters * are stripped as no longer useful. */ for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (ch == '\\' && ecp->clen > 1 && ecp->cp[1] == '\n') { ++discard; --ecp->clen; ch = *++ecp->cp; ++gp->if_lno; ++ecp->if_lno; } else if (ch == '\n') break; *p++ = ch; } } else if (ecp->cmd == &cmds[C_READ] || ecp->cmd == &cmds[C_WRITE]) { /* * For write commands, if the next character is a , and * the next non-blank character is a '!', it's a filter command * and we want to eat everything up to the . For read * commands, if the next non-blank character is a '!', it's a * filter command and we want to eat everything up to the next * . Otherwise, we're done. */ for (tmp = 0; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (cmdskip(ch)) tmp = 1; else break; } if (ecp->clen > 0 && ch == '!' && (ecp->cmd == &cmds[C_READ] || tmp)) for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (ecp->cp[0] == '\n') break; } else if (ecp->cmd == &cmds[C_SUBSTITUTE]) { /* * Move to the next non-whitespace character, we'll use it as * the delimiter. If the character isn't an alphanumeric or * a '|', it's the delimiter, so parse it. Otherwise, we're * into something like ":s g", so use the special s command. */ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!cmdskip(ecp->cp[0])) break; if (is09azAZ(ecp->cp[0]) || ecp->cp[0] == '|') { ecp->rcmd = cmds[C_SUBSTITUTE]; ecp->rcmd.fn = ex_subagain; ecp->cmd = &ecp->rcmd; } else if (ecp->clen > 0) { /* * QUOTING NOTE: * * Backslashes quote delimiter characters for RE's. * The backslashes are NOT removed since they'll be * used by the RE code. Move to the third delimiter * that's not escaped (or the end of the command). */ delim = *ecp->cp; ++ecp->cp; --ecp->clen; for (cnt = 2; ecp->clen > 0 && cnt != 0; --ecp->clen, ++ecp->cp) if (ecp->cp[0] == '\\' && ecp->clen > 1) { ++ecp->cp; --ecp->clen; } else if (ecp->cp[0] == delim) --cnt; } } /* * Use normal quoting and termination rules to find the end of this * command. * * QUOTING NOTE: * * Historically, vi permitted ^V's to escape 's in the .exrc * file. It was almost certainly a bug, but that's what bug-for-bug * compatibility means, Grasshopper. Also, ^V's escape the command * delimiters. Literal next quote characters in front of the newlines, * '|' characters or literal next characters are stripped as they're * no longer useful. */ vi_address = ecp->clen != 0 && ecp->cp[0] != '\n'; for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = ecp->cp[0]; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { CHAR_T tmp = ecp->cp[1]; if (tmp == '\n' || tmp == '|') { if (tmp == '\n') { ++gp->if_lno; ++ecp->if_lno; } ++discard; --ecp->clen; ++ecp->cp; ch = tmp; } } else if (ch == '\n' || ch == '|') { if (ch == '\n') F_SET(ecp, E_NEWLINE); --ecp->clen; break; } *p++ = ch; } /* * Save off the next command information, go back to the * original start of the command. */ p = ecp->cp + 1; ecp->cp = ecp->save_cmd; ecp->save_cmd = p; ecp->save_cmdlen = ecp->clen; ecp->clen = ((ecp->save_cmd - ecp->cp) - 1) - discard; /* * QUOTING NOTE: * * The "set tags" command historically used a backslash, not the * user's literal next character, to escape whitespace. Handle * it here instead of complicating the argv_exp3() code. Note, * this isn't a particularly complex trap, and if backslashes were * legal in set commands, this would have to be much more complicated. */ - if (ecp->cmd == &cmds[C_SET]) + if (ecp->cmd == &cmds[C_SET]) { for (p = ecp->cp, len = ecp->clen; len > 0; --len, ++p) if (IS_ESCAPE(sp, ecp, *p) && len > 1) { --len; ++p; } else if (*p == '\\') *p = CH_LITERAL; + } /* * Set the default addresses. It's an error to specify an address for * a command that doesn't take them. If two addresses are specified * for a command that only takes one, lose the first one. Two special * cases here, some commands take 0 or 2 addresses. For most of them * (the E_ADDR2_ALL flag), 0 defaults to the entire file. For one * (the `!' command, the E_ADDR2_NONE flag), 0 defaults to no lines. * * Also, if the file is empty, some commands want to use an address of * 0, i.e. the entire file is 0 to 0, and the default first address is * 0. Otherwise, an entire file is 1 to N and the default line is 1. * Note, we also add the E_ADDR_ZERO flag to the command flags, for the * case where the 0 address is only valid if it's a default address. * * Also, set a flag if we set the default addresses. Some commands * (ex: z) care if the user specified an address or if we just used * the current cursor. */ switch (F_ISSET(ecp, E_ADDR1 | E_ADDR2 | E_ADDR2_ALL | E_ADDR2_NONE)) { case E_ADDR1: /* One address: */ switch (ecp->addrcnt) { case 0: /* Default cursor/empty file. */ ecp->addrcnt = 1; F_SET(ecp, E_ADDR_DEF); if (F_ISSET(ecp, E_ADDR_ZERODEF)) { if (db_last(sp, &lno)) goto err; if (lno == 0) { ecp->addr1.lno = 0; F_SET(ecp, E_ADDR_ZERO); } else ecp->addr1.lno = sp->lno; } else ecp->addr1.lno = sp->lno; ecp->addr1.cno = sp->cno; break; case 1: break; case 2: /* Lose the first address. */ ecp->addrcnt = 1; ecp->addr1 = ecp->addr2; } break; case E_ADDR2_NONE: /* Zero/two addresses: */ if (ecp->addrcnt == 0) /* Default to nothing. */ break; goto two_addr; case E_ADDR2_ALL: /* Zero/two addresses: */ if (ecp->addrcnt == 0) { /* Default entire/empty file. */ F_SET(ecp, E_ADDR_DEF); ecp->addrcnt = 2; if (sp->ep == NULL) ecp->addr2.lno = 0; else if (db_last(sp, &ecp->addr2.lno)) goto err; if (F_ISSET(ecp, E_ADDR_ZERODEF) && ecp->addr2.lno == 0) { ecp->addr1.lno = 0; F_SET(ecp, E_ADDR_ZERO); } else ecp->addr1.lno = 1; ecp->addr1.cno = ecp->addr2.cno = 0; F_SET(ecp, E_ADDR2_ALL); break; } /* FALLTHROUGH */ case E_ADDR2: /* Two addresses: */ two_addr: switch (ecp->addrcnt) { case 0: /* Default cursor/empty file. */ ecp->addrcnt = 2; F_SET(ecp, E_ADDR_DEF); if (sp->lno == 1 && F_ISSET(ecp, E_ADDR_ZERODEF)) { if (db_last(sp, &lno)) goto err; if (lno == 0) { ecp->addr1.lno = ecp->addr2.lno = 0; F_SET(ecp, E_ADDR_ZERO); } else ecp->addr1.lno = ecp->addr2.lno = sp->lno; } else ecp->addr1.lno = ecp->addr2.lno = sp->lno; ecp->addr1.cno = ecp->addr2.cno = sp->cno; break; case 1: /* Default to first address. */ ecp->addrcnt = 2; ecp->addr2 = ecp->addr1; break; case 2: break; } break; default: if (ecp->addrcnt) /* Error. */ goto usage; } /* * !!! * The ^D scroll command historically scrolled the value of the scroll * option or to EOF. It was an error if the cursor was already at EOF. * (Leading addresses were permitted, but were then ignored.) */ if (ecp->cmd == &cmds[C_SCROLL]) { ecp->addrcnt = 2; ecp->addr1.lno = sp->lno + 1; ecp->addr2.lno = sp->lno + O_VAL(sp, O_SCROLL); ecp->addr1.cno = ecp->addr2.cno = sp->cno; if (db_last(sp, &lno)) goto err; if (lno != 0 && lno > sp->lno && ecp->addr2.lno > lno) ecp->addr2.lno = lno; } ecp->flagoff = 0; for (np = ecp->cmd->syntax; *np != '\0'; ++np) { /* * The force flag is sensitive to leading whitespace, i.e. * "next !" is different from "next!". Handle it before * skipping leading s. */ if (*np == '!') { if (ecp->clen > 0 && *ecp->cp == '!') { ++ecp->cp; --ecp->clen; FL_SET(ecp->iflags, E_C_FORCE); } continue; } /* Skip leading s. */ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!cmdskip(*ecp->cp)) break; if (ecp->clen == 0) break; switch (*np) { case '1': /* +, -, #, l, p */ /* * !!! * Historically, some flags were ignored depending * on where they occurred in the command line. For * example, in the command, ":3+++p--#", historic vi * acted on the '#' flag, but ignored the '-' flags. * It's unambiguous what the flags mean, so we just * handle them regardless of the stupidity of their * location. */ for (; ecp->clen; --ecp->clen, ++ecp->cp) switch (*ecp->cp) { case '+': ++ecp->flagoff; break; case '-': case '^': --ecp->flagoff; break; case '#': F_CLR(ecp, E_OPTNUM); FL_SET(ecp->iflags, E_C_HASH); exp->fdef |= E_C_HASH; break; case 'l': FL_SET(ecp->iflags, E_C_LIST); exp->fdef |= E_C_LIST; break; case 'p': FL_SET(ecp->iflags, E_C_PRINT); exp->fdef |= E_C_PRINT; break; default: goto end_case1; } end_case1: break; case '2': /* -, ., +, ^ */ case '3': /* -, ., +, ^, = */ for (; ecp->clen; --ecp->clen, ++ecp->cp) switch (*ecp->cp) { case '-': FL_SET(ecp->iflags, E_C_DASH); break; case '.': FL_SET(ecp->iflags, E_C_DOT); break; case '+': FL_SET(ecp->iflags, E_C_PLUS); break; case '^': FL_SET(ecp->iflags, E_C_CARAT); break; case '=': if (*np == '3') { FL_SET(ecp->iflags, E_C_EQUAL); break; } /* FALLTHROUGH */ default: goto end_case23; } end_case23: break; case 'b': /* buffer */ /* * !!! * Historically, "d #" was a delete with a flag, not a * delete into the '#' buffer. If the current command * permits a flag, don't use one as a buffer. However, * the 'l' and 'p' flags were legal buffer names in the * historic ex, and were used as buffers, not flags. */ if ((ecp->cp[0] == '+' || ecp->cp[0] == '-' || ecp->cp[0] == '^' || ecp->cp[0] == '#') && strchr(np, '1') != NULL) break; /* * !!! * Digits can't be buffer names in ex commands, or the * command "d2" would be a delete into buffer '2', and * not a two-line deletion. */ if (!ISDIGIT(ecp->cp[0])) { ecp->buffer = *ecp->cp; ++ecp->cp; --ecp->clen; FL_SET(ecp->iflags, E_C_BUFFER); } break; case 'c': /* count [01+a] */ ++np; /* Validate any signed value. */ if (!ISDIGIT(*ecp->cp) && (*np != '+' || (*ecp->cp != '+' && *ecp->cp != '-'))) break; /* If a signed value, set appropriate flags. */ if (*ecp->cp == '-') FL_SET(ecp->iflags, E_C_COUNT_NEG); else if (*ecp->cp == '+') FL_SET(ecp->iflags, E_C_COUNT_POS); if ((nret = nget_slong(<mp, ecp->cp, &t, 10)) != NUM_OK) { ex_badaddr(sp, NULL, A_NOTSET, nret); goto err; } if (ltmp == 0 && *np != '0') { msgq(sp, M_ERR, "083|Count may not be zero"); goto err; } ecp->clen -= (t - ecp->cp); ecp->cp = t; /* * Counts as address offsets occur in commands taking * two addresses. Historic vi practice was to use * the count as an offset from the *second* address. * * Set a count flag; some underlying commands (see * join) do different things with counts than with * line addresses. */ if (*np == 'a') { ecp->addr1 = ecp->addr2; ecp->addr2.lno = ecp->addr1.lno + ltmp - 1; } else ecp->count = ltmp; FL_SET(ecp->iflags, E_C_COUNT); break; case 'f': /* file */ if (argv_exp2(sp, ecp, ecp->cp, ecp->clen)) goto err; goto arg_cnt_chk; case 'l': /* line */ /* * Get a line specification. * * If the line was a search expression, we may have * changed state during the call, and we're now * searching the file. Push ourselves onto the state * stack. */ if (ex_line(sp, ecp, &cur, &isaddr, &tmp)) goto rfail; if (tmp) goto err; /* Line specifications are always required. */ if (!isaddr) { msgq_wstr(sp, M_ERR, ecp->cp, "084|%s: bad line specification"); goto err; } /* * The target line should exist for these commands, * but 0 is legal for them as well. */ if (cur.lno != 0 && !db_exist(sp, cur.lno)) { ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } ecp->lineno = cur.lno; break; case 'S': /* string, file exp. */ if (ecp->clen != 0) { if (argv_exp1(sp, ecp, ecp->cp, ecp->clen, ecp->cmd == &cmds[C_BANG])) goto err; goto addr_verify; } /* FALLTHROUGH */ case 's': /* string */ if (argv_exp0(sp, ecp, ecp->cp, ecp->clen)) goto err; goto addr_verify; case 'W': /* word string */ /* * QUOTING NOTE: * * Literal next characters escape the following * character. Quoting characters are stripped here * since they are no longer useful. * * First there was the word. */ for (p = t = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { --ecp->clen; *p++ = *++ecp->cp; } else if (cmdskip(ch)) { ++ecp->cp; --ecp->clen; break; } else *p++ = ch; } if (argv_exp0(sp, ecp, t, p - t)) goto err; /* Delete intervening whitespace. */ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (!cmdskip(ch)) break; } if (ecp->clen == 0) goto usage; /* Followed by the string. */ for (p = t = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp, ++p) { ch = *ecp->cp; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { --ecp->clen; *p = *++ecp->cp; } else *p = ch; } if (argv_exp0(sp, ecp, t, p - t)) goto err; goto addr_verify; case 'w': /* word */ if (argv_exp3(sp, ecp, ecp->cp, ecp->clen)) goto err; arg_cnt_chk: if (*++np != 'N') { /* N */ /* * If a number is specified, must either be * 0 or that number, if optional, and that * number, if required. */ tmp = *np - '0'; if ((*++np != 'o' || exp->argsoff != 0) && exp->argsoff != tmp) goto usage; } goto addr_verify; default: { size_t nlen; char *nstr; INT2CHAR(sp, ecp->cmd->name, STRLEN(ecp->cmd->name) + 1, nstr, nlen); msgq(sp, M_ERR, "085|Internal syntax table error (%s: %s)", nstr, KEY_NAME(sp, *np)); } } } /* Skip trailing whitespace. */ for (; ecp->clen > 0; --ecp->clen) { ch = *ecp->cp++; if (!cmdskip(ch)) break; } /* * There shouldn't be anything left, and no more required fields, * i.e neither 'l' or 'r' in the syntax string. */ if (ecp->clen != 0 || strpbrk(np, "lr")) { usage: msgq(sp, M_ERR, "086|Usage: %s", ecp->cmd->usage); goto err; } /* * Verify that the addresses are legal. Check the addresses here, * because this is a place where all ex addresses pass through. * (They don't all pass through ex_line(), for instance.) We're * assuming that any non-existent line doesn't exist because it's * past the end-of-file. That's a pretty good guess. * * If it's a "default vi command", an address of zero is okay. */ addr_verify: switch (ecp->addrcnt) { case 2: /* * Historic ex/vi permitted commands with counts to go past * EOF. So, for example, if the file only had 5 lines, the * ex command "1,6>" would fail, but the command ">300" * would succeed. Since we don't want to have to make all * of the underlying commands handle random line numbers, * fix it here. */ if (ecp->addr2.lno == 0) { if (!F_ISSET(ecp, E_ADDR_ZERO) && (F_ISSET(sp, SC_EX) || !F_ISSET(ecp, E_USELASTCMD))) { ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK); goto err; } - } else if (!db_exist(sp, ecp->addr2.lno)) + } else if (!db_exist(sp, ecp->addr2.lno)) { if (FL_ISSET(ecp->iflags, E_C_COUNT)) { if (db_last(sp, &lno)) goto err; ecp->addr2.lno = lno; } else { ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } + } /* FALLTHROUGH */ case 1: if (ecp->addr1.lno == 0) { if (!F_ISSET(ecp, E_ADDR_ZERO) && (F_ISSET(sp, SC_EX) || !F_ISSET(ecp, E_USELASTCMD))) { ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK); goto err; } } else if (!db_exist(sp, ecp->addr1.lno)) { ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } break; } /* * If doing a default command and there's nothing left on the line, * vi just moves to the line. For example, ":3" and ":'a,'b" just * move to line 3 and line 'b, respectively, but ":3|" prints line 3. * * !!! * In addition, IF THE LINE CHANGES, move to the first nonblank of * the line. * * !!! * This is done before the absolute mark gets set; historically, * "/a/,/b/" did NOT set vi's absolute mark, but "/a/,/b/d" did. */ if ((F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_NOPRDEF)) && F_ISSET(ecp, E_USELASTCMD) && vi_address == 0) { switch (ecp->addrcnt) { case 2: if (sp->lno != (ecp->addr2.lno ? ecp->addr2.lno : 1)) { sp->lno = ecp->addr2.lno ? ecp->addr2.lno : 1; sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } break; case 1: if (sp->lno != (ecp->addr1.lno ? ecp->addr1.lno : 1)) { sp->lno = ecp->addr1.lno ? ecp->addr1.lno : 1; sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } break; } ecp->cp = ecp->save_cmd; ecp->clen = ecp->save_cmdlen; goto loop; } /* * Set the absolute mark -- we have to set it for vi here, in case * it's a compound command, e.g. ":5p|6" should set the absolute * mark for vi. */ if (F_ISSET(ecp, E_ABSMARK)) { cur.lno = sp->lno; cur.cno = sp->cno; F_CLR(ecp, E_ABSMARK); if (mark_set(sp, ABSMARK1, &cur, 1)) goto err; } #if defined(DEBUG) && defined(COMLOG) ex_comlog(sp, ecp); #endif /* Increment the command count if not called from vi. */ if (F_ISSET(sp, SC_EX)) ++sp->ccnt; /* * If file state available, and not doing a global command, * log the start of an action. */ if (sp->ep != NULL && !F_ISSET(sp, SC_EX_GLOBAL)) (void)log_cursor(sp); /* * !!! * There are two special commands for the purposes of this code: the * default command () or the scrolling commands (^D * and ) as the first non- characters in the line. * * If this is the first command in the command line, we received the * command from the ex command loop and we're talking to a tty, and * and there's nothing else on the command line, and it's one of the * special commands, we move back up to the previous line, and erase * the prompt character with the output. Since ex runs in canonical * mode, we don't have to do anything else, a has already * been echoed by the tty driver. It's OK if vi calls us -- we won't * be in ex mode so we'll do nothing. */ if (F_ISSET(ecp, E_NRSEP)) { if (sp->ep != NULL && F_ISSET(sp, SC_EX) && !F_ISSET(gp, G_SCRIPTED) && (F_ISSET(ecp, E_USELASTCMD) || ecp->cmd == &cmds[C_SCROLL])) gp->scr_ex_adjust(sp, EX_TERM_SCROLL); F_CLR(ecp, E_NRSEP); } /* * Call the underlying function for the ex command. * * XXX * Interrupts behave like errors, for now. */ if (ecp->cmd->fn(sp, ecp) || INTERRUPTED(sp)) { if (F_ISSET(gp, G_SCRIPTED)) F_SET(sp, SC_EXIT_FORCE); goto err; } #ifdef DEBUG /* Make sure no function left global temporary space locked. */ if (F_ISSET(gp, G_TMP_INUSE)) { F_CLR(gp, G_TMP_INUSE); msgq_wstr(sp, M_ERR, ecp->cmd->name, "087|%s: temporary buffer not released"); } #endif /* * Ex displayed the number of lines modified immediately after each * command, so the command "1,10d|1,10d" would display: * * 10 lines deleted * 10 lines deleted * * * Executing ex commands from vi only reported the final modified * lines message -- that's wrong enough that we don't match it. */ if (F_ISSET(sp, SC_EX)) mod_rpt(sp); /* * Integrate any offset parsed by the underlying command, and make * sure the referenced line exists. * * XXX * May not match historic practice (which I've never been able to * completely figure out.) For example, the '=' command from vi * mode often got the offset wrong, and complained it was too large, * but didn't seem to have a problem with the cursor. If anyone * complains, ask them how it's supposed to work, they might know. */ if (sp->ep != NULL && ecp->flagoff) { if (ecp->flagoff < 0) { if (sp->lno <= -ecp->flagoff) { msgq(sp, M_ERR, "088|Flag offset to before line 1"); goto err; } } else { if (!NPFITS(MAX_REC_NUMBER, sp->lno, ecp->flagoff)) { ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); goto err; } if (!db_exist(sp, sp->lno + ecp->flagoff)) { msgq(sp, M_ERR, "089|Flag offset past end-of-file"); goto err; } } sp->lno += ecp->flagoff; } /* * If the command executed successfully, we may want to display a line * based on the autoprint option or an explicit print flag. (Make sure * that there's a line to display.) Also, the autoprint edit option is * turned off for the duration of global commands. */ if (F_ISSET(sp, SC_EX) && sp->ep != NULL && sp->lno != 0) { /* * The print commands have already handled the `print' flags. * If so, clear them. */ if (FL_ISSET(ecp->iflags, E_CLRFLAG)) FL_CLR(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT); /* If hash set only because of the number option, discard it. */ if (F_ISSET(ecp, E_OPTNUM)) FL_CLR(ecp->iflags, E_C_HASH); /* * If there was an explicit flag to display the new cursor line, * or autoprint is set and a change was made, display the line. * If any print flags were set use them, else default to print. */ LF_INIT(FL_ISSET(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)); if (!LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT | E_NOAUTO) && !F_ISSET(sp, SC_EX_GLOBAL) && O_ISSET(sp, O_AUTOPRINT) && F_ISSET(ecp, E_AUTOPRINT)) LF_INIT(E_C_PRINT); if (LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT)) { cur.lno = sp->lno; cur.cno = 0; (void)ex_print(sp, ecp, &cur, &cur, flags); } } /* * If the command had an associated "+cmd", it has to be executed * before we finish executing any more of this ex command. For * example, consider a .exrc file that contains the following lines: * * :set all * :edit +25 file.c|s/abc/ABC/|1 * :3,5 print * * This can happen more than once -- the historic vi simply hung or * dropped core, of course. Prepend the + command back into the * current command and continue. We may have to add an additional * character. We know that it will fit because we * discarded at least one space and the + character. */ if (arg1_len != 0) { /* * If the last character of the + command was a * character, it would be treated differently because of the * append. Quote it, if necessary. */ if (IS_ESCAPE(sp, ecp, arg1[arg1_len - 1])) { *--ecp->save_cmd = CH_LITERAL; ++ecp->save_cmdlen; } ecp->save_cmd -= arg1_len; ecp->save_cmdlen += arg1_len; MEMMOVE(ecp->save_cmd, arg1, arg1_len); /* * Any commands executed from a +cmd are executed starting at * the first column of the last line of the file -- NOT the * first nonblank.) The main file startup code doesn't know * that a +cmd was set, however, so it may have put us at the * top of the file. (Note, this is safe because we must have * switched files to get here.) */ F_SET(ecp, E_MOVETOEND); } /* Update the current command. */ ecp->cp = ecp->save_cmd; ecp->clen = ecp->save_cmdlen; /* * !!! * If we've changed screens or underlying files, any pending global or * v command, or @ buffer that has associated addresses, has to be * discarded. This is historic practice for globals, and necessary for * @ buffers that had associated addresses. * * Otherwise, if we've changed underlying files, it's not a problem, * we continue with the rest of the ex command(s), operating on the * new file. However, if we switch screens (either by exiting or by * an explicit command), we have no way of knowing where to put output * messages, and, since we don't control screens here, we could screw * up the upper layers, (e.g. we could exit/reenter a screen multiple * times). So, return and continue after we've got a new screen. */ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_FSWITCH | SC_SSWITCH)) { at_found = gv_found = 0; SLIST_FOREACH(ecp, sp->gp->ecq, q) switch (ecp->agv_flags) { case 0: case AGV_AT_NORANGE: break; case AGV_AT: if (!at_found) { at_found = 1; msgq(sp, M_ERR, "090|@ with range running when the file/screen changed"); } break; case AGV_GLOBAL: case AGV_V: if (!gv_found) { gv_found = 1; msgq(sp, M_ERR, "091|Global/v command running when the file/screen changed"); } break; default: abort(); } if (at_found || gv_found) goto discard; if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_SSWITCH)) goto rsuccess; } goto loop; /* NOTREACHED */ err: /* * On command failure, we discard keys and pending commands remaining, * as well as any keys that were mapped and waiting. The save_cmdlen * test is not necessarily correct. If we fail early enough we don't * know if the entire string was a single command or not. Guess, as * it's useful to know if commands other than the current one are being * discarded. */ if (ecp->save_cmdlen == 0) for (; ecp->clen; --ecp->clen) { ch = *ecp->cp++; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { --ecp->clen; ++ecp->cp; } else if (ch == '\n' || ch == '|') { if (ecp->clen > 1) ecp->save_cmdlen = 1; break; } } if (ecp->save_cmdlen != 0 || SLIST_FIRST(gp->ecq) != &gp->excmd) { discard: msgq(sp, M_BERR, "092|Ex command failed: pending commands discarded"); ex_discard(sp); } if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_BERR, "093|Ex command failed: mapped keys discarded"); rfail: tmp = 1; if (0) rsuccess: tmp = 0; /* Turn off any file name error information. */ gp->if_name = NULL; /* Turn off the global bit. */ F_CLR(sp, SC_EX_GLOBAL); return (tmp); } /* * ex_range -- * Get a line range for ex commands, or perform a vi ex address search. * * PUBLIC: int ex_range(SCR *, EXCMD *, int *); */ int ex_range(SCR *sp, EXCMD *ecp, int *errp) { enum { ADDR_FOUND, ADDR_NEED, ADDR_NONE } addr; GS *gp; EX_PRIVATE *exp; MARK m; int isaddr; *errp = 0; /* * Parse comma or semi-colon delimited line specs. * * Semi-colon delimiters update the current address to be the last * address. For example, the command * * :3;/pattern/ecp->cp * * will search for pattern from line 3. In addition, if ecp->cp * is not a valid command, the current line will be left at 3, not * at the original address. * * Extra addresses are discarded, starting with the first. * * !!! * If any addresses are missing, they default to the current line. * This was historically true for both leading and trailing comma * delimited addresses as well as for trailing semicolon delimited * addresses. For consistency, we make it true for leading semicolon * addresses as well. */ gp = sp->gp; exp = EXP(sp); for (addr = ADDR_NONE, ecp->addrcnt = 0; ecp->clen > 0;) switch (*ecp->cp) { case '%': /* Entire file. */ /* Vi ex address searches didn't permit % signs. */ if (F_ISSET(ecp, E_VISEARCH)) goto ret; /* It's an error if the file is empty. */ if (sp->ep == NULL) { ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); *errp = 1; return (0); } /* * !!! * A percent character addresses all of the lines in * the file. Historically, it couldn't be followed by * any other address. We do it as a text substitution * for simplicity. POSIX 1003.2 is expected to follow * this practice. * * If it's an empty file, the first line is 0, not 1. */ if (addr == ADDR_FOUND) { ex_badaddr(sp, NULL, A_COMBO, NUM_OK); *errp = 1; return (0); } if (db_last(sp, &ecp->addr2.lno)) return (1); ecp->addr1.lno = ecp->addr2.lno == 0 ? 0 : 1; ecp->addr1.cno = ecp->addr2.cno = 0; ecp->addrcnt = 2; addr = ADDR_FOUND; ++ecp->cp; --ecp->clen; break; case ',': /* Comma delimiter. */ /* Vi ex address searches didn't permit commas. */ if (F_ISSET(ecp, E_VISEARCH)) goto ret; /* FALLTHROUGH */ case ';': /* Semi-colon delimiter. */ if (sp->ep == NULL) { ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); *errp = 1; return (0); } if (addr != ADDR_FOUND) switch (ecp->addrcnt) { case 0: ecp->addr1.lno = sp->lno; ecp->addr1.cno = sp->cno; ecp->addrcnt = 1; break; case 2: ecp->addr1 = ecp->addr2; /* FALLTHROUGH */ case 1: ecp->addr2.lno = sp->lno; ecp->addr2.cno = sp->cno; ecp->addrcnt = 2; break; } if (*ecp->cp == ';') switch (ecp->addrcnt) { case 0: abort(); /* NOTREACHED */ case 1: sp->lno = ecp->addr1.lno; sp->cno = ecp->addr1.cno; break; case 2: sp->lno = ecp->addr2.lno; sp->cno = ecp->addr2.cno; break; } addr = ADDR_NEED; /* FALLTHROUGH */ case ' ': /* Whitespace. */ case '\t': /* Whitespace. */ ++ecp->cp; --ecp->clen; break; default: /* Get a line specification. */ if (ex_line(sp, ecp, &m, &isaddr, errp)) return (1); if (*errp) return (0); if (!isaddr) goto ret; if (addr == ADDR_FOUND) { ex_badaddr(sp, NULL, A_COMBO, NUM_OK); *errp = 1; return (0); } switch (ecp->addrcnt) { case 0: ecp->addr1 = m; ecp->addrcnt = 1; break; case 1: ecp->addr2 = m; ecp->addrcnt = 2; break; case 2: ecp->addr1 = ecp->addr2; ecp->addr2 = m; break; } addr = ADDR_FOUND; break; } /* * !!! * Vi ex address searches are indifferent to order or trailing * semi-colons. */ ret: if (F_ISSET(ecp, E_VISEARCH)) return (0); if (addr == ADDR_NEED) switch (ecp->addrcnt) { case 0: ecp->addr1.lno = sp->lno; ecp->addr1.cno = sp->cno; ecp->addrcnt = 1; break; case 2: ecp->addr1 = ecp->addr2; /* FALLTHROUGH */ case 1: ecp->addr2.lno = sp->lno; ecp->addr2.cno = sp->cno; ecp->addrcnt = 2; break; } if (ecp->addrcnt == 2 && ecp->addr2.lno < ecp->addr1.lno) { msgq(sp, M_ERR, "094|The second address is smaller than the first"); *errp = 1; } return (0); } /* * ex_line -- * Get a single line address specifier. * * The way the "previous context" mark worked was that any "non-relative" * motion set it. While ex/vi wasn't totally consistent about this, ANY * numeric address, search pattern, '$', or mark reference in an address * was considered non-relative, and set the value. Which should explain * why we're hacking marks down here. The problem was that the mark was * only set if the command was called, i.e. we have to set a flag and test * it later. * * XXX * This is probably still not exactly historic practice, although I think * it's fairly close. */ static int ex_line(SCR *sp, EXCMD *ecp, MARK *mp, int *isaddrp, int *errp) { enum nresult nret; EX_PRIVATE *exp; GS *gp; long total, val; int isneg; int (*sf)(SCR *, MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int); CHAR_T *endp; gp = sp->gp; exp = EXP(sp); *isaddrp = *errp = 0; F_CLR(ecp, E_DELTA); /* No addresses permitted until a file has been read in. */ if (sp->ep == NULL && STRCHR(L("$0123456789'\\/?.+-^"), *ecp->cp)) { ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); *errp = 1; return (0); } switch (*ecp->cp) { case '$': /* Last line in the file. */ *isaddrp = 1; F_SET(ecp, E_ABSMARK); mp->cno = 0; if (db_last(sp, &mp->lno)) return (1); ++ecp->cp; --ecp->clen; break; /* Absolute line number. */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': *isaddrp = 1; F_SET(ecp, E_ABSMARK); if ((nret = nget_slong(&val, ecp->cp, &endp, 10)) != NUM_OK) { ex_badaddr(sp, NULL, A_NOTSET, nret); *errp = 1; return (0); } if (!NPFITS(MAX_REC_NUMBER, 0, val)) { ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); *errp = 1; return (0); } mp->lno = val; mp->cno = 0; ecp->clen -= (endp - ecp->cp); ecp->cp = endp; break; case '\'': /* Use a mark. */ *isaddrp = 1; F_SET(ecp, E_ABSMARK); if (ecp->clen == 1) { msgq(sp, M_ERR, "095|No mark name supplied"); *errp = 1; return (0); } if (mark_get(sp, ecp->cp[1], mp, M_ERR)) { *errp = 1; return (0); } ecp->cp += 2; ecp->clen -= 2; break; case '\\': /* Search: forward/backward. */ /* * !!! * I can't find any difference between // and \/ or between * ?? and \?. Mark Horton doesn't remember there being any * difference. C'est la vie. */ if (ecp->clen < 2 || (ecp->cp[1] != '/' && ecp->cp[1] != '?')) { msgq(sp, M_ERR, "096|\\ not followed by / or ?"); *errp = 1; return (0); } ++ecp->cp; --ecp->clen; sf = ecp->cp[0] == '/' ? f_search : b_search; goto search; case '/': /* Search forward. */ sf = f_search; goto search; case '?': /* Search backward. */ sf = b_search; search: mp->lno = sp->lno; mp->cno = sp->cno; if (sf(sp, mp, mp, ecp->cp, ecp->clen, &endp, SEARCH_MSG | SEARCH_PARSE | SEARCH_SET | (F_ISSET(ecp, E_SEARCH_WMSG) ? SEARCH_WMSG : 0))) { *errp = 1; return (0); } /* Fix up the command pointers. */ ecp->clen -= (endp - ecp->cp); ecp->cp = endp; *isaddrp = 1; F_SET(ecp, E_ABSMARK); break; case '.': /* Current position. */ *isaddrp = 1; mp->cno = sp->cno; /* If an empty file, then '.' is 0, not 1. */ if (sp->lno == 1) { if (db_last(sp, &mp->lno)) return (1); if (mp->lno != 0) mp->lno = 1; } else mp->lno = sp->lno; /* * !!! * Historically, . was the same as .+, i.e. * the '+' could be omitted. (This feature is found in ed * as well.) */ if (ecp->clen > 1 && ISDIGIT(ecp->cp[1])) *ecp->cp = '+'; else { ++ecp->cp; --ecp->clen; } break; } /* Skip trailing s. */ for (; ecp->clen > 0 && cmdskip(ecp->cp[0]); ++ecp->cp, --ecp->clen); /* * Evaluate any offset. If no address yet found, the offset * is relative to ".". */ total = 0; if (ecp->clen != 0 && (ISDIGIT(ecp->cp[0]) || ecp->cp[0] == '+' || ecp->cp[0] == '-' || ecp->cp[0] == '^')) { if (!*isaddrp) { *isaddrp = 1; mp->lno = sp->lno; mp->cno = sp->cno; } /* * Evaluate an offset, defined as: * * [+-^]*[]*[0-9]* * * The rough translation is any number of signs, optionally * followed by numbers, or a number by itself, all * separated. * * !!! * All address offsets were additive, e.g. "2 2 3p" was the * same as "7p", or, "/ZZZ/ 2" was the same as "/ZZZ/+2". * Note, however, "2 /ZZZ/" was an error. It was also legal * to insert signs without numbers, so "3 - 2" was legal, and * equal to 4. * * !!! * Offsets were historically permitted for any line address, * e.g. the command "1,2 copy 2 2 2 2" copied lines 1,2 after * line 8. * * !!! * Offsets were historically permitted for search commands, * and handled as addresses: "/pattern/2 2 2" was legal, and * referenced the 6th line after pattern. */ F_SET(ecp, E_DELTA); for (;;) { for (; ecp->clen > 0 && cmdskip(ecp->cp[0]); ++ecp->cp, --ecp->clen); if (ecp->clen == 0 || (!ISDIGIT(ecp->cp[0]) && ecp->cp[0] != '+' && ecp->cp[0] != '-' && ecp->cp[0] != '^')) break; if (!ISDIGIT(ecp->cp[0]) && !ISDIGIT(ecp->cp[1])) { total += ecp->cp[0] == '+' ? 1 : -1; --ecp->clen; ++ecp->cp; } else { if (ecp->cp[0] == '-' || ecp->cp[0] == '^') { ++ecp->cp; --ecp->clen; isneg = 1; } else isneg = 0; /* Get a signed long, add it to the total. */ if ((nret = nget_slong(&val, ecp->cp, &endp, 10)) != NUM_OK || (nret = NADD_SLONG(sp, total, val)) != NUM_OK) { ex_badaddr(sp, NULL, A_NOTSET, nret); *errp = 1; return (0); } total += isneg ? -val : val; ecp->clen -= (endp - ecp->cp); ecp->cp = endp; } } } /* * Any value less than 0 is an error. Make sure that the new value * will fit into a recno_t. */ if (*isaddrp && total != 0) { if (total < 0) { if (-total > mp->lno) { msgq(sp, M_ERR, "097|Reference to a line number less than 0"); *errp = 1; return (0); } } else if (!NPFITS(MAX_REC_NUMBER, mp->lno, total)) { ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); *errp = 1; return (0); } mp->lno += total; } return (0); } /* * ex_load -- * Load up the next command, which may be an @ buffer or global command. */ static int ex_load(SCR *sp) { GS *gp; EXCMD *ecp; RANGE *rp; F_CLR(sp, SC_EX_GLOBAL); /* * Lose any exhausted commands. We know that the first command * can't be an AGV command, which makes things a bit easier. */ for (gp = sp->gp;;) { ecp = SLIST_FIRST(gp->ecq); /* Discard the allocated source name as requested. */ if (F_ISSET(ecp, E_NAMEDISCARD)) free(ecp->if_name); /* * If we're back to the original structure, leave it around, * since we've returned to the beginning of the command stack. */ if (ecp == &gp->excmd) { ecp->if_name = NULL; return (0); } /* * ecp->clen will be 0 for the first discarded command, but * may not be 0 for subsequent ones, e.g. if the original * command was ":g/xx/@a|s/b/c/", then when we discard the * command pushed on the stack by the @a, we have to resume * the global command which included the substitute command. */ if (ecp->clen != 0) return (0); /* * If it's an @, global or v command, we may need to continue * the command on a different line. */ if (FL_ISSET(ecp->agv_flags, AGV_ALL)) { /* Discard any exhausted ranges. */ while ((rp = TAILQ_FIRST(ecp->rq)) != NULL) if (rp->start > rp->stop) { TAILQ_REMOVE(ecp->rq, rp, q); free(rp); } else break; /* If there's another range, continue with it. */ if (rp != NULL) break; /* If it's a global/v command, fix up the last line. */ if (FL_ISSET(ecp->agv_flags, - AGV_GLOBAL | AGV_V) && ecp->range_lno != OOBLNO) + AGV_GLOBAL | AGV_V) && ecp->range_lno != OOBLNO) { if (db_exist(sp, ecp->range_lno)) sp->lno = ecp->range_lno; else { if (db_last(sp, &sp->lno)) return (1); if (sp->lno == 0) sp->lno = 1; } + } free(ecp->o_cp); } /* Discard the EXCMD. */ SLIST_REMOVE_HEAD(gp->ecq, q); free(ecp); } /* * We only get here if it's an active @, global or v command. Set * the current line number, and get a new copy of the command for * the parser. Note, the original pointer almost certainly moved, * so we have play games. */ ecp->cp = ecp->o_cp; MEMCPY(ecp->cp, ecp->cp + ecp->o_clen, ecp->o_clen); ecp->clen = ecp->o_clen; ecp->range_lno = sp->lno = rp->start++; if (FL_ISSET(ecp->agv_flags, AGV_GLOBAL | AGV_V)) F_SET(sp, SC_EX_GLOBAL); return (0); } /* * ex_discard -- * Discard any pending ex commands. */ static int ex_discard(SCR *sp) { GS *gp; EXCMD *ecp; RANGE *rp; /* * We know the first command can't be an AGV command, so we don't * process it specially. We do, however, nail the command itself. */ for (gp = sp->gp;;) { ecp = SLIST_FIRST(gp->ecq); if (F_ISSET(ecp, E_NAMEDISCARD)) free(ecp->if_name); /* Reset the last command without dropping it. */ if (ecp == &gp->excmd) break; if (FL_ISSET(ecp->agv_flags, AGV_ALL)) { while ((rp = TAILQ_FIRST(ecp->rq)) != NULL) { TAILQ_REMOVE(ecp->rq, rp, q); free(rp); } free(ecp->o_cp); } SLIST_REMOVE_HEAD(gp->ecq, q); free(ecp); } ecp->if_name = NULL; ecp->clen = 0; return (0); } /* * ex_unknown -- * Display an unknown command name. */ static void ex_unknown(SCR *sp, CHAR_T *cmd, size_t len) { size_t blen; CHAR_T *bp; GET_SPACE_GOTOW(sp, bp, blen, len + 1); bp[len] = '\0'; MEMCPY(bp, cmd, len); msgq_wstr(sp, M_ERR, bp, "098|The %s command is unknown"); FREE_SPACEW(sp, bp, blen); alloc_err: return; } /* * ex_is_abbrev - * The vi text input routine needs to know if ex thinks this is an * [un]abbreviate command, so it can turn off abbreviations. See * the usual ranting in the vi/v_txt_ev.c:txt_abbrev() routine. * * PUBLIC: int ex_is_abbrev(CHAR_T *, size_t); */ int ex_is_abbrev(CHAR_T *name, size_t len) { EXCMDLIST const *cp; return ((cp = ex_comm_search(name, len)) != NULL && (cp == &cmds[C_ABBR] || cp == &cmds[C_UNABBREVIATE])); } /* * ex_is_unmap - * The vi text input routine needs to know if ex thinks this is an * unmap command, so it can turn off input mapping. See the usual * ranting in the vi/v_txt_ev.c:txt_unmap() routine. * * PUBLIC: int ex_is_unmap(CHAR_T *, size_t); */ int ex_is_unmap(CHAR_T *name, size_t len) { EXCMDLIST const *cp; /* * The command the vi input routines are really interested in * is "unmap!", not just unmap. */ if (name[len - 1] != '!') return (0); --len; return ((cp = ex_comm_search(name, len)) != NULL && cp == &cmds[C_UNMAP]); } /* * ex_comm_search -- * Search for a command name. */ static EXCMDLIST const * ex_comm_search(CHAR_T *name, size_t len) { EXCMDLIST const *cp; for (cp = cmds; cp->name != NULL; ++cp) { if (cp->name[0] > name[0]) return (NULL); if (cp->name[0] != name[0]) continue; if (!MEMCMP(name, cp->name, len)) return (cp); } return (NULL); } /* * ex_badaddr -- * Display a bad address message. * * PUBLIC: void ex_badaddr * PUBLIC: (SCR *, EXCMDLIST const *, enum badaddr, enum nresult); */ void ex_badaddr(SCR *sp, const EXCMDLIST *cp, enum badaddr ba, enum nresult nret) { recno_t lno; switch (nret) { case NUM_OK: break; case NUM_ERR: msgq(sp, M_SYSERR, NULL); return; case NUM_OVER: msgq(sp, M_ERR, "099|Address value overflow"); return; case NUM_UNDER: msgq(sp, M_ERR, "100|Address value underflow"); return; } /* * When encountering an address error, tell the user if there's no * underlying file, that's the real problem. */ if (sp->ep == NULL) { ex_wemsg(sp, cp ? cp->name : NULL, EXM_NOFILEYET); return; } switch (ba) { case A_COMBO: msgq(sp, M_ERR, "101|Illegal address combination"); break; case A_EOF: if (db_last(sp, &lno)) return; if (lno != 0) { msgq(sp, M_ERR, "102|Illegal address: only %lu lines in the file", (u_long)lno); break; } /* FALLTHROUGH */ case A_EMPTY: msgq(sp, M_ERR, "103|Illegal address: the file is empty"); break; case A_NOTSET: abort(); /* NOTREACHED */ case A_ZERO: msgq_wstr(sp, M_ERR, cp->name, "104|The %s command doesn't permit an address of 0"); break; } return; } #if defined(DEBUG) && defined(COMLOG) /* * ex_comlog -- * Log ex commands. */ static void ex_comlog(sp, ecp) SCR *sp; EXCMD *ecp; { TRACE(sp, "ecmd: "WS, ecp->cmd->name); if (ecp->addrcnt > 0) { TRACE(sp, " a1 %d", ecp->addr1.lno); if (ecp->addrcnt > 1) TRACE(sp, " a2: %d", ecp->addr2.lno); } if (ecp->lineno) TRACE(sp, " line %d", ecp->lineno); if (ecp->flags) TRACE(sp, " flags 0x%x", ecp->flags); if (FL_ISSET(ecp->iflags, E_C_BUFFER)) TRACE(sp, " buffer "WC, ecp->buffer); if (ecp->argc) { int cnt; for (cnt = 0; cnt < ecp->argc; ++cnt) TRACE(sp, " arg %d: {"WS"}", cnt, ecp->argv[cnt]->bp); } TRACE(sp, "\n"); } #endif Index: vendor/nvi/dist/ex/ex.h =================================================================== --- vendor/nvi/dist/ex/ex.h (revision 366306) +++ vendor/nvi/dist/ex/ex.h (revision 366307) @@ -1,231 +1,231 @@ /*- * 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. */ #define PROMPTCHAR ':' /* Prompt using a colon. */ typedef struct _excmdlist { /* Ex command table structure. */ CHAR_T *name; /* Command name, underlying function. */ int (*fn)(SCR *, EXCMD *); #define E_ADDR1 0x00000001 /* One address. */ #define E_ADDR2 0x00000002 /* Two addresses. */ #define E_ADDR2_ALL 0x00000004 /* Zero/two addresses; zero == all. */ #define E_ADDR2_NONE 0x00000008 /* Zero/two addresses; zero == none. */ #define E_ADDR_ZERO 0x00000010 /* 0 is a legal addr1. */ #define E_ADDR_ZERODEF 0x00000020 /* 0 is default addr1 of empty files. */ #define E_AUTOPRINT 0x00000040 /* Command always sets autoprint. */ #define E_CLRFLAG 0x00000080 /* Clear the print (#, l, p) flags. */ #define E_NEWSCREEN 0x00000100 /* Create a new screen. */ #define E_SECURE 0x00000200 /* Permission denied if O_SECURE set. */ #define E_VIONLY 0x00000400 /* Meaningful only in vi. */ #define __INUSE1 0xfffff800 /* Same name space as EX_PRIVATE. */ u_int16_t flags; char *syntax; /* Syntax script. */ char *usage; /* Usage line. */ char *help; /* Help line. */ } EXCMDLIST; #define MAXCMDNAMELEN 12 /* Longest command name. */ extern EXCMDLIST const cmds[]; /* Table of ex commands. */ /* * !!! * QUOTING NOTE: * * Historically, .exrc files and EXINIT variables could only use ^V as an * escape character, neither ^Q or a user specified character worked. We * enforce that here, just in case someone depends on it. */ #define IS_ESCAPE(sp, cmdp, ch) \ (F_ISSET(cmdp, E_VLITONLY) ? \ (ch) == CH_LITERAL : KEY_VAL(sp, ch) == K_VLNEXT) #define IS_SHELLMETA(sp, ch) \ ((ch) <= CHAR_MAX && strchr(O_STR(sp, O_SHELLMETA), ch) != NULL) /* * File state must be checked for each command -- any ex command may be entered * at any time, and most of them won't work well if a file hasn't yet been read * in. Historic vi generally took the easy way out and dropped core. */ -#define NEEDFILE(sp, cmdp) { \ +#define NEEDFILE(sp, cmdp) do { \ if ((sp)->ep == NULL) { \ ex_wemsg(sp, (cmdp)->cmd->name, EXM_NOFILEYET); \ return (1); \ } \ -} +} while (0) /* Range structures for global and @ commands. */ typedef struct _range RANGE; struct _range { /* Global command range. */ TAILQ_ENTRY(_range) q; /* Linked list of ranges. */ recno_t start, stop; /* Start/stop of the range. */ }; /* Ex command structure. */ struct _excmd { SLIST_ENTRY(_excmd) q; /* Linked list of commands. */ char *if_name; /* Associated file. */ recno_t if_lno; /* Associated line number. */ /* Clear the structure for the ex parser. */ #define CLEAR_EX_PARSER(cmdp) \ memset(&((cmdp)->cp), 0, ((char *)&(cmdp)->flags - \ (char *)&((cmdp)->cp)) + sizeof((cmdp)->flags)) CHAR_T *cp; /* Current command text. */ size_t clen; /* Current command length. */ CHAR_T *save_cmd; /* Remaining command. */ size_t save_cmdlen; /* Remaining command length. */ EXCMDLIST const *cmd; /* Command: entry in command table. */ EXCMDLIST rcmd; /* Command: table entry/replacement. */ TAILQ_HEAD(_rh, _range) rq[1]; /* @/global range: linked list. */ recno_t range_lno; /* @/global range: set line number. */ CHAR_T *o_cp; /* Original @/global command. */ size_t o_clen; /* Original @/global command length. */ #define AGV_AT 0x01 /* @ buffer execution. */ #define AGV_AT_NORANGE 0x02 /* @ buffer execution without range. */ #define AGV_GLOBAL 0x04 /* global command. */ #define AGV_V 0x08 /* v command. */ #define AGV_ALL (AGV_AT | AGV_AT_NORANGE | AGV_GLOBAL | AGV_V) u_int8_t agv_flags; /* Clear the structure before each ex command. */ -#define CLEAR_EX_CMD(cmdp) { \ +#define CLEAR_EX_CMD(cmdp) do { \ u_int32_t L__f = F_ISSET(cmdp, E_PRESERVE); \ memset(&((cmdp)->buffer), 0, ((char *)&(cmdp)->flags - \ (char *)&((cmdp)->buffer)) + sizeof((cmdp)->flags)); \ F_SET(cmdp, L__f); \ -} +} while (0) CHAR_T buffer; /* Command: named buffer. */ recno_t lineno; /* Command: line number. */ long count; /* Command: signed count. */ long flagoff; /* Command: signed flag offset. */ int addrcnt; /* Command: addresses (0, 1 or 2). */ MARK addr1; /* Command: 1st address. */ MARK addr2; /* Command: 2nd address. */ ARGS **argv; /* Command: array of arguments. */ int argc; /* Command: count of arguments. */ #define E_C_BUFFER 0x00001 /* Buffer name specified. */ #define E_C_CARAT 0x00002 /* ^ flag. */ #define E_C_COUNT 0x00004 /* Count specified. */ #define E_C_COUNT_NEG 0x00008 /* Count was signed negative. */ #define E_C_COUNT_POS 0x00010 /* Count was signed positive. */ #define E_C_DASH 0x00020 /* - flag. */ #define E_C_DOT 0x00040 /* . flag. */ #define E_C_EQUAL 0x00080 /* = flag. */ #define E_C_FORCE 0x00100 /* ! flag. */ #define E_C_HASH 0x00200 /* # flag. */ #define E_C_LIST 0x00400 /* l flag. */ #define E_C_PLUS 0x00800 /* + flag. */ #define E_C_PRINT 0x01000 /* p flag. */ u_int16_t iflags; /* User input information. */ #define __INUSE2 0x000007ff /* Same name space as EXCMDLIST. */ #define E_BLIGNORE 0x00000800 /* Ignore blank lines. */ #define E_NAMEDISCARD 0x00001000 /* Free/discard the name. */ #define E_NOAUTO 0x00002000 /* Don't do autoprint output. */ #define E_NOPRDEF 0x00004000 /* Don't print as default. */ #define E_NRSEP 0x00008000 /* Need to line adjust ex output. */ #define E_OPTNUM 0x00010000 /* Number edit option affected. */ #define E_VLITONLY 0x00020000 /* Use ^V quoting only. */ #define E_PRESERVE 0x0003f800 /* Bits to preserve across commands. */ #define E_ABSMARK 0x00040000 /* Set the absolute mark. */ #define E_ADDR_DEF 0x00080000 /* Default addresses used. */ #define E_DELTA 0x00100000 /* Search address with delta. */ #define E_MODIFY 0x00200000 /* File name expansion modified arg. */ #define E_MOVETOEND 0x00400000 /* Move to the end of the file first. */ #define E_NEWLINE 0x00800000 /* Found ending . */ #define E_SEARCH_WMSG 0x01000000 /* Display search-wrapped message. */ #define E_USELASTCMD 0x02000000 /* Use the last command. */ #define E_VISEARCH 0x04000000 /* It's really a vi search command. */ u_int32_t flags; /* Current flags. */ }; /* Ex private, per-screen memory. */ typedef struct _ex_private { /* Tag file list. */ TAILQ_HEAD(_tagfh, _tagf) tagfq[1]; TAILQ_HEAD(_tqh, _tagq) tq[1]; /* Tag queue. */ SLIST_HEAD(_csch, _csc) cscq[1];/* Cscope connection list. */ CHAR_T *tag_last; /* Saved last tag string. */ CHAR_T *lastbcomm; /* Last bang command. */ ARGS **args; /* Command: argument list. */ int argscnt; /* Command: argument list count. */ int argsoff; /* Command: offset into arguments. */ u_int32_t fdef; /* Saved E_C_* default command flags. */ char *ibp; /* File line input buffer. */ size_t ibp_len; /* File line input buffer length. */ CONVWIN ibcw; /* File line input conversion buffer. */ /* * Buffers for the ex output. The screen/vi support doesn't do any * character buffering of any kind. We do it here so that we're not * calling the screen output routines on every character. * * XXX * Change to grow dynamically. */ char obp[1024]; /* Ex output buffer. */ size_t obp_len; /* Ex output buffer length. */ #define EXP_CSCINIT 0x01 /* Cscope initialized. */ u_int8_t flags; } EX_PRIVATE; #define EXP(sp) ((EX_PRIVATE *)((sp)->ex_private)) /* * Filter actions: * * FILTER_BANG !: filter text through the utility. * FILTER_RBANG !: read from the utility (without stdin). * FILTER_READ read: read from the utility (with stdin). * FILTER_WRITE write: write to the utility, display its output. */ enum filtertype { FILTER_BANG, FILTER_RBANG, FILTER_READ, FILTER_WRITE }; /* Ex common error messages. */ typedef enum { EXM_EMPTYBUF, /* Empty buffer. */ EXM_FILECOUNT, /* Too many file names. */ EXM_NOCANON, /* No terminal interface. */ EXM_NOCANON_F, /* EXM_NOCANO: filter version. */ EXM_NOFILEYET, /* Illegal until a file read in. */ EXM_NOPREVBUF, /* No previous buffer specified. */ EXM_NOPREVRE, /* No previous RE specified. */ EXM_NOSUSPEND, /* No suspension. */ EXM_SECURE, /* Illegal if secure edit option set. */ EXM_SECURE_F, /* EXM_SECURE: filter version */ EXM_USAGE /* Standard usage message. */ } exm_t; /* Ex address error types. */ enum badaddr { A_COMBO, A_EMPTY, A_EOF, A_NOTSET, A_ZERO }; /* Ex common tag error messages. */ typedef enum { TAG_BADLNO, /* Tag line doesn't exist. */ TAG_EMPTY, /* Tags stack is empty. */ TAG_SEARCH /* Tags search pattern wasn't found. */ } tagmsg_t; #include "ex_def.h" #include "extern.h" Index: vendor/nvi/dist/ex/ex_argv.c =================================================================== --- vendor/nvi/dist/ex/ex_argv.c (revision 366306) +++ vendor/nvi/dist/ex/ex_argv.c (revision 366307) @@ -1,910 +1,918 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" static int argv_alloc(SCR *, size_t); static int argv_comp(const void *, const void *); static int argv_fexp(SCR *, EXCMD *, CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int); static int argv_sexp(SCR *, CHAR_T **, size_t *, size_t *); static int argv_flt_user(SCR *, EXCMD *, CHAR_T *, size_t); /* * argv_init -- * Build a prototype arguments list. * * PUBLIC: int argv_init(SCR *, EXCMD *); */ int argv_init(SCR *sp, EXCMD *excp) { EX_PRIVATE *exp; exp = EXP(sp); exp->argsoff = 0; argv_alloc(sp, 1); excp->argv = exp->args; excp->argc = exp->argsoff; return (0); } /* * argv_exp0 -- * Append a string to the argument list. * * PUBLIC: int argv_exp0(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_exp0(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; exp = EXP(sp); argv_alloc(sp, cmdlen); MEMCPY(exp->args[exp->argsoff]->bp, cmd, cmdlen); exp->args[exp->argsoff]->bp[cmdlen] = '\0'; exp->args[exp->argsoff]->len = cmdlen; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; return (0); } /* * argv_exp1 -- * Do file name expansion on a string, and append it to the * argument list. * * PUBLIC: int argv_exp1(SCR *, EXCMD *, CHAR_T *, size_t, int); */ int argv_exp1(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, int is_bang) { EX_PRIVATE *exp; size_t blen, len; CHAR_T *p, *t, *bp; GET_SPACE_RETW(sp, bp, blen, 512); len = 0; exp = EXP(sp); if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) { FREE_SPACEW(sp, bp, blen); return (1); } /* If it's empty, we're done. */ if (len != 0) { for (p = bp, t = bp + len; p < t; ++p) if (!cmdskip(*p)) break; if (p == t) goto ret; } else goto ret; (void)argv_exp0(sp, excp, bp, len); ret: FREE_SPACEW(sp, bp, blen); return (0); } /* * argv_exp2 -- * Do file name and shell expansion on a string, and append it to * the argument list. * * PUBLIC: int argv_exp2(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_exp2(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { size_t blen, len, n; int rval; CHAR_T *bp, *p; GET_SPACE_RETW(sp, bp, blen, 512); #define SHELLECHO L("echo ") #define SHELLOFFSET (SIZE(SHELLECHO) - 1) MEMCPY(bp, SHELLECHO, SHELLOFFSET); p = bp + SHELLOFFSET; len = SHELLOFFSET; #if defined(DEBUG) && 0 TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd); #endif if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) { rval = 1; goto err; } #if defined(DEBUG) && 0 TRACE(sp, "before shell: %d: {%s}\n", len, bp); #endif /* * Do shell word expansion -- it's very, very hard to figure out what * magic characters the user's shell expects. Historically, it was a * union of v7 shell and csh meta characters. We match that practice * by default, so ":read \%" tries to read a file named '%'. It would * make more sense to pass any special characters through the shell, * but then, if your shell was csh, the above example will behave * differently in nvi than in vi. If you want to get other characters * passed through to your shell, change the "meta" option. */ if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1)) n = 0; else { p = bp + SHELLOFFSET; n = len - SHELLOFFSET; for (; n > 0; --n, ++p) if (IS_SHELLMETA(sp, *p)) break; } /* * If we found a meta character in the string, fork a shell to expand * it. Unfortunately, this is comparatively slow. Historically, it * didn't matter much, since users don't enter meta characters as part * of pathnames that frequently. The addition of filename completion * broke that assumption because it's easy to use. To increase the * completion performance, nvi used to have an internal routine to * handle "filename*". However, the shell special characters does not * limit to "shellmeta", so such a hack breaks historic practice. * After it all, we split the completion logic out from here. */ switch (n) { case 0: p = bp + SHELLOFFSET; len -= SHELLOFFSET; rval = argv_exp3(sp, excp, p, len); break; default: if (argv_sexp(sp, &bp, &blen, &len)) { rval = 1; goto err; } p = bp; rval = argv_exp3(sp, excp, p, len); break; } err: FREE_SPACEW(sp, bp, blen); return (rval); } /* * argv_exp3 -- * Take a string and break it up into an argv, which is appended * to the argument list. * * PUBLIC: int argv_exp3(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_exp3(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; size_t len; int ch, off; CHAR_T *ap, *p; for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) { /* Skip any leading whitespace. */ for (; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (!cmdskip(ch)) break; } if (cmdlen == 0) break; /* * Determine the length of this whitespace delimited * argument. * * QUOTING NOTE: * * Skip any character preceded by the user's quoting * character. */ for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) { ch = *cmd; if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) { ++cmd; --cmdlen; } else if (cmdskip(ch)) break; } /* * Copy the argument into place. * * QUOTING NOTE: * * Lose quote chars. */ argv_alloc(sp, len); off = exp->argsoff; exp->args[off]->len = len; for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++) if (IS_ESCAPE(sp, excp, *ap)) ++ap; *p = '\0'; } excp->argv = exp->args; excp->argc = exp->argsoff; #if defined(DEBUG) && 0 for (cnt = 0; cnt < exp->argsoff; ++cnt) TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]); #endif return (0); } /* * argv_flt_ex -- * Filter the ex commands with a prefix, and append the results to * the argument list. * * PUBLIC: int argv_flt_ex(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_flt_ex(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; EXCMDLIST const *cp; int off; size_t len; exp = EXP(sp); for (off = exp->argsoff, cp = cmds; cp->name != NULL; ++cp) { len = STRLEN(cp->name); if (cmdlen > 0 && (cmdlen > len || MEMCMP(cmd, cp->name, cmdlen))) continue; /* Copy the matched ex command name. */ argv_alloc(sp, len + 1); MEMCPY(exp->args[exp->argsoff]->bp, cp->name, len + 1); exp->args[exp->argsoff]->len = len; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } return (0); } /* * argv_flt_user -- * Filter the ~user list on the system with a prefix, and append * the results to the argument list. */ static int argv_flt_user(SCR *sp, EXCMD *excp, CHAR_T *uname, size_t ulen) { EX_PRIVATE *exp; struct passwd *pw; int off; char *np; size_t len, nlen; exp = EXP(sp); off = exp->argsoff; /* The input must come with a leading '~'. */ INT2CHAR(sp, uname + 1, ulen - 1, np, nlen); if ((np = v_strdup(sp, np, nlen)) == NULL) return (1); setpwent(); while ((pw = getpwent()) != NULL) { len = strlen(pw->pw_name); if (nlen > 0 && (nlen > len || memcmp(np, pw->pw_name, nlen))) continue; /* Copy '~' + the matched user name. */ CHAR2INT(sp, pw->pw_name, len + 1, uname, ulen); argv_alloc(sp, ulen + 1); exp->args[exp->argsoff]->bp[0] = '~'; MEMCPY(exp->args[exp->argsoff]->bp + 1, uname, ulen); exp->args[exp->argsoff]->len = ulen; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } endpwent(); free(np); qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); return (0); } /* * argv_fexp -- * Do file name and bang command expansion. */ static int argv_fexp(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, CHAR_T *p, size_t *lenp, CHAR_T **bpp, size_t *blenp, int is_bang) { EX_PRIVATE *exp; char *t; size_t blen, len, off, tlen; CHAR_T *bp; CHAR_T *wp; size_t wlen; /* Replace file name characters. */ for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd) switch (*cmd) { case '!': if (!is_bang) goto ins_ch; exp = EXP(sp); if (exp->lastbcomm == NULL) { msgq(sp, M_ERR, "115|No previous command to replace \"!\""); return (1); } len += tlen = STRLEN(exp->lastbcomm); off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; MEMCPY(p, exp->lastbcomm, tlen); p += tlen; F_SET(excp, E_MODIFY); break; case '%': if ((t = sp->frp->name) == NULL) { msgq(sp, M_ERR, "116|No filename to substitute for %%"); return (1); } tlen = strlen(t); len += tlen; off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; CHAR2INT(sp, t, tlen, wp, wlen); MEMCPY(p, wp, wlen); p += wlen; F_SET(excp, E_MODIFY); break; case '#': if ((t = sp->alt_name) == NULL) { msgq(sp, M_ERR, "117|No filename to substitute for #"); return (1); } len += tlen = strlen(t); off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; CHAR2INT(sp, t, tlen, wp, wlen); MEMCPY(p, wp, wlen); p += wlen; F_SET(excp, E_MODIFY); break; case '\\': /* * QUOTING NOTE: * * Strip any backslashes that protected the file * expansion characters. */ if (cmdlen > 1 && (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) { ++cmd; --cmdlen; } /* FALLTHROUGH */ default: ins_ch: ++len; off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; *p++ = *cmd; } /* Nul termination. */ ++len; off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; *p = '\0'; /* Return the new string length, buffer, buffer length. */ *lenp = len - 1; *bpp = bp; *blenp = blen; return (0); } /* * argv_alloc -- * Make more space for arguments. */ static int argv_alloc(SCR *sp, size_t len) { ARGS *ap; EX_PRIVATE *exp; int cnt, off; /* * Allocate room for another argument, always leaving * enough room for an ARGS structure with a length of 0. */ #define INCREMENT 20 exp = EXP(sp); off = exp->argsoff; if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) { cnt = exp->argscnt + INCREMENT; REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *)); if (exp->args == NULL) { (void)argv_free(sp); goto mem; } memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *)); exp->argscnt = cnt; } /* First argument. */ if (exp->args[off] == NULL) { CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); if (exp->args[off] == NULL) goto mem; } /* First argument buffer. */ ap = exp->args[off]; ap->len = 0; if (ap->blen < len + 1) { ap->blen = len + 1; REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T)); if (ap->bp == NULL) { ap->bp = NULL; ap->blen = 0; F_CLR(ap, A_ALLOCATED); mem: msgq(sp, M_SYSERR, NULL); return (1); } F_SET(ap, A_ALLOCATED); } /* Second argument. */ if (exp->args[++off] == NULL) { CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); if (exp->args[off] == NULL) goto mem; } /* 0 length serves as end-of-argument marker. */ exp->args[off]->len = 0; return (0); } /* * argv_free -- * Free up argument structures. * * PUBLIC: int argv_free(SCR *); */ int argv_free(SCR *sp) { EX_PRIVATE *exp; int off; exp = EXP(sp); if (exp->args != NULL) { for (off = 0; off < exp->argscnt; ++off) { if (exp->args[off] == NULL) continue; if (F_ISSET(exp->args[off], A_ALLOCATED)) free(exp->args[off]->bp); free(exp->args[off]); } free(exp->args); } exp->args = NULL; exp->argscnt = 0; exp->argsoff = 0; return (0); } /* * argv_flt_path -- * Find all file names matching the prefix and append them to the * argument list. * * PUBLIC: int argv_flt_path(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_flt_path(SCR *sp, EXCMD *excp, CHAR_T *path, size_t plen) { struct dirent *dp; DIR *dirp; EX_PRIVATE *exp; int off; size_t dlen, len, nlen; CHAR_T *dname; CHAR_T *p, *np, *n; char *name, *tp, *epd = NULL; CHAR_T *wp; size_t wlen; exp = EXP(sp); /* Set up the name and length for comparison. */ if ((path = v_wstrdup(sp, path, plen)) == NULL) return (1); if ((p = STRRCHR(path, '/')) == NULL) { if (*path == '~') { int rc; /* Filter ~user list instead. */ rc = argv_flt_user(sp, excp, path, plen); free(path); return (rc); } dname = L("."); dlen = 0; np = path; } else { if (p == path) { dname = L("/"); dlen = 1; } else { *p = '\0'; dname = path; dlen = p - path; } np = p + 1; } INT2CHAR(sp, dname, dlen + 1, tp, nlen); if ((epd = expanduser(tp)) != NULL) tp = epd; if ((dirp = opendir(tp)) == NULL) { free(epd); free(path); return (1); } free(epd); INT2CHAR(sp, np, STRLEN(np), tp, nlen); if ((name = v_strdup(sp, tp, nlen)) == NULL) { free(path); return (1); } for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) { if (nlen == 0) { if (dp->d_name[0] == '.') continue; +#ifdef HAVE_DIRENT_D_NAMLEN len = dp->d_namlen; +#else + len = strlen(dp->d_name); +#endif } else { +#ifdef HAVE_DIRENT_D_NAMLEN len = dp->d_namlen; +#else + len = strlen(dp->d_name); +#endif if (len < nlen || memcmp(dp->d_name, name, nlen)) continue; } /* Directory + name + slash + null. */ CHAR2INT(sp, dp->d_name, len + 1, wp, wlen); argv_alloc(sp, dlen + wlen + 1); n = exp->args[exp->argsoff]->bp; if (dlen != 0) { MEMCPY(n, dname, dlen); n += dlen; if (dlen > 1 || dname[0] != '/') *n++ = '/'; exp->args[exp->argsoff]->len = dlen + 1; } MEMCPY(n, wp, wlen); exp->args[exp->argsoff]->len += wlen - 1; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } closedir(dirp); free(name); free(path); qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); return (0); } /* * argv_comp -- * Alphabetic comparison. */ static int argv_comp(const void *a, const void *b) { return (STRCMP((*(ARGS **)a)->bp, (*(ARGS **)b)->bp)); } /* * argv_sexp -- * Fork a shell, pipe a command through it, and read the output into * a buffer. */ static int argv_sexp(SCR *sp, CHAR_T **bpp, size_t *blenp, size_t *lenp) { enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval; FILE *ifp; pid_t pid; size_t blen, len; int ch, std_output[2]; CHAR_T *bp, *p; char *sh, *sh_path; char *np; size_t nlen; /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { msgq(sp, M_ERR, "289|Shell expansions not supported when the secure edit option is set"); return (1); } sh_path = O_STR(sp, O_SHELL); if ((sh = strrchr(sh_path, '/')) == NULL) sh = sh_path; else ++sh; /* Local copies of the buffer variables. */ bp = *bpp; blen = *blenp; /* * There are two different processes running through this code, named * the utility (the shell) and the parent. The utility reads standard * input and writes standard output and standard error output. The * parent writes to the utility, reads its standard output and ignores * its standard error output. Historically, the standard error output * was discarded by vi, as it produces a lot of noise when file patterns * don't match. * * The parent reads std_output[0], and the utility writes std_output[1]. */ ifp = NULL; std_output[0] = std_output[1] = -1; if (pipe(std_output) < 0) { msgq(sp, M_SYSERR, "pipe"); return (1); } if ((ifp = fdopen(std_output[0], "r")) == NULL) { msgq(sp, M_SYSERR, "fdopen"); goto err; } /* * Do the minimal amount of work possible, the shell is going to run * briefly and then exit. We sincerely hope. */ switch (pid = vfork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "vfork"); err: if (ifp != NULL) (void)fclose(ifp); else if (std_output[0] != -1) close(std_output[0]); if (std_output[1] != -1) close(std_output[0]); return (1); case 0: /* Utility. */ /* Redirect stdout to the write end of the pipe. */ (void)dup2(std_output[1], STDOUT_FILENO); /* Close the utility's file descriptors. */ (void)close(std_output[0]); (void)close(std_output[1]); (void)close(STDERR_FILENO); /* * XXX * Assume that all shells have -c. */ INT2CHAR(sp, bp, STRLEN(bp)+1, np, nlen); execl(sh_path, sh, "-c", np, (char *)NULL); msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s"); _exit(127); default: /* Parent. */ /* Close the pipe ends the parent won't use. */ (void)close(std_output[1]); break; } /* * Copy process standard output into a buffer. * * !!! * Historic vi apparently discarded leading \n and \r's from * the shell output stream. We don't on the grounds that any * shell that does that is broken. */ for (p = bp, len = 0, ch = EOF; (ch = GETC(ifp)) != EOF; *p++ = ch, blen-=sizeof(CHAR_T), ++len) if (blen < 5) { ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2); p = bp + len; blen = *blenp - len; } /* Delete the final newline, nul terminate the string. */ if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) { --p; --len; } *p = '\0'; *lenp = len; *bpp = bp; /* *blenp is already updated. */ if (ferror(ifp)) goto ioerr; if (fclose(ifp)) { ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s"); alloc_err: rval = SEXP_ERR; } else rval = SEXP_OK; /* * Wait for the process. If the shell process fails (e.g., "echo $q" * where q wasn't a defined variable) or if the returned string has * no characters or only blank characters, (e.g., "echo $5"), complain * that the shell expansion failed. We can't know for certain that's * the error, but it's a good guess, and it matches historic practice. * This won't catch "echo foo_$5", but that's not a common error and * historic vi didn't catch it either. */ if (proc_wait(sp, (long)pid, sh, 1, 0)) rval = SEXP_EXPANSION_ERR; for (p = bp; len; ++p, --len) if (!cmdskip(*p)) break; if (len == 0) rval = SEXP_EXPANSION_ERR; if (rval == SEXP_EXPANSION_ERR) msgq(sp, M_ERR, "304|Shell expansion failed"); return (rval == SEXP_OK ? 0 : 1); } /* * argv_esc -- * Escape a string into an ex and shell argument. * * PUBLIC: CHAR_T *argv_esc(SCR *, EXCMD *, CHAR_T *, size_t); */ CHAR_T * argv_esc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len) { size_t blen, off; CHAR_T *bp, *p; int ch; GET_SPACE_GOTOW(sp, bp, blen, len + 1); /* * Leaving the first '~' unescaped causes the user to need a * "./" prefix to edit a file which really starts with a '~'. * However, the file completion happens to not work for these * files without the prefix. * * All ex expansion characters, "!%#", are double escaped. */ for (p = bp; len > 0; ++str, --len) { ch = *str; off = p - bp; if (blen / sizeof(CHAR_T) - off < 3) { ADD_SPACE_GOTOW(sp, bp, blen, off + 3); p = bp + off; } if (cmdskip(ch) || ch == '\n' || IS_ESCAPE(sp, excp, ch)) /* Ex. */ *p++ = CH_LITERAL; else switch (ch) { case '~': /* ~user. */ if (p != bp) *p++ = '\\'; break; case '+': /* Ex +cmd. */ if (p == bp) *p++ = '\\'; break; case '!': case '%': case '#': /* Ex exp. */ *p++ = '\\'; *p++ = '\\'; break; case ',': case '-': case '.': case '/': /* Safe. */ case ':': case '=': case '@': case '_': break; default: /* Unsafe. */ if (isascii(ch) && !isalnum(ch)) *p++ = '\\'; } *p++ = ch; } *p = '\0'; return bp; alloc_err: return NULL; } /* * argv_uesc -- * Unescape an escaped ex and shell argument. * * PUBLIC: CHAR_T *argv_uesc(SCR *, EXCMD *, CHAR_T *, size_t); */ CHAR_T * argv_uesc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len) { size_t blen; CHAR_T *bp, *p; GET_SPACE_GOTOW(sp, bp, blen, len + 1); for (p = bp; len > 0; ++str, --len) { if (IS_ESCAPE(sp, excp, *str)) { if (--len < 1) break; ++str; } else if (*str == '\\') { if (--len < 1) break; ++str; /* Check for double escaping. */ if (*str == '\\' && len > 1) switch (str[1]) { case '!': case '%': case '#': ++str; --len; } } *p++ = *str; } *p = '\0'; return bp; alloc_err: return NULL; } Index: vendor/nvi/dist/ex/ex_bang.c =================================================================== --- vendor/nvi/dist/ex/ex_bang.c (revision 366306) +++ vendor/nvi/dist/ex/ex_bang.c (revision 366307) @@ -1,186 +1,187 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_bang -- :[line [,line]] ! command * * Pass the rest of the line after the ! character to the program named by * the O_SHELL option. * * Historical vi did NOT do shell expansion on the arguments before passing * them, only file name expansion. This means that the O_SHELL program got * "$t" as an argument if that is what the user entered. Also, there's a * special expansion done for the bang command. Any exclamation points in * the user's argument are replaced by the last, expanded ! command. * * There's some fairly amazing slop in this routine to make the different * ways of getting here display the right things. It took a long time to * get it right (wrong?), so be careful. * * PUBLIC: int ex_bang(SCR *, EXCMD *); */ int ex_bang(SCR *sp, EXCMD *cmdp) { enum filtertype ftype; ARGS *ap; EX_PRIVATE *exp; MARK rm; recno_t lno; int rval; const char *msg; char *np; size_t nlen; ap = cmdp->argv[0]; if (ap->len == 0) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } /* Set the "last bang command" remembered value. */ exp = EXP(sp); free(exp->lastbcomm); if ((exp->lastbcomm = v_wstrdup(sp, ap->bp, ap->len)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* * If the command was modified by the expansion, it was historically * redisplayed. */ if (F_ISSET(cmdp, E_MODIFY) && !F_ISSET(sp, SC_EX_SILENT)) { /* * Display the command if modified. Historic ex/vi displayed * the command if it was modified due to file name and/or bang * expansion. If piping lines in vi, it would be immediately * overwritten by any error or line change reporting. */ if (F_ISSET(sp, SC_VI)) vs_update(sp, "!", ap->bp); else { (void)ex_printf(sp, "!"WS"\n", ap->bp); (void)ex_fflush(sp); } } /* * If no addresses were specified, run the command. If there's an * underlying file, it's been modified and autowrite is set, write * the file back. If the file has been modified, autowrite is not * set and the warn option is set, tell the user about the file. */ if (cmdp->addrcnt == 0) { msg = NULL; - if (sp->ep != NULL && F_ISSET(sp->ep, F_MODIFIED)) + if (sp->ep != NULL && F_ISSET(sp->ep, F_MODIFIED)) { if (O_ISSET(sp, O_AUTOWRITE)) { if (file_aw(sp, FS_ALL)) return (0); } else if (O_ISSET(sp, O_WARN) && !F_ISSET(sp, SC_EX_SILENT)) msg = msg_cat(sp, "303|File modified since last write.", NULL); + } /* If we're still in a vi screen, move out explicitly. */ INT2CHAR(sp, ap->bp, ap->len+1, np, nlen); (void)ex_exec_proc(sp, cmdp, np, msg, !F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)); } /* * If addresses were specified, pipe lines from the file through the * command. * * Historically, vi lines were replaced by both the stdout and stderr * lines of the command, but ex lines by only the stdout lines. This * makes no sense to me, so nvi makes it consistent for both, and * matches vi's historic behavior. */ else { NEEDFILE(sp, cmdp); /* Autoprint is set historically, even if the command fails. */ F_SET(cmdp, E_AUTOPRINT); /* * !!! * Historical vi permitted "!!" in an empty file. When this * happens, we arrive here with two addresses of 1,1 and a * bad attitude. The simple solution is to turn it into a * FILTER_READ operation, with the exception that stdin isn't * opened for the utility, and the cursor position isn't the * same. The only historic glitch (I think) is that we don't * put an empty line into the default cut buffer, as historic * vi did. Imagine, if you can, my disappointment. */ ftype = FILTER_BANG; if (cmdp->addr1.lno == 1 && cmdp->addr2.lno == 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { cmdp->addr1.lno = cmdp->addr2.lno = 0; ftype = FILTER_RBANG; } } rval = ex_filter(sp, cmdp, &cmdp->addr1, &cmdp->addr2, &rm, ap->bp, ftype); /* * If in vi mode, move to the first nonblank. * * !!! * Historic vi wasn't consistent in this area -- if you used * a forward motion it moved to the first nonblank, but if you * did a backward motion it didn't. And, if you followed a * backward motion with a forward motion, it wouldn't move to * the nonblank for either. Going to the nonblank generally * seems more useful and consistent, so we do it. */ sp->lno = rm.lno; if (F_ISSET(sp, SC_VI)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } else sp->cno = rm.cno; } /* Ex terminates with a bang, even if the command fails. */ if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT)) (void)ex_puts(sp, "!\n"); /* Apply expandtab to the new text */ if (O_ISSET(sp, O_EXPANDTAB)) ex_retab(sp, cmdp); /* * XXX * The ! commands never return an error, so that autoprint always * happens in the ex parser. */ return (0); } Index: vendor/nvi/dist/ex/ex_cscope.c =================================================================== --- vendor/nvi/dist/ex/ex_cscope.c (revision 366306) +++ vendor/nvi/dist/ex/ex_cscope.c (revision 366307) @@ -1,1084 +1,1086 @@ /*- * Copyright (c) 1994, 1996 * Rob Mayoff. All rights reserved. * Copyright (c) 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "pathnames.h" #include "tag.h" #define CSCOPE_DBFILE "cscope.out" #define CSCOPE_PATHS "cscope.tpath" /* * 0name find all uses of name * 1name find definition of name * 2name find all function calls made from name * 3name find callers of name * 4string find text string (cscope 12.9) * 4name find assignments to name (cscope 13.3) * 5pattern change pattern -- NOT USED * 6pattern find pattern * 7name find files with name as substring * 8name find files #including name */ #define FINDHELP "\ find c|d|e|f|g|i|s|t buffer|pattern\n\ c: find callers of name\n\ d: find all function calls made from name\n\ e: find pattern\n\ f: find files with name as substring\n\ g: find definition of name\n\ i: find files #including name\n\ s: find all uses of name\n\ t: find assignments to name" static int cscope_add(SCR *, EXCMD *, CHAR_T *); static int cscope_find(SCR *, EXCMD*, CHAR_T *); static int cscope_help(SCR *, EXCMD *, CHAR_T *); static int cscope_kill(SCR *, EXCMD *, CHAR_T *); static int cscope_reset(SCR *, EXCMD *, CHAR_T *); typedef struct _cc { char *name; int (*function)(SCR *, EXCMD *, CHAR_T *); char *help_msg; char *usage_msg; } CC; static CC const cscope_cmds[] = { { "add", cscope_add, "Add a new cscope database", "add file | directory" }, { "find", cscope_find, "Query the databases for a pattern", FINDHELP }, { "help", cscope_help, "Show help for cscope commands", "help [command]" }, { "kill", cscope_kill, "Kill a cscope connection", "kill number" }, { "reset", cscope_reset, "Discard all current cscope connections", "reset" }, { NULL } }; static TAGQ *create_cs_cmd(SCR *, char *, size_t *); static int csc_help(SCR *, char *); static void csc_file(SCR *, CSC *, char *, char **, size_t *, int *); static int get_paths(SCR *, CSC *); static CC const *lookup_ccmd(char *); static int parse(SCR *, CSC *, TAGQ *, int *); static int read_prompt(SCR *, CSC *); static int run_cscope(SCR *, CSC *, char *); static int start_cscopes(SCR *, EXCMD *); static int terminate(SCR *, CSC *, int); /* * ex_cscope -- * Perform an ex cscope. * * PUBLIC: int ex_cscope(SCR *, EXCMD *); */ int ex_cscope(SCR *sp, EXCMD *cmdp) { CC const *ccp; EX_PRIVATE *exp; int i; CHAR_T *cmd; CHAR_T *p; char *np; size_t nlen; /* Initialize the default cscope directories. */ exp = EXP(sp); if (!F_ISSET(exp, EXP_CSCINIT) && start_cscopes(sp, cmdp)) return (1); F_SET(exp, EXP_CSCINIT); /* Skip leading whitespace. */ for (p = cmdp->argv[0]->bp, i = cmdp->argv[0]->len; i > 0; --i, ++p) if (!isspace(*p)) break; if (i == 0) goto usage; /* Skip the command to any arguments. */ for (cmd = p; i > 0; --i, ++p) if (isspace(*p)) break; if (*p != '\0') { *p++ = '\0'; for (; *p && isspace(*p); ++p); } INT2CHAR(sp, cmd, STRLEN(cmd) + 1, np, nlen); if ((ccp = lookup_ccmd(np)) == NULL) { usage: msgq(sp, M_ERR, "309|Use \"cscope help\" for help"); return (1); } /* Call the underlying function. */ return (ccp->function(sp, cmdp, p)); } /* * start_cscopes -- * Initialize the cscope package. */ static int start_cscopes(SCR *sp, EXCMD *cmdp) { size_t blen, len; char *bp, *cscopes, *p, *t; CHAR_T *wp; size_t wlen; /* * EXTENSION #1: * * If the CSCOPE_DIRS environment variable is set, we treat it as a * list of cscope directories that we're using, similar to the tags * edit option. * * XXX * This should probably be an edit option, although that implies that * we start/stop cscope processes periodically, instead of once when * the editor starts. */ if ((cscopes = getenv("CSCOPE_DIRS")) == NULL) return (0); len = strlen(cscopes); GET_SPACE_RETC(sp, bp, blen, len); memcpy(bp, cscopes, len + 1); for (cscopes = t = bp; (p = strsep(&t, "\t :")) != NULL;) if (*p != '\0') { CHAR2INT(sp, p, strlen(p) + 1, wp, wlen); (void)cscope_add(sp, cmdp, wp); } FREE_SPACE(sp, bp, blen); return (0); } /* * cscope_add -- * The cscope add command. */ static int cscope_add(SCR *sp, EXCMD *cmdp, CHAR_T *dname) { struct stat sb; EX_PRIVATE *exp; CSC *csc; size_t len; int cur_argc; char *dbname, *path; char *np = NULL; size_t nlen; exp = EXP(sp); /* * 0 additional args: usage. * 1 additional args: matched a file. * >1 additional args: object, too many args. */ cur_argc = cmdp->argc; if (argv_exp2(sp, cmdp, dname, STRLEN(dname))) { return (1); } if (cmdp->argc == cur_argc) { (void)csc_help(sp, "add"); return (1); } if (cmdp->argc == cur_argc + 1) dname = cmdp->argv[cur_argc]->bp; else { ex_emsg(sp, np, EXM_FILECOUNT); return (1); } INT2CHAR(sp, dname, STRLEN(dname)+1, np, nlen); /* * The user can specify a specific file (so they can have multiple * Cscope databases in a single directory) or a directory. If the * file doesn't exist, we're done. If it's a directory, append the * standard database file name and try again. Store the directory * name regardless so that we can use it as a base for searches. */ if (stat(np, &sb)) { msgq(sp, M_SYSERR, "%s", np); return (1); } if (S_ISDIR(sb.st_mode)) { if ((path = join(np, CSCOPE_DBFILE)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } if (stat(path, &sb)) { msgq(sp, M_SYSERR, "%s", path); free(path); return (1); } free(path); dbname = CSCOPE_DBFILE; } else if ((dbname = strrchr(np, '/')) != NULL) *dbname++ = '\0'; else { dbname = np; np = "."; } /* Allocate a cscope connection structure and initialize its fields. */ len = strlen(np); CALLOC_RET(sp, csc, 1, sizeof(CSC) + len); csc->dname = csc->buf; csc->dlen = len; memcpy(csc->dname, np, len); - csc->mtim = sb.st_mtimespec; + csc->mtim = sb.st_mtim; /* Get the search paths for the cscope. */ if (get_paths(sp, csc)) goto err; /* Start the cscope process. */ if (run_cscope(sp, csc, dbname)) goto err; /* * Add the cscope connection to the screen's list. From now on, * on error, we have to call terminate, which expects the csc to * be on the chain. */ SLIST_INSERT_HEAD(exp->cscq, csc, q); /* Read the initial prompt from the cscope to make sure it's okay. */ return read_prompt(sp, csc); err: free(csc); return (1); } /* * get_paths -- * Get the directories to search for the files associated with this * cscope database. */ static int get_paths(SCR *sp, CSC *csc) { struct stat sb; int fd, nentries; size_t len; char *p, **pathp, *buf; /* * EXTENSION #2: * * If there's a cscope directory with a file named CSCOPE_PATHS, it * contains a colon-separated list of paths in which to search for * files returned by cscope. * * XXX * These paths are absolute paths, and not relative to the cscope * directory. To fix this, rewrite the each path using the cscope * directory as a prefix. */ if ((buf = join(csc->dname, CSCOPE_PATHS)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } if (stat(buf, &sb) == 0) { /* Read in the CSCOPE_PATHS file. */ len = sb.st_size; MALLOC_RET(sp, csc->pbuf, len + 1); if ((fd = open(buf, O_RDONLY, 0)) < 0 || read(fd, csc->pbuf, len) != len) { msgq_str(sp, M_SYSERR, buf, "%s"); if (fd >= 0) (void)close(fd); free(buf); return (1); } (void)close(fd); free(buf); csc->pbuf[len] = '\0'; /* Count up the entries. */ for (nentries = 0, p = csc->pbuf; *p != '\0'; ++p) if (p[0] == ':' && p[1] != '\0') ++nentries; /* Build an array of pointers to the paths. */ CALLOC_GOTO(sp, csc->paths, nentries + 1, sizeof(char **)); for (pathp = csc->paths, p = strtok(csc->pbuf, ":"); p != NULL; p = strtok(NULL, ":")) *pathp++ = p; return (0); } free(buf); /* * If the CSCOPE_PATHS file doesn't exist, we look for files * relative to the cscope directory. */ if ((csc->pbuf = strdup(csc->dname)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } CALLOC_GOTO(sp, csc->paths, 2, sizeof(char *)); csc->paths[0] = csc->pbuf; return (0); alloc_err: free(csc->pbuf); csc->pbuf = NULL; return (1); } /* * run_cscope -- * Fork off the cscope process. */ static int run_cscope(SCR *sp, CSC *csc, char *dbname) { int to_cs[2], from_cs[2]; char *cmd; /* * Cscope reads from to_cs[0] and writes to from_cs[1]; vi reads from * from_cs[0] and writes to to_cs[1]. */ to_cs[0] = to_cs[1] = from_cs[0] = from_cs[1] = -1; if (pipe(to_cs) < 0 || pipe(from_cs) < 0) { msgq(sp, M_SYSERR, "pipe"); goto err; } switch (csc->pid = vfork()) { char *dn, *dbn; case -1: msgq(sp, M_SYSERR, "vfork"); err: if (to_cs[0] != -1) (void)close(to_cs[0]); if (to_cs[1] != -1) (void)close(to_cs[1]); if (from_cs[0] != -1) (void)close(from_cs[0]); if (from_cs[1] != -1) (void)close(from_cs[1]); return (1); case 0: /* child: run cscope. */ (void)dup2(to_cs[0], STDIN_FILENO); (void)dup2(from_cs[1], STDOUT_FILENO); (void)dup2(from_cs[1], STDERR_FILENO); /* Close unused file descriptors. */ (void)close(to_cs[1]); (void)close(from_cs[0]); /* Run the cscope command. */ #define CSCOPE_CMD_FMT "cd %s && exec cscope -dl -f %s" if ((dn = quote(csc->dname)) == NULL) goto nomem; if ((dbn = quote(dbname)) == NULL) { free(dn); goto nomem; } - (void)asprintf(&cmd, CSCOPE_CMD_FMT, dn, dbn); + if (asprintf(&cmd, CSCOPE_CMD_FMT, dn, dbn) == -1) + cmd = NULL; free(dbn); free(dn); if (cmd == NULL) { nomem: msgq(sp, M_SYSERR, NULL); _exit (1); } (void)execl(_PATH_BSHELL, "sh", "-c", cmd, (char *)NULL); msgq_str(sp, M_SYSERR, cmd, "execl: %s"); free(cmd); _exit (127); /* NOTREACHED */ default: /* parent. */ /* Close unused file descriptors. */ (void)close(to_cs[0]); (void)close(from_cs[1]); /* * Save the file descriptors for later duplication, and * reopen as streams. */ csc->to_fd = to_cs[1]; csc->to_fp = fdopen(to_cs[1], "w"); csc->from_fd = from_cs[0]; csc->from_fp = fdopen(from_cs[0], "r"); break; } return (0); } /* * cscope_find -- * The cscope find command. */ static int cscope_find(SCR *sp, EXCMD *cmdp, CHAR_T *pattern) { CSC *csc, *csc_next; EX_PRIVATE *exp; FREF *frp; TAGQ *rtqp, *tqp; TAG *rtp; recno_t lno; size_t cno, search; int force, istmp, matches; char *np = NULL; size_t nlen; exp = EXP(sp); /* Check for connections. */ if (SLIST_EMPTY(exp->cscq)) { msgq(sp, M_ERR, "310|No cscope connections running"); return (1); } /* * Allocate all necessary memory before doing anything hard. If the * tags stack is empty, we'll need the `local context' TAGQ structure * later. */ rtp = NULL; rtqp = NULL; if (TAILQ_EMPTY(exp->tq)) { /* Initialize the `local context' tag queue structure. */ CALLOC_GOTO(sp, rtqp, 1, sizeof(TAGQ)); TAILQ_INIT(rtqp->tagq); /* Initialize and link in its tag structure. */ CALLOC_GOTO(sp, rtp, 1, sizeof(TAG)); TAILQ_INSERT_HEAD(rtqp->tagq, rtp, q); rtqp->current = rtp; } /* Create the cscope command. */ INT2CHAR(sp, pattern, STRLEN(pattern) + 1, np, nlen); np = strdup(np); if ((tqp = create_cs_cmd(sp, np, &search)) == NULL) goto err; free(np); np = NULL; /* * Stick the current context in a convenient place, we'll lose it * when we switch files. */ frp = sp->frp; lno = sp->lno; cno = sp->cno; istmp = F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN); /* Search all open connections for a match. */ matches = 0; /* Copy next connect here in case csc is killed. */ SLIST_FOREACH_SAFE(csc, exp->cscq, q, csc_next) { /* * Send the command to the cscope program. (We skip the * first two bytes of the command, because we stored the * search cscope command character and a leading space * there.) */ (void)fprintf(csc->to_fp, "%lu%s\n", search, tqp->tag + 2); (void)fflush(csc->to_fp); /* Read the output. */ if (parse(sp, csc, tqp, &matches)) goto nomatch; } if (matches == 0) { msgq(sp, M_INFO, "278|No matches for query"); nomatch: free(rtp); free(rtqp); tagq_free(sp, tqp); return (1); } /* Try to switch to the first tag. */ force = FL_ISSET(cmdp->iflags, E_C_FORCE); if (F_ISSET(cmdp, E_NEWSCREEN)) { if (ex_tag_Nswitch(sp, tqp->current, force)) goto err; /* Everything else gets done in the new screen. */ sp = sp->nextdisp; exp = EXP(sp); } else if (ex_tag_nswitch(sp, tqp->current, force)) goto err; /* * If this is the first tag, put a `current location' queue entry * in place, so we can pop all the way back to the current mark. * Note, it doesn't point to much of anything, it's a placeholder. */ if (TAILQ_EMPTY(exp->tq)) { TAILQ_INSERT_HEAD(exp->tq, rtqp, q); } else rtqp = TAILQ_FIRST(exp->tq); /* Link the current TAGQ structure into place. */ TAILQ_INSERT_HEAD(exp->tq, tqp, q); (void)cscope_search(sp, tqp, tqp->current); /* * Move the current context from the temporary save area into the * right structure. * * If we were in a temporary file, we don't have a context to which * we can return, so just make it be the same as what we're moving * to. It will be a little odd that ^T doesn't change anything, but * I don't think it's a big deal. */ if (istmp) { rtqp->current->frp = sp->frp; rtqp->current->lno = sp->lno; rtqp->current->cno = sp->cno; } else { rtqp->current->frp = frp; rtqp->current->lno = lno; rtqp->current->cno = cno; } return (0); err: alloc_err: free(rtqp); free(rtp); free(np); return (1); } /* * create_cs_cmd -- * Build a cscope command, creating and initializing the base TAGQ. */ static TAGQ * create_cs_cmd(SCR *sp, char *pattern, size_t *searchp) { CB *cbp; TAGQ *tqp; size_t tlen; char *p; /* * Cscope supports a "change pattern" command which we never use, * cscope command 5. Set CSCOPE_QUERIES[5] to " " since the user * can't pass " " as the first character of pattern. That way the * user can't ask for pattern 5 so we don't need any special-case * code. */ #define CSCOPE_QUERIES "sgdct efi" if (pattern == NULL) goto usage; /* Skip leading blanks, check for command character. */ for (; cmdskip(pattern[0]); ++pattern); if (pattern[0] == '\0' || !cmdskip(pattern[1])) goto usage; for (*searchp = 0, p = CSCOPE_QUERIES; *p != '\0' && *p != pattern[0]; ++*searchp, ++p); if (*p == '\0') { msgq(sp, M_ERR, "311|%s: unknown search type: use one of %s", KEY_NAME(sp, pattern[0]), CSCOPE_QUERIES); return (NULL); } /* Skip characters to the pattern. */ for (p = pattern + 1; *p != '\0' && cmdskip(*p); ++p); if (*p == '\0') { usage: (void)csc_help(sp, "find"); return (NULL); } /* The user can specify the contents of a buffer as the pattern. */ cbp = NULL; if (p[0] == '"' && p[1] != '\0' && p[2] == '\0') CBNAME(sp, cbp, p[1]); if (cbp != NULL) { INT2CHAR(sp, TAILQ_FIRST(cbp->textq)->lb, TAILQ_FIRST(cbp->textq)->len, p, tlen); } else tlen = strlen(p); /* Allocate and initialize the TAGQ structure. */ CALLOC(sp, tqp, 1, sizeof(TAGQ) + tlen + 3); if (tqp == NULL) return (NULL); TAILQ_INIT(tqp->tagq); tqp->tag = tqp->buf; tqp->tag[0] = pattern[0]; tqp->tag[1] = ' '; tqp->tlen = tlen + 2; memcpy(tqp->tag + 2, p, tlen); tqp->tag[tlen + 2] = '\0'; F_SET(tqp, TAG_CSCOPE); return (tqp); } /* * parse -- * Parse the cscope output. */ static int parse(SCR *sp, CSC *csc, TAGQ *tqp, int *matchesp) { TAG *tp; recno_t slno = 0; size_t dlen, nlen = 0, slen = 0; int ch, i, isolder = 0, nlines; char *dname = NULL, *name = NULL, *search, *p, *t, dummy[2], buf[2048]; CHAR_T *wp; size_t wlen; for (;;) { if (!fgets(buf, sizeof(buf), csc->from_fp)) goto io_err; /* * If the database is out of date, or there's some other * problem, cscope will output error messages before the * number-of-lines output. Display/discard any output * that doesn't match what we want. */ #define CSCOPE_NLINES_FMT "cscope: %d lines%1[\n]" if (sscanf(buf, CSCOPE_NLINES_FMT, &nlines, dummy) == 2) break; if ((p = strchr(buf, '\n')) != NULL) *p = '\0'; msgq(sp, M_ERR, "%s: \"%s\"", csc->dname, buf); } while (nlines--) { if (fgets(buf, sizeof(buf), csc->from_fp) == NULL) goto io_err; /* If the line's too long for the buffer, discard it. */ if ((p = strchr(buf, '\n')) == NULL) { while ((ch = getc(csc->from_fp)) != EOF && ch != '\n'); continue; } *p = '\0'; /* * The cscope output is in the following format: * * * * Figure out how long everything is so we can allocate in one * swell foop, but discard anything that looks wrong. */ for (p = buf, i = 0; i < 3 && (t = strsep(&p, "\t ")) != NULL; ++i) switch (i) { case 0: /* Filename. */ name = t; nlen = strlen(name); break; case 1: /* Context. */ break; case 2: /* Line number. */ slno = (recno_t)atol(t); break; } if (i != 3 || p == NULL || t == NULL) continue; /* The rest of the string is the search pattern. */ search = p; slen = strlen(p); /* Resolve the file name. */ csc_file(sp, csc, name, &dname, &dlen, &isolder); /* * If the file is older than the cscope database, that is, * the database was built since the file was last modified, * or there wasn't a search string, use the line number. */ if (isolder || strcmp(search, "") == 0) { search = NULL; slen = 0; } /* * Allocate and initialize a tag structure plus the variable * length cscope information that follows it. */ CALLOC_RET(sp, tp, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + (slen + 1) * sizeof(CHAR_T)); tp->fname = (char *)tp->buf; if (dlen == 1 && *dname == '.') --dlen; else if (dlen != 0) { memcpy(tp->fname, dname, dlen); tp->fname[dlen] = '/'; ++dlen; } memcpy(tp->fname + dlen, name, nlen + 1); tp->fnlen = dlen + nlen; tp->slno = slno; if (slen != 0) { tp->search = (CHAR_T*)(tp->fname + tp->fnlen + 1); CHAR2INT(sp, search, slen + 1, wp, wlen); MEMCPY(tp->search, wp, (tp->slen = slen) + 1); } TAILQ_INSERT_TAIL(tqp->tagq, tp, q); /* Try to preset the tag within the current file. */ if (sp->frp != NULL && sp->frp->name != NULL && tqp->current == NULL && !strcmp(tp->fname, sp->frp->name)) tqp->current = tp; ++*matchesp; } if (tqp->current == NULL) tqp->current = TAILQ_FIRST(tqp->tagq); return read_prompt(sp, csc); io_err: if (feof(csc->from_fp)) errno = EIO; msgq_str(sp, M_SYSERR, "%s", csc->dname); terminate(sp, csc, 0); return (1); } /* * csc_file -- * Search for the right path to this file. */ static void csc_file(SCR *sp, CSC *csc, char *name, char **dirp, size_t *dlenp, int *isolderp) { struct stat sb; char **pp, *buf; /* * Check for the file in all of the listed paths. If we don't * find it, we simply return it unchanged. We have to do this * now, even though it's expensive, because if the user changes * directories, we can't change our minds as to where the file * lives. */ for (pp = csc->paths; *pp != NULL; ++pp) { if ((buf = join(*pp, name)) == NULL) { msgq(sp, M_SYSERR, NULL); *dlenp = 0; return; } if (stat(buf, &sb) == 0) { free(buf); *dirp = *pp; *dlenp = strlen(*pp); *isolderp = timespeccmp( - &sb.st_mtimespec, &csc->mtim, <); + &sb.st_mtim, &csc->mtim, <); return; } free(buf); } *dlenp = 0; } /* * cscope_help -- * The cscope help command. */ static int cscope_help(SCR *sp, EXCMD *cmdp, CHAR_T *subcmd) { char *np; size_t nlen; INT2CHAR(sp, subcmd, STRLEN(subcmd) + 1, np, nlen); return (csc_help(sp, np)); } /* * csc_help -- * Display help/usage messages. */ static int csc_help(SCR *sp, char *cmd) { CC const *ccp; - if (cmd != NULL && *cmd != '\0') + if (cmd != NULL && *cmd != '\0') { if ((ccp = lookup_ccmd(cmd)) == NULL) { ex_printf(sp, "%s doesn't match any cscope command\n", cmd); return (1); } else { ex_printf(sp, "Command: %s (%s)\n", ccp->name, ccp->help_msg); ex_printf(sp, " Usage: %s\n", ccp->usage_msg); return (0); } + } ex_printf(sp, "cscope commands:\n"); for (ccp = cscope_cmds; ccp->name != NULL; ++ccp) ex_printf(sp, " %*s: %s\n", 5, ccp->name, ccp->help_msg); return (0); } /* * cscope_kill -- * The cscope kill command. */ static int cscope_kill(SCR *sp, EXCMD *cmdp, CHAR_T *cn) { char *np; size_t nlen; int n = 1; if (*cn) { INT2CHAR(sp, cn, STRLEN(cn) + 1, np, nlen); n = atoi(np); } return (terminate(sp, NULL, n)); } /* * terminate -- * Detach from a cscope process. */ static int terminate(SCR *sp, CSC *csc, int n) { EX_PRIVATE *exp; int i = 0, pstat; CSC *cp, *pre_cp = NULL; exp = EXP(sp); /* * We either get a csc structure or a number. Locate and remove * the candidate which matches the structure or the number. */ if (csc == NULL && n < 1) goto badno; SLIST_FOREACH(cp, exp->cscq, q) { ++i; if (csc == NULL ? i != n : cp != csc) { pre_cp = cp; continue; } if (cp == SLIST_FIRST(exp->cscq)) SLIST_REMOVE_HEAD(exp->cscq, q); else SLIST_REMOVE_AFTER(pre_cp, q); csc = cp; break; } if (csc == NULL) { badno: msgq(sp, M_ERR, "312|%d: no such cscope session", n); return (1); } /* * XXX * Theoretically, we have the only file descriptors to the process, * so closing them should let it exit gracefully, deleting temporary * files, etc. However, the earlier created cscope processes seems * to refuse to quit unless we send a SIGTERM signal. */ if (csc->from_fp != NULL) (void)fclose(csc->from_fp); if (csc->to_fp != NULL) (void)fclose(csc->to_fp); if (i > 1) (void)kill(csc->pid, SIGTERM); (void)waitpid(csc->pid, &pstat, 0); /* Discard cscope connection information. */ free(csc->pbuf); free(csc->paths); free(csc); return (0); } /* * cscope_reset -- * The cscope reset command. */ static int cscope_reset(SCR *sp, EXCMD *cmdp, CHAR_T *notusedp) { return cscope_end(sp); } /* * cscope_end -- * End all cscope connections. * * PUBLIC: int cscope_end(SCR *); */ int cscope_end(SCR *sp) { EX_PRIVATE *exp; for (exp = EXP(sp); !SLIST_EMPTY(exp->cscq);) if (terminate(sp, NULL, 1)) return (1); return (0); } /* * cscope_display -- * Display current connections. * * PUBLIC: int cscope_display(SCR *); */ int cscope_display(SCR *sp) { EX_PRIVATE *exp; CSC *csc; int i = 0; exp = EXP(sp); if (SLIST_EMPTY(exp->cscq)) { ex_printf(sp, "No cscope connections.\n"); return (0); } SLIST_FOREACH(csc, exp->cscq, q) ex_printf(sp, "%2d %s (process %lu)\n", ++i, csc->dname, (u_long)csc->pid); return (0); } /* * cscope_search -- * Search a file for a cscope entry. * * PUBLIC: int cscope_search(SCR *, TAGQ *, TAG *); */ int cscope_search(SCR *sp, TAGQ *tqp, TAG *tp) { MARK m; /* If we don't have a search pattern, use the line number. */ if (tp->search == NULL) { if (!db_exist(sp, tp->slno)) { tag_msg(sp, TAG_BADLNO, tqp->tag); return (1); } m.lno = tp->slno; } else { /* * Search for the tag; cheap fallback for C functions * if the name is the same but the arguments have changed. */ m.lno = 1; m.cno = 0; if (f_search(sp, &m, &m, tp->search, tp->slen, NULL, SEARCH_CSCOPE | SEARCH_FILE)) { tag_msg(sp, TAG_SEARCH, tqp->tag); return (1); } /* * !!! * Historically, tags set the search direction if it wasn't * already set. */ if (sp->searchdir == NOTSET) sp->searchdir = FORWARD; } /* * !!! * Tags move to the first non-blank, NOT the search pattern start. */ sp->lno = m.lno; sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); return (0); } /* * lookup_ccmd -- * Return a pointer to the command structure. */ static CC const * lookup_ccmd(char *name) { CC const *ccp; size_t len; len = strlen(name); for (ccp = cscope_cmds; ccp->name != NULL; ++ccp) if (strncmp(name, ccp->name, len) == 0) return (ccp); return (NULL); } /* * read_prompt -- * Read a prompt from cscope. */ static int read_prompt(SCR *sp, CSC *csc) { int ch; #define CSCOPE_PROMPT ">> " for (;;) { while ((ch = getc(csc->from_fp)) != EOF && ch != CSCOPE_PROMPT[0]); if (ch == EOF) { terminate(sp, csc, 0); return (1); } if (getc(csc->from_fp) != CSCOPE_PROMPT[1]) continue; if (getc(csc->from_fp) != CSCOPE_PROMPT[2]) continue; break; } return (0); } Index: vendor/nvi/dist/ex/ex_filter.c =================================================================== --- vendor/nvi/dist/ex/ex_filter.c (revision 366306) +++ vendor/nvi/dist/ex/ex_filter.c (revision 366307) @@ -1,314 +1,315 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" static int filter_ldisplay(SCR *, FILE *); /* * ex_filter -- * Run a range of lines through a filter utility and optionally * replace the original text with the stdout/stderr output of * the utility. * * PUBLIC: int ex_filter(SCR *, * PUBLIC: EXCMD *, MARK *, MARK *, MARK *, CHAR_T *, enum filtertype); */ int ex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, CHAR_T *cmd, enum filtertype ftype) { FILE *ifp, *ofp; pid_t parent_writer_pid, utility_pid; recno_t nread; int input[2], output[2], rval; char *name; char *np; size_t nlen; rval = 0; /* Set return cursor position, which is never less than line 1. */ *rp = *fm; if (rp->lno == 0) rp->lno = 1; /* We're going to need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); /* * There are three different processes running through this code. * They are the utility, the parent-writer and the parent-reader. * The parent-writer is the process that writes from the file to * the utility, the parent reader is the process that reads from * the utility. * * Input and output are named from the utility's point of view. * The utility reads from input[0] and the parent(s) write to * input[1]. The parent(s) read from output[0] and the utility * writes to output[1]. * * !!! * Historically, in the FILTER_READ case, the utility reads from * the terminal (e.g. :r! cat works). Otherwise open up utility * input pipe. */ ofp = NULL; input[0] = input[1] = output[0] = output[1] = -1; if (ftype != FILTER_READ && pipe(input) < 0) { msgq(sp, M_SYSERR, "pipe"); goto err; } /* Open up utility output pipe. */ if (pipe(output) < 0) { msgq(sp, M_SYSERR, "pipe"); goto err; } if ((ofp = fdopen(output[0], "r")) == NULL) { msgq(sp, M_SYSERR, "fdopen"); goto err; } /* Fork off the utility process. */ switch (utility_pid = vfork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "vfork"); err: if (input[0] != -1) (void)close(input[0]); if (input[1] != -1) (void)close(input[1]); if (ofp != NULL) (void)fclose(ofp); else if (output[0] != -1) (void)close(output[0]); if (output[1] != -1) (void)close(output[1]); return (1); case 0: /* Utility. */ /* * Redirect stdin from the read end of the input pipe, and * redirect stdout/stderr to the write end of the output pipe. * * !!! * Historically, ex only directed stdout into the input pipe, * letting stderr come out on the terminal as usual. Vi did * not, directing both stdout and stderr into the input pipe. * We match that practice in both ex and vi for consistency. */ if (input[0] != -1) (void)dup2(input[0], STDIN_FILENO); (void)dup2(output[1], STDOUT_FILENO); (void)dup2(output[1], STDERR_FILENO); /* Close the utility's file descriptors. */ if (input[0] != -1) (void)close(input[0]); if (input[1] != -1) (void)close(input[1]); (void)close(output[0]); (void)close(output[1]); if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL) name = O_STR(sp, O_SHELL); else ++name; INT2CHAR(sp, cmd, STRLEN(cmd)+1, np, nlen); execl(O_STR(sp, O_SHELL), name, "-c", np, (char *)NULL); msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s"); _exit (127); /* NOTREACHED */ default: /* Parent-reader, parent-writer. */ /* Close the pipe ends neither parent will use. */ if (input[0] != -1) (void)close(input[0]); (void)close(output[1]); break; } /* * FILTER_RBANG, FILTER_READ: * * Reading is the simple case -- we don't need a parent writer, * so the parent reads the output from the read end of the output * pipe until it finishes, then waits for the child. Ex_readfp * appends to the MARK, and closes ofp. * * For FILTER_RBANG, there is nothing to write to the utility. * Make sure it doesn't wait forever by closing its standard * input. * * !!! * Set the return cursor to the last line read in for FILTER_READ. * Historically, this behaves differently from ":r file" command, * which leaves the cursor at the first line read in. Check to * make sure that it's not past EOF because we were reading into an * empty file. */ if (ftype == FILTER_RBANG || ftype == FILTER_READ) { if (ftype == FILTER_RBANG) (void)close(input[1]); if (ex_readfp(sp, "filter", ofp, fm, &nread, 1)) rval = 1; sp->rptlines[L_ADDED] += nread; - if (ftype == FILTER_READ) + if (ftype == FILTER_READ) { if (fm->lno == 0) rp->lno = nread; else rp->lno += nread; + } goto uwait; } /* * FILTER_BANG, FILTER_WRITE * * Here we need both a reader and a writer. Temporary files are * expensive and we'd like to avoid disk I/O. Using pipes has the * obvious starvation conditions. It's done as follows: * * fork * child * write lines out * exit * parent * FILTER_BANG: * read lines into the file * delete old lines * FILTER_WRITE * read and display lines * wait for child * * XXX * We get away without locking the underlying database because we know * that none of the records that we're reading will be modified until * after we've read them. This depends on the fact that the current * B+tree implementation doesn't balance pages or similar things when * it inserts new records. When the DB code has locking, we should * treat vi as if it were multiple applications sharing a database, and * do the required locking. If necessary a work-around would be to do * explicit locking in the line.c:db_get() code, based on the flag set * here. */ F_SET(sp->ep, F_MULTILOCK); switch (parent_writer_pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); (void)close(input[1]); (void)close(output[0]); rval = 1; break; case 0: /* Parent-writer. */ /* * Write the selected lines to the write end of the input * pipe. This instance of ifp is closed by ex_writefp. */ (void)close(output[0]); if ((ifp = fdopen(input[1], "w")) == NULL) _exit (1); _exit(ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1)); /* NOTREACHED */ default: /* Parent-reader. */ (void)close(input[1]); if (ftype == FILTER_WRITE) { /* * Read the output from the read end of the output * pipe and display it. Filter_ldisplay closes ofp. */ if (filter_ldisplay(sp, ofp)) rval = 1; } else { /* * Read the output from the read end of the output * pipe. Ex_readfp appends to the MARK and closes * ofp. */ if (ex_readfp(sp, "filter", ofp, tm, &nread, 1)) rval = 1; sp->rptlines[L_ADDED] += nread; } /* Wait for the parent-writer. */ if (proc_wait(sp, (long)parent_writer_pid, "parent-writer", 0, 1)) rval = 1; /* Delete any lines written to the utility. */ if (rval == 0 && ftype == FILTER_BANG && (cut(sp, NULL, fm, tm, CUT_LINEMODE) || del(sp, fm, tm, 1))) { rval = 1; break; } /* * If the filter had no output, we may have just deleted * the cursor. Don't do any real error correction, we'll * try and recover later. */ if (rp->lno > 1 && !db_exist(sp, rp->lno)) --rp->lno; break; } F_CLR(sp->ep, F_MULTILOCK); /* * !!! * Ignore errors on vi file reads, to make reads prettier. It's * completely inconsistent, and historic practice. */ uwait: INT2CHAR(sp, cmd, STRLEN(cmd) + 1, np, nlen); return (proc_wait(sp, (long)utility_pid, np, ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval); } /* * filter_ldisplay -- * Display output from a utility. * * !!! * Historically, the characters were passed unmodified to the terminal. * We use the ex print routines to make sure they're printable. */ static int filter_ldisplay(SCR *sp, FILE *fp) { size_t len; size_t wlen; CHAR_T *wp; EX_PRIVATE *exp; for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);) { FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen); if (ex_ldisplay(sp, wp, wlen, 0, 0)) break; } if (ferror(fp)) msgq(sp, M_SYSERR, "filter read"); (void)fclose(fp); return (0); } Index: vendor/nvi/dist/ex/ex_global.c =================================================================== --- vendor/nvi/dist/ex/ex_global.c (revision 366306) +++ vendor/nvi/dist/ex/ex_global.c (revision 366307) @@ -1,312 +1,313 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" enum which {GLOBAL, V}; static int ex_g_setup(SCR *, EXCMD *, enum which); /* * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands] * Exec on lines matching a pattern. * * PUBLIC: int ex_global(SCR *, EXCMD *); */ int ex_global(SCR *sp, EXCMD *cmdp) { return (ex_g_setup(sp, cmdp, FL_ISSET(cmdp->iflags, E_C_FORCE) ? V : GLOBAL)); } /* * ex_v -- [line [,line]] v /pattern/ [commands] * Exec on lines not matching a pattern. * * PUBLIC: int ex_v(SCR *, EXCMD *); */ int ex_v(SCR *sp, EXCMD *cmdp) { return (ex_g_setup(sp, cmdp, V)); } /* * ex_g_setup -- * Ex global and v commands. */ static int ex_g_setup(SCR *sp, EXCMD *cmdp, enum which cmd) { CHAR_T *ptrn, *p, *t; EXCMD *ecp; MARK abs; RANGE *rp; busy_t btype; recno_t start, end; regex_t *re; regmatch_t match[1]; size_t len; int cnt, delim, eval; CHAR_T *dbp; NEEDFILE(sp, cmdp); if (F_ISSET(sp, SC_EX_GLOBAL)) { msgq_wstr(sp, M_ERR, cmdp->cmd->name, "124|The %s command can't be used as part of a global or v command"); return (1); } /* * Skip leading white space. Historic vi allowed any non-alphanumeric * to serve as the global command delimiter. */ if (cmdp->argc == 0) goto usage; for (p = cmdp->argv[0]->bp; cmdskip(*p); ++p); if (*p == '\0' || is09azAZ(*p) || *p == '\\' || *p == '|' || *p == '\n') { usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } delim = *p++; /* * Get the pattern string, toss escaped characters. * * QUOTING NOTE: * Only toss an escaped character if it escapes a delimiter. */ for (ptrn = t = p;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; /* * !!! * Nul terminate the pattern string -- it's passed * to regcomp which doesn't understand anything else. */ *t = '\0'; break; } - if (p[0] == '\\') + if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') *t++ = *p++; + } *t++ = *p++; } /* If the pattern string is empty, use the last one. */ if (*ptrn == '\0') { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } /* Re-compile the RE if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); } else { /* Compile the RE. */ if (re_compile(sp, ptrn, t - ptrn, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) return (1); /* * Set saved RE. Historic practice is that globals set * direction as well as the RE. */ sp->searchdir = FORWARD; } re = &sp->re_c; /* The global commands always set the previous context mark. */ abs.lno = sp->lno; abs.cno = sp->cno; if (mark_set(sp, ABSMARK1, &abs, 1)) return (1); /* Get an EXCMD structure. */ CALLOC_RET(sp, ecp, 1, sizeof(EXCMD)); TAILQ_INIT(ecp->rq); /* * Get a copy of the command string; the default command is print. * Don't worry about a set of s with no command, that will * default to print in the ex parser. We need to have two copies * because the ex parser may step on the command string when it's * parsing it. */ if ((len = cmdp->argv[0]->len - (p - cmdp->argv[0]->bp)) == 0) { p = L("p"); len = 1; } MALLOC_RET(sp, ecp->cp, (len * 2) * sizeof(CHAR_T)); ecp->o_cp = ecp->cp; ecp->o_clen = len; MEMCPY(ecp->cp + len, p, len); ecp->range_lno = OOBLNO; FL_SET(ecp->agv_flags, cmd == GLOBAL ? AGV_GLOBAL : AGV_V); SLIST_INSERT_HEAD(sp->gp->ecq, ecp, q); /* * For each line... The semantics of global matching are that we first * have to decide which lines are going to get passed to the command, * and then pass them to the command, ignoring other changes. There's * really no way to do this in a single pass, since arbitrary line * creation, deletion and movement can be done in the ex command. For * example, a good vi clone test is ":g/X/mo.-3", or "g/X/.,.+1d". * What we do is create linked list of lines that are tracked through * each ex command. There's a callback routine which the DB interface * routines call when a line is created or deleted. This doesn't help * the layering much. */ btype = BUSY_ON; cnt = INTERRUPT_CHECK; for (start = cmdp->addr1.lno, end = cmdp->addr2.lno; start <= end; ++start) { if (cnt-- == 0) { if (INTERRUPTED(sp)) { SLIST_REMOVE_HEAD(sp->gp->ecq, q); free(ecp->cp); free(ecp); break; } search_busy(sp, btype); btype = BUSY_UPDATE; cnt = INTERRUPT_CHECK; } if (db_get(sp, start, DBG_FATAL, &dbp, &len)) return (1); match[0].rm_so = 0; match[0].rm_eo = len; switch (eval = regexec(&sp->re_c, dbp, 0, match, REG_STARTEND)) { case 0: if (cmd == V) continue; break; case REG_NOMATCH: if (cmd == GLOBAL) continue; break; default: re_error(sp, eval, &sp->re_c); break; } /* If follows the last entry, extend the last entry's range. */ if ((rp = TAILQ_LAST(ecp->rq, _rh)) != NULL && rp->stop == start - 1) { ++rp->stop; continue; } /* Allocate a new range, and append it to the list. */ CALLOC(sp, rp, 1, sizeof(RANGE)); if (rp == NULL) return (1); rp->start = rp->stop = start; TAILQ_INSERT_TAIL(ecp->rq, rp, q); } search_busy(sp, BUSY_OFF); return (0); } /* * ex_g_insdel -- * Update the ranges based on an insertion or deletion. * * PUBLIC: int ex_g_insdel(SCR *, lnop_t, recno_t); */ int ex_g_insdel(SCR *sp, lnop_t op, recno_t lno) { EXCMD *ecp; RANGE *nrp, *rp; /* All insert/append operations are done as inserts. */ if (op == LINE_APPEND) abort(); if (op == LINE_RESET) return (0); SLIST_FOREACH(ecp, sp->gp->ecq, q) { if (!FL_ISSET(ecp->agv_flags, AGV_AT | AGV_GLOBAL | AGV_V)) continue; TAILQ_FOREACH_SAFE(rp, ecp->rq, q, nrp) { /* If range less than the line, ignore it. */ if (rp->stop < lno) continue; /* * If range greater than the line, decrement or * increment the range. */ if (rp->start > lno) { if (op == LINE_DELETE) { --rp->start; --rp->stop; } else { ++rp->start; ++rp->stop; } continue; } /* * Lno is inside the range, decrement the end point * for deletion, and split the range for insertion. * In the latter case, since we're inserting a new * element, neither range can be exhausted. */ if (op == LINE_DELETE) { if (rp->start > --rp->stop) { TAILQ_REMOVE(ecp->rq, rp, q); free(rp); } } else { CALLOC_RET(sp, nrp, 1, sizeof(RANGE)); nrp->start = lno + 1; nrp->stop = rp->stop + 1; rp->stop = lno - 1; TAILQ_INSERT_AFTER(ecp->rq, rp, nrp, q); } } /* * If the command deleted/inserted lines, the cursor moves to * the line after the deleted/inserted line. */ ecp->range_lno = lno; } return (0); } Index: vendor/nvi/dist/ex/ex_script.c =================================================================== --- vendor/nvi/dist/ex/ex_script.c (revision 366306) +++ vendor/nvi/dist/ex/ex_script.c (revision 366307) @@ -1,622 +1,624 @@ /*- * 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. * * This code is derived from software contributed to Berkeley by * Brian Hirt. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBUTIL_H #include +#elif defined HAVE_PTY_H +#include #else #include #endif #include "../common/common.h" #include "../vi/vi.h" #include "script.h" #include "pathnames.h" static void sscr_check(SCR *); static int sscr_getprompt(SCR *); static int sscr_init(SCR *); static int sscr_insert(SCR *); static int sscr_matchprompt(SCR *, char *, size_t, size_t *); static int sscr_setprompt(SCR *, char *, size_t); /* * ex_script -- : sc[ript][!] [file] * Switch to script mode. * * PUBLIC: int ex_script(SCR *, EXCMD *); */ int ex_script(SCR *sp, EXCMD *cmdp) { /* Vi only command. */ if (!F_ISSET(sp, SC_VI)) { msgq(sp, M_ERR, "150|The script command is only available in vi mode"); return (1); } /* Switch to the new file. */ if (cmdp->argc != 0 && ex_edit(sp, cmdp)) return (1); /* Create the shell, figure out the prompt. */ if (sscr_init(sp)) return (1); return (0); } /* * sscr_init -- * Create a pty setup for a shell. */ static int sscr_init(SCR *sp) { SCRIPT *sc; char *sh, *sh_path; /* We're going to need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); MALLOC_RET(sp, sc, sizeof(SCRIPT)); sp->script = sc; sc->sh_prompt = NULL; sc->sh_prompt_len = 0; /* * There are two different processes running through this code. * They are the shell and the parent. */ sc->sh_master = sc->sh_slave = -1; if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } /* * Turn off output postprocessing and echo. */ sc->sh_term.c_oflag &= ~OPOST; sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK); if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } if (openpty(&sc->sh_master, &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) { msgq(sp, M_SYSERR, "openpty"); goto err; } /* * __TK__ huh? * Don't use vfork() here, because the signal semantics differ from * implementation to implementation. */ switch (sc->sh_pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); err: if (sc->sh_master != -1) (void)close(sc->sh_master); if (sc->sh_slave != -1) (void)close(sc->sh_slave); return (1); case 0: /* Utility. */ /* * XXX * So that shells that do command line editing turn it off. */ (void)setenv("TERM", "emacs", 1); (void)setenv("TERMCAP", "emacs:", 1); (void)setenv("EMACS", "t", 1); (void)setsid(); #ifdef TIOCSCTTY /* * 4.4BSD allocates a controlling terminal using the TIOCSCTTY * ioctl, not by opening a terminal device file. POSIX 1003.1 * doesn't define a portable way to do this. If TIOCSCTTY is * not available, hope that the open does it. */ (void)ioctl(sc->sh_slave, TIOCSCTTY, 0); #endif (void)close(sc->sh_master); (void)dup2(sc->sh_slave, STDIN_FILENO); (void)dup2(sc->sh_slave, STDOUT_FILENO); (void)dup2(sc->sh_slave, STDERR_FILENO); (void)close(sc->sh_slave); /* Assumes that all shells have -i. */ sh_path = O_STR(sp, O_SHELL); if ((sh = strrchr(sh_path, '/')) == NULL) sh = sh_path; else ++sh; execl(sh_path, sh, "-i", NULL); msgq_str(sp, M_SYSERR, sh_path, "execl: %s"); _exit(127); default: /* Parent. */ break; } if (sscr_getprompt(sp)) return (1); F_SET(sp, SC_SCRIPT); F_SET(sp->gp, G_SCRWIN); return (0); } /* * sscr_getprompt -- * Eat lines printed by the shell until a line with no trailing * carriage return comes; set the prompt from that line. */ static int sscr_getprompt(SCR *sp) { EX_PRIVATE *exp; struct timeval tv; char *endp, *p, *t, buf[1024]; SCRIPT *sc; fd_set fdset; recno_t lline; size_t llen, len; int nr; CHAR_T *wp; size_t wlen; exp = EXP(sp); FD_ZERO(&fdset); endp = buf; len = sizeof(buf); /* Wait up to a second for characters to read. */ tv.tv_sec = 5; tv.tv_usec = 0; sc = sp->script; FD_SET(sc->sh_master, &fdset); switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) { case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "select"); goto prompterr; case 0: /* Timeout */ msgq(sp, M_ERR, "Error: timed out"); goto prompterr; case 1: /* Characters to read. */ break; } /* Read the characters. */ more: len = sizeof(buf) - (endp - buf); switch (nr = read(sc->sh_master, endp, len)) { case 0: /* EOF. */ msgq(sp, M_ERR, "Error: shell: EOF"); goto prompterr; case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); goto prompterr; default: endp += nr; break; } /* If any complete lines, push them into the file. */ for (p = t = buf; p < endp; ++p) { if (*p == '\r' || *p == '\n') { if (CHAR2INT5(sp, exp->ibcw, t, p - t, wp, wlen)) goto conv_err; if (db_last(sp, &lline) || db_append(sp, 0, lline, wp, wlen)) goto prompterr; t = p + 1; } } if (p > buf) { memmove(buf, t, endp - t); endp = buf + (endp - t); } if (endp == buf) goto more; /* Wait up 1/10 of a second to make sure that we got it all. */ tv.tv_sec = 0; tv.tv_usec = 100000; switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) { case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "select"); goto prompterr; case 0: /* Timeout */ break; case 1: /* Characters to read. */ goto more; } /* Timed out, so theoretically we have a prompt. */ llen = endp - buf; endp = buf; /* Append the line into the file. */ if (CHAR2INT5(sp, exp->ibcw, buf, llen, wp, wlen)) goto conv_err; if (db_last(sp, &lline) || db_append(sp, 0, lline, wp, wlen)) { if (0) conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated."); prompterr: sscr_end(sp); return (1); } return (sscr_setprompt(sp, buf, llen)); } /* * sscr_exec -- * Take a line and hand it off to the shell. * * PUBLIC: int sscr_exec(SCR *, recno_t); */ int sscr_exec(SCR *sp, recno_t lno) { SCRIPT *sc; recno_t last_lno; size_t blen, len, last_len, tlen; int isempty, matchprompt, nw, rval; char *bp = NULL, *p; CHAR_T *wp; size_t wlen; /* If there's a prompt on the last line, append the command. */ if (db_last(sp, &last_lno)) return (1); if (db_get(sp, last_lno, DBG_FATAL, &wp, &wlen)) return (1); INT2CHAR(sp, wp, wlen, p, last_len); if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) { matchprompt = 1; GET_SPACE_RETC(sp, bp, blen, last_len + 128); memmove(bp, p, last_len); } else matchprompt = 0; /* Get something to execute. */ if (db_eget(sp, lno, &wp, &wlen, &isempty)) { if (isempty) goto empty; goto err1; } /* Empty lines aren't interesting. */ if (wlen == 0) goto empty; INT2CHAR(sp, wp, wlen, p, len); /* Delete any prompt. */ if (sscr_matchprompt(sp, p, len, &tlen)) { if (tlen == len) { empty: msgq(sp, M_BERR, "151|No command to execute"); goto err1; } p += (len - tlen); len = tlen; } /* Push the line to the shell. */ sc = sp->script; if ((nw = write(sc->sh_master, p, len)) != len) goto err2; rval = 0; if (write(sc->sh_master, "\n", 1) != 1) { err2: if (nw == 0) errno = EIO; msgq(sp, M_SYSERR, "shell"); goto err1; } if (matchprompt) { ADD_SPACE_RETC(sp, bp, blen, last_len + len); memmove(bp + last_len, p, len); CHAR2INT(sp, bp, last_len + len, wp, wlen); if (db_set(sp, last_lno, wp, wlen)) err1: rval = 1; } if (matchprompt) FREE_SPACE(sp, bp, blen); return (rval); } /* * sscr_input -- * Read any waiting shell input. * * PUBLIC: int sscr_input(SCR *); */ int sscr_input(SCR *sp) { GS *gp; struct timeval poll; fd_set rdfd; int maxfd; gp = sp->gp; loop: maxfd = 0; FD_ZERO(&rdfd); poll.tv_sec = 0; poll.tv_usec = 0; /* Set up the input mask. */ TAILQ_FOREACH(sp, gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) { FD_SET(sp->script->sh_master, &rdfd); if (sp->script->sh_master > maxfd) maxfd = sp->script->sh_master; } /* Check for input. */ switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) { case -1: msgq(sp, M_SYSERR, "select"); return (1); case 0: return (0); default: break; } /* Read the input. */ TAILQ_FOREACH(sp, gp->dq, q) if (F_ISSET(sp, SC_SCRIPT) && FD_ISSET(sp->script->sh_master, &rdfd) && sscr_insert(sp)) return (1); goto loop; } /* * sscr_insert -- * Take a line from the shell and insert it into the file. */ static int sscr_insert(SCR *sp) { EX_PRIVATE *exp; struct timeval tv; char *endp, *p, *t; SCRIPT *sc; fd_set rdfd; recno_t lno; size_t blen, len, tlen; int nr, rval; char *bp; CHAR_T *wp; size_t wlen = 0; exp = EXP(sp); /* Find out where the end of the file is. */ if (db_last(sp, &lno)) return (1); #define MINREAD 1024 GET_SPACE_RETC(sp, bp, blen, MINREAD); endp = bp; /* Read the characters. */ rval = 1; sc = sp->script; more: switch (nr = read(sc->sh_master, endp, MINREAD)) { case 0: /* EOF; shell just exited. */ sscr_end(sp); rval = 0; goto ret; case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); goto ret; default: endp += nr; break; } /* Append the lines into the file. */ for (p = t = bp; p < endp; ++p) { if (*p == '\r' || *p == '\n') { len = p - t; if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen)) goto conv_err; if (db_append(sp, 1, lno++, wp, wlen)) goto ret; t = p + 1; } } if (p > t) { len = p - t; /* * If the last thing from the shell isn't another prompt, wait * up to 1/10 of a second for more stuff to show up, so that * we don't break the output into two separate lines. Don't * want to hang indefinitely because some program is hanging, * confused the shell, or whatever. */ if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) { tv.tv_sec = 0; tv.tv_usec = 100000; FD_ZERO(&rdfd); FD_SET(sc->sh_master, &rdfd); if (select(sc->sh_master + 1, &rdfd, NULL, NULL, &tv) == 1) { memmove(bp, t, len); endp = bp + len; goto more; } } if (sscr_setprompt(sp, t, len)) return (1); if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen)) goto conv_err; if (db_append(sp, 1, lno++, wp, wlen)) goto ret; } /* The cursor moves to EOF. */ sp->lno = lno; sp->cno = wlen ? wlen - 1 : 0; rval = vs_refresh(sp, 1); if (0) conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated."); ret: FREE_SPACE(sp, bp, blen); return (rval); } /* * sscr_setprompt -- * * Set the prompt to the last line we got from the shell. * */ static int sscr_setprompt(SCR *sp, char *buf, size_t len) { SCRIPT *sc; sc = sp->script; free(sc->sh_prompt); MALLOC(sp, sc->sh_prompt, len + 1); if (sc->sh_prompt == NULL) { sscr_end(sp); return (1); } memmove(sc->sh_prompt, buf, len); sc->sh_prompt_len = len; sc->sh_prompt[len] = '\0'; return (0); } /* * sscr_matchprompt -- * Check to see if a line matches the prompt. Nul's indicate * parts that can change, in both content and size. */ static int sscr_matchprompt(SCR *sp, char *lp, size_t line_len, size_t *lenp) { SCRIPT *sc; size_t prompt_len; char *pp; sc = sp->script; if (line_len < (prompt_len = sc->sh_prompt_len)) return (0); for (pp = sc->sh_prompt; prompt_len && line_len; --prompt_len, --line_len) { if (*pp == '\0') { for (; prompt_len && *pp == '\0'; --prompt_len, ++pp); if (!prompt_len) return (0); for (; line_len && *lp != *pp; --line_len, ++lp); if (!line_len) return (0); } if (*pp++ != *lp++) break; } if (prompt_len) return (0); if (lenp != NULL) *lenp = line_len; return (1); } /* * sscr_end -- * End the pipe to a shell. * * PUBLIC: int sscr_end(SCR *); */ int sscr_end(SCR *sp) { SCRIPT *sc; if ((sc = sp->script) == NULL) return (0); /* Turn off the script flags. */ F_CLR(sp, SC_SCRIPT); sscr_check(sp); /* Close down the parent's file descriptors. */ if (sc->sh_master != -1) (void)close(sc->sh_master); if (sc->sh_slave != -1) (void)close(sc->sh_slave); /* This should have killed the child. */ (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0); /* Free memory. */ free(sc->sh_prompt); free(sc); sp->script = NULL; return (0); } /* * sscr_check -- * Set/clear the global scripting bit. */ static void sscr_check(SCR *sp) { GS *gp; gp = sp->gp; TAILQ_FOREACH(sp, gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) { F_SET(gp, G_SCRWIN); return; } F_CLR(gp, G_SCRWIN); } Index: vendor/nvi/dist/ex/ex_shell.c =================================================================== --- vendor/nvi/dist/ex/ex_shell.c (revision 366306) +++ vendor/nvi/dist/ex/ex_shell.c (revision 366307) @@ -1,223 +1,222 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" static const char *sigmsg(int); /* * ex_shell -- :sh[ell] * Invoke the program named in the SHELL environment variable * with the argument -i. * * PUBLIC: int ex_shell(SCR *, EXCMD *); */ int ex_shell(SCR *sp, EXCMD *cmdp) { int rval; char *buf; /* We'll need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); /* * XXX * Assumes all shells use -i. */ - (void)asprintf(&buf, "%s -i", O_STR(sp, O_SHELL)); - if (buf == NULL) { + if (asprintf(&buf, "%s -i", O_STR(sp, O_SHELL)) == -1) { msgq(sp, M_SYSERR, NULL); return (1); } /* Restore the window name. */ (void)sp->gp->scr_rename(sp, NULL, 0); /* If we're still in a vi screen, move out explicitly. */ rval = ex_exec_proc(sp, cmdp, buf, NULL, !F_ISSET(sp, SC_SCR_EXWROTE)); free(buf); /* Set the window name. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* * !!! * Historically, vi didn't require a continue message after the * return of the shell. Match it. */ F_SET(sp, SC_EX_WAIT_NO); return (rval); } /* * ex_exec_proc -- * Run a separate process. * * PUBLIC: int ex_exec_proc(SCR *, EXCMD *, char *, const char *, int); */ int ex_exec_proc(SCR *sp, EXCMD *cmdp, char *cmd, const char *msg, int need_newline) { GS *gp; const char *name; pid_t pid; gp = sp->gp; /* We'll need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); /* Enter ex mode. */ if (F_ISSET(sp, SC_VI)) { if (gp->scr_screen(sp, SC_EX)) { ex_wemsg(sp, cmdp->cmd->name, EXM_NOCANON); return (1); } (void)gp->scr_attr(sp, SA_ALTERNATE, 0); F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); } /* Put out additional newline, message. */ if (need_newline) (void)ex_puts(sp, "\n"); if (msg != NULL) { (void)ex_puts(sp, msg); (void)ex_puts(sp, "\n"); } (void)ex_fflush(sp); switch (pid = vfork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "vfork"); return (1); case 0: /* Utility. */ if (gp->scr_child) gp->scr_child(sp); if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL) name = O_STR(sp, O_SHELL); else ++name; execl(O_STR(sp, O_SHELL), name, "-c", cmd, (char *)NULL); msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s"); _exit(127); /* NOTREACHED */ default: /* Parent. */ return (proc_wait(sp, (long)pid, cmd, 0, 0)); } /* NOTREACHED */ } /* * proc_wait -- * Wait for one of the processes. * * !!! * The pid_t type varies in size from a short to a long depending on the * system. It has to be cast into something or the standard promotion * rules get you. I'm using a long based on the belief that nobody is * going to make it unsigned and it's unlikely to be a quad. * * PUBLIC: int proc_wait(SCR *, long, const char *, int, int); */ int proc_wait(SCR *sp, long int pid, const char *cmd, int silent, int okpipe) { size_t len; int nf, pstat; char *p; /* Wait for the utility, ignoring interruptions. */ for (;;) { errno = 0; if (waitpid((pid_t)pid, &pstat, 0) != -1) break; if (errno != EINTR) { msgq(sp, M_SYSERR, "waitpid"); return (1); } } /* * Display the utility's exit status. Ignore SIGPIPE from the * parent-writer, as that only means that the utility chose to * exit before reading all of its input. */ if (WIFSIGNALED(pstat) && (!okpipe || WTERMSIG(pstat) != SIGPIPE)) { for (; cmdskip(*cmd); ++cmd); p = msg_print(sp, cmd, &nf); len = strlen(p); msgq(sp, M_ERR, "%.*s%s: received signal: %s%s", (int)MIN(len, 20), p, len > 20 ? " ..." : "", sigmsg(WTERMSIG(pstat)), WCOREDUMP(pstat) ? "; core dumped" : ""); if (nf) FREE_SPACE(sp, p, 0); return (1); } if (WIFEXITED(pstat) && WEXITSTATUS(pstat)) { /* * Remain silent for "normal" errors when doing shell file * name expansions, they almost certainly indicate nothing * more than a failure to match. * * Remain silent for vi read filter errors. It's historic * practice. */ if (!silent) { for (; cmdskip(*cmd); ++cmd); p = msg_print(sp, cmd, &nf); len = strlen(p); msgq(sp, M_ERR, "%.*s%s: exited with status %d", (int)MIN(len, 20), p, len > 20 ? " ..." : "", WEXITSTATUS(pstat)); if (nf) FREE_SPACE(sp, p, 0); } return (1); } return (0); } /* * sigmsg -- * Return a pointer to a message describing a signal. */ static const char * sigmsg(int signo) { static char buf[40]; char *message; /* POSIX.1-2008 leaves strsignal(3)'s return value unspecified. */ if ((message = strsignal(signo)) != NULL) return message; (void)snprintf(buf, sizeof(buf), "Unknown signal: %d", signo); return (buf); } Index: vendor/nvi/dist/ex/ex_subst.c =================================================================== --- vendor/nvi/dist/ex/ex_subst.c (revision 366306) +++ vendor/nvi/dist/ex/ex_subst.c (revision 366307) @@ -1,1433 +1,1434 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" #define SUB_FIRST 0x01 /* The 'r' flag isn't reasonable. */ #define SUB_MUSTSETR 0x02 /* The 'r' flag is required. */ static int re_conv(SCR *, CHAR_T **, size_t *, int *); static int re_cscope_conv(SCR *, CHAR_T **, size_t *, int *); static int re_sub(SCR *, CHAR_T *, CHAR_T **, size_t *, size_t *, regmatch_t [10]); static int re_tag_conv(SCR *, CHAR_T **, size_t *, int *); static int s(SCR *, EXCMD *, CHAR_T *, regex_t *, u_int); /* * ex_s -- * [line [,line]] s[ubstitute] [[/;]pat[/;]/repl[/;] [cgr] [count] [#lp]] * * Substitute on lines matching a pattern. * * PUBLIC: int ex_s(SCR *, EXCMD *); */ int ex_s(SCR *sp, EXCMD *cmdp) { regex_t *re; size_t blen, len; u_int flags; int delim; CHAR_T *bp, *p, *ptrn, *rep, *t; /* * Skip leading white space. * * !!! * Historic vi allowed any non-alphanumeric to serve as the * substitution command delimiter. * * !!! * If the arguments are empty, it's the same as &, i.e. we * repeat the last substitution. */ if (cmdp->argc == 0) goto subagain; for (p = cmdp->argv[0]->bp, len = cmdp->argv[0]->len; len > 0; --len, ++p) { if (!cmdskip(*p)) break; } if (len == 0) subagain: return (ex_subagain(sp, cmdp)); delim = *p++; if (is09azAZ(delim) || delim == '\\') return (s(sp, cmdp, p, &sp->subre_c, SUB_MUSTSETR)); /* * !!! * The full-blown substitute command reset the remembered * state of the 'c' and 'g' suffices. */ sp->c_suffix = sp->g_suffix = 0; /* * Get the pattern string, toss escaping characters. * * !!! * Historic vi accepted any of the following forms: * * :s/abc/def/ change "abc" to "def" * :s/abc/def change "abc" to "def" * :s/abc/ delete "abc" * :s/abc delete "abc" * * QUOTING NOTE: * * Only toss an escaping character if it escapes a delimiter. * This means that "s/A/\\\\f" replaces "A" with "\\f". It * would be nice to be more regular, i.e. for each layer of * escaping a single escaping character is removed, but that's * not how the historic vi worked. */ for (ptrn = t = p;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; /* * !!! * Nul terminate the pattern string -- it's passed * to regcomp which doesn't understand anything else. */ *t = '\0'; break; } - if (p[0] == '\\') + if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') *t++ = *p++; + } *t++ = *p++; } /* * If the pattern string is empty, use the last RE (not just the * last substitution RE). */ if (*ptrn == '\0') { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } /* Re-compile the RE if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); flags = 0; } else { /* * !!! * Compile the RE. Historic practice is that substitutes set * the search direction as well as both substitute and search * RE's. We compile the RE twice, as we don't want to bother * ref counting the pattern string and (opaque) structure. */ if (re_compile(sp, ptrn, t - ptrn, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) return (1); if (re_compile(sp, ptrn, t - ptrn, &sp->subre, &sp->subre_len, &sp->subre_c, RE_C_SUBST)) return (1); flags = SUB_FIRST; sp->searchdir = FORWARD; } re = &sp->re_c; /* * Get the replacement string. * * The special character & (\& if O_MAGIC not set) matches the * entire RE. No handling of & is required here, it's done by * re_sub(). * * The special character ~ (\~ if O_MAGIC not set) inserts the * previous replacement string into this replacement string. * Count ~'s to figure out how much space we need. We could * special case nonexistent last patterns or whether or not * O_MAGIC is set, but it's probably not worth the effort. * * QUOTING NOTE: * * Only toss an escaping character if it escapes a delimiter or * if O_MAGIC is set and it escapes a tilde. * * !!! * If the entire replacement pattern is "%", then use the last * replacement pattern. This semantic was added to vi in System * V and then percolated elsewhere, presumably around the time * that it was added to their version of ed(1). */ if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; free(sp->repl); sp->repl = NULL; sp->repl_len = 0; } else if (p[0] == '%' && (p[1] == '\0' || p[1] == delim)) p += p[1] == delim ? 2 : 1; else { for (rep = p, len = 0; p[0] != '\0' && p[0] != delim; ++p, ++len) if (p[0] == '~') len += sp->repl_len; GET_SPACE_RETW(sp, bp, blen, len); for (t = bp, len = 0, p = rep;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; break; } if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') { *t++ = *p++; ++len; } else if (p[1] == '~') { ++p; if (!O_ISSET(sp, O_MAGIC)) goto tilde; } } else if (p[0] == '~' && O_ISSET(sp, O_MAGIC)) { tilde: ++p; MEMCPY(t, sp->repl, sp->repl_len); t += sp->repl_len; len += sp->repl_len; continue; } *t++ = *p++; ++len; } if ((sp->repl_len = len) != 0) { free(sp->repl); MALLOC(sp, sp->repl, len * sizeof(CHAR_T)); if (sp->repl == NULL) { FREE_SPACEW(sp, bp, blen); return (1); } MEMCPY(sp->repl, bp, len); } FREE_SPACEW(sp, bp, blen); } return (s(sp, cmdp, p, re, flags)); } /* * ex_subagain -- * [line [,line]] & [cgr] [count] [#lp]] * * Substitute using the last substitute RE and replacement pattern. * * PUBLIC: int ex_subagain(SCR *, EXCMD *); */ int ex_subagain(SCR *sp, EXCMD *cmdp) { if (sp->subre == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } if (!F_ISSET(sp, SC_RE_SUBST) && re_compile(sp, sp->subre, sp->subre_len, NULL, NULL, &sp->subre_c, RE_C_SUBST)) return (1); return (s(sp, cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->subre_c, 0)); } /* * ex_subtilde -- * [line [,line]] ~ [cgr] [count] [#lp]] * * Substitute using the last RE and last substitute replacement pattern. * * PUBLIC: int ex_subtilde(SCR *, EXCMD *); */ int ex_subtilde(SCR *sp, EXCMD *cmdp) { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); return (s(sp, cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->re_c, 0)); } /* * s -- * Do the substitution. This stuff is *really* tricky. There are lots of * special cases, and general nastiness. Don't mess with it unless you're * pretty confident. * * The nasty part of the substitution is what happens when the replacement * string contains newlines. It's a bit tricky -- consider the information * that has to be retained for "s/f\(o\)o/^M\1^M\1/". The solution here is * to build a set of newline offsets which we use to break the line up later, * when the replacement is done. Don't change it unless you're *damned* * confident. */ -#define NEEDNEWLINE(sp) { \ +#define NEEDNEWLINE(sp) do { \ if (sp->newl_len == sp->newl_cnt) { \ sp->newl_len += 25; \ REALLOC(sp, sp->newl, size_t *, \ sp->newl_len * sizeof(size_t)); \ if (sp->newl == NULL) { \ sp->newl_len = 0; \ return (1); \ } \ } \ -} +} while (0) -#define BUILD(sp, l, len) { \ +#define BUILD(sp, l, len) do { \ if (lbclen + (len) > lblen) { \ lblen = p2roundup(MAX(lbclen + (len), 256)); \ REALLOC(sp, lb, CHAR_T *, lblen * sizeof(CHAR_T)); \ if (lb == NULL) { \ lbclen = 0; \ return (1); \ } \ } \ MEMCPY(lb + lbclen, l, len); \ lbclen += len; \ -} +} while (0) -#define NEEDSP(sp, len, pnt) { \ +#define NEEDSP(sp, len, pnt) do { \ if (lbclen + (len) > lblen) { \ lblen = p2roundup(MAX(lbclen + (len), 256)); \ REALLOC(sp, lb, CHAR_T *, lblen * sizeof(CHAR_T)); \ if (lb == NULL) { \ lbclen = 0; \ return (1); \ } \ pnt = lb + lbclen; \ } \ -} +} while (0) static int s(SCR *sp, EXCMD *cmdp, CHAR_T *s, regex_t *re, u_int flags) { EVENT ev; MARK from, to; TEXTH tiq[] = {{ 0 }}; recno_t elno, lno, slno; u_long ul; regmatch_t match[10]; size_t blen, cnt, last, lbclen, lblen, len, llen; size_t offset, saved_offset, scno; int cflag, lflag, nflag, pflag, rflag; int didsub, do_eol_match, eflags, empty_ok, eval; int linechanged, matched, quit, rval; CHAR_T *bp, *lb; enum nresult nret; NEEDFILE(sp, cmdp); slno = sp->lno; scno = sp->cno; /* * !!! * Historically, the 'g' and 'c' suffices were always toggled as flags, * so ":s/A/B/" was the same as ":s/A/B/ccgg". If O_EDCOMPATIBLE was * not set, they were initialized to 0 for all substitute commands. If * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user * specified substitute/replacement patterns (see ex_s()). */ if (!O_ISSET(sp, O_EDCOMPATIBLE)) sp->c_suffix = sp->g_suffix = 0; /* * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but * it only displayed the last change. I'd disallow them, but they are * useful in combination with the [v]global commands. In the current * model the problem is combining them with the 'c' flag -- the screen * would have to flip back and forth between the confirm screen and the * ex print screen, which would be pretty awful. We do display all * changes, though, for what that's worth. * * !!! * Historic vi was fairly strict about the order of "options", the * count, and "flags". I'm somewhat fuzzy on the difference between * options and flags, anyway, so this is a simpler approach, and we * just take it them in whatever order the user gives them. (The ex * usage statement doesn't reflect this.) */ cflag = lflag = nflag = pflag = rflag = 0; if (s == NULL) goto noargs; for (lno = OOBLNO; *s != '\0'; ++s) switch (*s) { case ' ': case '\t': continue; case '+': ++cmdp->flagoff; break; case '-': --cmdp->flagoff; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (lno != OOBLNO) goto usage; errno = 0; nret = nget_uslong(&ul, s, &s, 10); lno = ul; if (*s == '\0') /* Loop increment correction. */ --s; if (nret != NUM_OK) { if (nret == NUM_OVER) msgq(sp, M_ERR, "153|Count overflow"); else if (nret == NUM_UNDER) msgq(sp, M_ERR, "154|Count underflow"); else msgq(sp, M_SYSERR, NULL); return (1); } /* * In historic vi, the count was inclusive from the * second address. */ cmdp->addr1.lno = cmdp->addr2.lno; cmdp->addr2.lno += lno - 1; if (!db_exist(sp, cmdp->addr2.lno) && db_last(sp, &cmdp->addr2.lno)) return (1); break; case '#': nflag = 1; break; case 'c': sp->c_suffix = !sp->c_suffix; /* Ex text structure initialization. */ if (F_ISSET(sp, SC_EX)) TAILQ_INIT(tiq); break; case 'g': sp->g_suffix = !sp->g_suffix; break; case 'l': lflag = 1; break; case 'p': pflag = 1; break; case 'r': if (LF_ISSET(SUB_FIRST)) { msgq(sp, M_ERR, "155|Regular expression specified; r flag meaningless"); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH)) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } rflag = 1; re = &sp->re_c; break; default: goto usage; } if (*s != '\0' || (!rflag && LF_ISSET(SUB_MUSTSETR))) { usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } noargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) { msgq(sp, M_ERR, "156|The #, l and p flags may not be combined with the c flag in vi mode"); return (1); } /* * bp: if interactive, line cache * blen: if interactive, line cache length * lb: build buffer pointer. * lbclen: current length of built buffer. * lblen; length of build buffer. */ bp = lb = NULL; blen = lbclen = lblen = 0; /* For each line... */ lno = cmdp->addr1.lno == 0 ? 1 : cmdp->addr1.lno; for (matched = quit = 0, elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) { /* Someone's unhappy, time to stop. */ if (INTERRUPTED(sp)) break; /* Get the line. */ if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; /* * Make a local copy if doing confirmation -- when calling * the confirm routine we're likely to lose the cached copy. */ if (sp->c_suffix) { if (bp == NULL) { GET_SPACE_RETW(sp, bp, blen, llen); } else ADD_SPACE_RETW(sp, bp, blen, llen); MEMCPY(bp, s, llen); s = bp; } /* Start searching from the beginning. */ offset = 0; len = llen; /* Reset the build buffer offset. */ lbclen = 0; /* Reset empty match flag. */ empty_ok = 1; /* * We don't want to have to do a setline if the line didn't * change -- keep track of whether or not this line changed. * If doing confirmations, don't want to keep setting the * line if change is refused -- keep track of substitutions. */ didsub = linechanged = 0; /* New line, do an EOL match. */ do_eol_match = 1; /* It's not nul terminated, but we pretend it is. */ eflags = REG_STARTEND; /* * The search area is from s + offset to the EOL. * * Generally, match[0].rm_so is the offset of the start * of the match from the start of the search, and offset * is the offset of the start of the last search. */ nextmatch: match[0].rm_so = 0; match[0].rm_eo = len; /* Get the next match. */ eval = regexec(re, s + offset, 10, match, eflags); /* * There wasn't a match or if there was an error, deal with * it. If there was a previous match in this line, resolve * the changes into the database. Otherwise, just move on. */ if (eval == REG_NOMATCH) goto endmatch; if (eval != 0) { re_error(sp, eval, re); goto err; } matched = 1; /* Only the first search can match an anchored expression. */ eflags |= REG_NOTBOL; /* * !!! * It's possible to match 0-length strings -- for example, the * command s;a*;X;, when matched against the string "aabb" will * result in "XbXbX", i.e. the matches are "aa", the space * between the b's and the space between the b's and the end of * the string. There is a similar space between the beginning * of the string and the a's. The rule that we use (because vi * historically used it) is that any 0-length match, occurring * immediately after a match, is ignored. Otherwise, the above * example would have resulted in "XXbXbX". Another example is * incorrectly using " *" to replace groups of spaces with one * space. * * The way we do this is that if we just had a successful match, * the starting offset does not skip characters, and the match * is empty, ignore the match and move forward. If there's no * more characters in the string, we were attempting to match * after the last character, so quit. */ if (!empty_ok && match[0].rm_so == 0 && match[0].rm_eo == 0) { empty_ok = 1; if (len == 0) goto endmatch; - BUILD(sp, s + offset, 1) + BUILD(sp, s + offset, 1); ++offset; --len; goto nextmatch; } /* Confirm change. */ if (sp->c_suffix) { /* * Set the cursor position for confirmation. Note, * if we matched on a '$', the cursor may be past * the end of line. */ from.lno = to.lno = lno; from.cno = match[0].rm_so + offset; to.cno = match[0].rm_eo + offset; /* * Both ex and vi have to correct for a change before * the first character in the line. */ if (llen == 0) from.cno = to.cno = 0; if (F_ISSET(sp, SC_VI)) { /* * Only vi has to correct for a change after * the last character in the line. * * XXX * It would be nice to change the vi code so * that we could display a cursor past EOL. */ if (to.cno >= llen) to.cno = llen - 1; if (from.cno >= llen) from.cno = llen - 1; sp->lno = from.lno; sp->cno = from.cno; if (vs_refresh(sp, 1)) goto err; vs_update(sp, msg_cat(sp, "169|Confirm change? [n]", NULL), NULL); if (v_event_get(sp, &ev, 0, 0)) goto err; switch (ev.e_event) { case E_CHARACTER: break; case E_EOF: case E_ERR: case E_INTERRUPT: goto lquit; default: v_event_err(sp, &ev); goto lquit; } } else { if (ex_print(sp, cmdp, &from, &to, 0) || ex_scprint(sp, &from, &to)) goto lquit; if (ex_txt(sp, tiq, 0, TXT_CR)) goto err; ev.e_c = TAILQ_FIRST(tiq)->lb[0]; } switch (ev.e_c) { case CH_YES: break; default: case CH_NO: didsub = 0; BUILD(sp, s +offset, match[0].rm_eo); goto skip; case CH_QUIT: /* Set the quit/interrupted flags. */ lquit: quit = 1; F_SET(sp->gp, G_INTERRUPTED); /* * Resolve any changes, then return to (and * exit from) the main loop. */ goto endmatch; } } /* * Set the cursor to the last position changed, converting * from 1-based to 0-based. */ sp->lno = lno; sp->cno = match[0].rm_so; /* Copy the bytes before the match into the build buffer. */ BUILD(sp, s + offset, match[0].rm_so); /* Substitute the matching bytes. */ didsub = 1; if (re_sub(sp, s + offset, &lb, &lbclen, &lblen, match)) goto err; /* Set the change flag so we know this line was modified. */ linechanged = 1; /* Move past the matched bytes. */ skip: offset += match[0].rm_eo; len -= match[0].rm_eo; /* A match cannot be followed by an empty pattern. */ empty_ok = 0; /* * If doing a global change with confirmation, we have to * update the screen. The basic idea is to store the line * so the screen update routines can find it, and restart. */ if (didsub && sp->c_suffix && sp->g_suffix) { /* * The new search offset will be the end of the * modified line. */ saved_offset = lbclen; /* Copy the rest of the line. */ if (len) - BUILD(sp, s + offset, len) + BUILD(sp, s + offset, len); /* Set the new offset. */ offset = saved_offset; /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; offset -= last; sp->newl_cnt = 0; } /* Store and retrieve the line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; - ADD_SPACE_RETW(sp, bp, blen, llen) + ADD_SPACE_RETW(sp, bp, blen, llen); MEMCPY(bp, s, llen); s = bp; len = llen - offset; /* Restart the build. */ lbclen = 0; BUILD(sp, s, offset); /* * If we haven't already done the after-the-string * match, do one. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (!do_eol_match) goto endmatch; if (offset == len) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } /* * If it's a global: * * If at the end of the string, do a test for the after * the string match. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (sp->g_suffix && do_eol_match) { if (len == 0) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } endmatch: if (!linechanged) continue; /* Copy any remaining bytes into the build buffer. */ if (len) - BUILD(sp, s + offset, len) + BUILD(sp, s + offset, len); /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; sp->newl_cnt = 0; } /* Store the changed line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; /* Update changed line counter. */ if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* * !!! * Display as necessary. Historic practice is to only * display the last line of a line split into multiple * lines. */ if (lflag || nflag || pflag) { from.lno = to.lno = lno; from.cno = to.cno = 0; if (lflag) (void)ex_print(sp, cmdp, &from, &to, E_C_LIST); if (nflag) (void)ex_print(sp, cmdp, &from, &to, E_C_HASH); if (pflag) (void)ex_print(sp, cmdp, &from, &to, E_C_PRINT); } } /* * !!! * Historically, vi attempted to leave the cursor at the same place if * the substitution was done at the current cursor position. Otherwise * it moved it to the first non-blank of the last line changed. There * were some problems: for example, :s/$/foo/ with the cursor on the * last character of the line left the cursor on the last character, or * the & command with multiple occurrences of the matching string in the * line usually left the cursor in a fairly random position. * * We try to do the same thing, with the exception that if the user is * doing substitution with confirmation, we move to the last line about * which the user was consulted, as opposed to the last line that they * actually changed. This prevents a screen flash if the user doesn't * change many of the possible lines. */ if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * If not in a global command, and nothing matched, say so. * Else, if none of the lines displayed, put something up. */ rval = 0; if (!matched) { if (!F_ISSET(sp, SC_EX_GLOBAL)) { msgq(sp, M_ERR, "157|No match found"); goto err; } } else if (!lflag && !nflag && !pflag) F_SET(cmdp, E_AUTOPRINT); if (0) { err: rval = 1; } if (bp != NULL) FREE_SPACEW(sp, bp, blen); free(lb); return (rval); } /* * re_compile -- * Compile the RE. * * PUBLIC: int re_compile(SCR *, * PUBLIC: CHAR_T *, size_t, CHAR_T **, size_t *, regex_t *, u_int); */ int re_compile(SCR *sp, CHAR_T *ptrn, size_t plen, CHAR_T **ptrnp, size_t *lenp, regex_t *rep, u_int flags) { size_t len; int reflags, replaced, rval; CHAR_T *p; /* Set RE flags. */ reflags = 0; if (!LF_ISSET(RE_C_CSCOPE | RE_C_TAG)) { if (O_ISSET(sp, O_EXTENDED)) reflags |= REG_EXTENDED; if (O_ISSET(sp, O_IGNORECASE)) reflags |= REG_ICASE; if (O_ISSET(sp, O_ICLOWER)) { for (p = ptrn, len = plen; len > 0; ++p, --len) if (ISUPPER(*p)) break; if (len == 0) reflags |= REG_ICASE; } } /* If we're replacing a saved value, clear the old one. */ if (LF_ISSET(RE_C_SEARCH) && F_ISSET(sp, SC_RE_SEARCH)) { regfree(&sp->re_c); F_CLR(sp, SC_RE_SEARCH); } if (LF_ISSET(RE_C_SUBST) && F_ISSET(sp, SC_RE_SUBST)) { regfree(&sp->subre_c); F_CLR(sp, SC_RE_SUBST); } /* * If we're saving the string, it's a pattern we haven't seen before, * so convert the vi-style RE's to POSIX 1003.2 RE's. Save a copy for * later recompilation. Free any previously saved value. */ if (ptrnp != NULL) { replaced = 0; if (LF_ISSET(RE_C_CSCOPE)) { if (re_cscope_conv(sp, &ptrn, &plen, &replaced)) return (1); /* * XXX * Currently, the match-any- expression used in * re_cscope_conv() requires extended RE's. This may * not be right or safe. */ reflags |= REG_EXTENDED; } else if (LF_ISSET(RE_C_TAG)) { if (re_tag_conv(sp, &ptrn, &plen, &replaced)) return (1); } else if (re_conv(sp, &ptrn, &plen, &replaced)) return (1); /* Discard previous pattern. */ free(*ptrnp); *ptrnp = NULL; if (lenp != NULL) *lenp = plen; /* * Copy the string into allocated memory. * * XXX * Regcomp isn't 8-bit clean, so the pattern is nul-terminated * for now. There's just no other solution. */ MALLOC(sp, *ptrnp, (plen + 1) * sizeof(CHAR_T)); if (*ptrnp != NULL) { MEMCPY(*ptrnp, ptrn, plen); (*ptrnp)[plen] = '\0'; } /* Free up conversion-routine-allocated memory. */ if (replaced) FREE_SPACEW(sp, ptrn, 0); if (*ptrnp == NULL) return (1); ptrn = *ptrnp; } /* * XXX * Regcomp isn't 8-bit clean, so we just lost if the pattern * contained a nul. Bummer! */ if ((rval = regcomp(rep, ptrn, /* plen, */ reflags)) != 0) { if (!LF_ISSET(RE_C_SILENT)) re_error(sp, rval, rep); return (1); } if (LF_ISSET(RE_C_SEARCH)) F_SET(sp, SC_RE_SEARCH); if (LF_ISSET(RE_C_SUBST)) F_SET(sp, SC_RE_SUBST); return (0); } /* * re_conv -- * Convert vi's regular expressions into something that the * the POSIX 1003.2 RE functions can handle. * * There are three conversions we make to make vi's RE's (specifically * the global, search, and substitute patterns) work with POSIX RE's. * * 1: If O_MAGIC is not set, strip backslashes from the magic character * set (.[*~) that have them, and add them to the ones that don't. * 2: If O_MAGIC is not set, the string "\~" is replaced with the text * from the last substitute command's replacement string. If O_MAGIC * is set, it's the string "~". * 3: The pattern \ does "word" searches, convert it to use the * new RE escapes. * * !!!/XXX * This doesn't exactly match the historic behavior of vi because we do * the ~ substitution before calling the RE engine, so magic characters * in the replacement string will be expanded by the RE engine, and they * weren't historically. It's a bug. */ static int re_conv(SCR *sp, CHAR_T **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len, needlen; int magic; CHAR_T *bp, *p, *t; /* * First pass through, we figure out how much space we'll need. * We do it in two passes, on the grounds that most of the time * the user is doing a search and won't have magic characters. * That way we can skip most of the memory allocation and copies. */ magic = 0; for (p = *ptrnp, len = *plenp, needlen = 0; len > 0; ++p, --len) switch (*p) { case '\\': if (len > 1) { --len; switch (*++p) { case '<': magic = 1; needlen += RE_WSTART_LEN + 1; break; case '>': magic = 1; needlen += RE_WSTOP_LEN + 1; break; case '~': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += sp->repl_len; } break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += 1; } break; default: needlen += 2; } } else needlen += 1; break; case '~': if (O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += sp->repl_len; } break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += 2; } break; default: needlen += 1; break; } if (!magic) { *replacedp = 0; return (0); } /* Get enough memory to hold the final pattern. */ *replacedp = 1; GET_SPACE_RETW(sp, bp, blen, needlen); for (p = *ptrnp, len = *plenp, t = bp; len > 0; ++p, --len) switch (*p) { case '\\': if (len > 1) { --len; switch (*++p) { case '<': MEMCPY(t, RE_WSTART, RE_WSTART_LEN); t += RE_WSTART_LEN; break; case '>': MEMCPY(t, RE_WSTOP, RE_WSTOP_LEN); t += RE_WSTOP_LEN; break; case '~': if (O_ISSET(sp, O_MAGIC)) *t++ = '~'; else { MEMCPY(t, sp->repl, sp->repl_len); t += sp->repl_len; } break; case '.': case '[': case '*': if (O_ISSET(sp, O_MAGIC)) *t++ = '\\'; *t++ = *p; break; default: *t++ = '\\'; *t++ = *p; } } else *t++ = '\\'; break; case '~': if (O_ISSET(sp, O_MAGIC)) { MEMCPY(t, sp->repl, sp->repl_len); t += sp->repl_len; } else *t++ = '~'; break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) *t++ = '\\'; *t++ = *p; break; default: *t++ = *p; break; } *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_tag_conv -- * Convert a tags search path into something that the POSIX * 1003.2 RE functions can handle. */ static int re_tag_conv(SCR *sp, CHAR_T **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len; int lastdollar; CHAR_T *bp, *p, *t; len = *plenp; /* Max memory usage is 2 times the length of the string. */ *replacedp = 1; GET_SPACE_RETW(sp, bp, blen, len * 2); p = *ptrnp; t = bp; /* If the last character is a '/' or '?', we just strip it. */ if (len > 0 && (p[len - 1] == '/' || p[len - 1] == '?')) --len; /* If the next-to-last or last character is a '$', it's magic. */ if (len > 0 && p[len - 1] == '$') { --len; lastdollar = 1; } else lastdollar = 0; /* If the first character is a '/' or '?', we just strip it. */ if (len > 0 && (p[0] == '/' || p[0] == '?')) { ++p; --len; } /* If the first or second character is a '^', it's magic. */ if (p[0] == '^') { *t++ = *p++; --len; } /* * Escape every other magic character we can find, meanwhile stripping * the backslashes ctags inserts when escaping the search delimiter * characters. */ for (; len > 0; --len) { if (p[0] == '\\' && (p[1] == '/' || p[1] == '?')) { ++p; --len; } else if (STRCHR(L("^.[]$*"), p[0])) *t++ = '\\'; *t++ = *p++; } if (lastdollar) *t++ = '$'; *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_cscope_conv -- * Convert a cscope search path into something that the POSIX * 1003.2 RE functions can handle. */ static int re_cscope_conv(SCR *sp, CHAR_T **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len, nspaces; CHAR_T *bp, *t; CHAR_T *p; CHAR_T *wp; size_t wlen; /* * Each space in the source line printed by cscope represents an * arbitrary sequence of spaces, tabs, and comments. */ #define CSCOPE_RE_SPACE "([ \t]|/\\*([^*]|\\*/)*\\*/)*" #define CSCOPE_LEN sizeof(CSCOPE_RE_SPACE) - 1 CHAR2INT(sp, CSCOPE_RE_SPACE, CSCOPE_LEN, wp, wlen); for (nspaces = 0, p = *ptrnp, len = *plenp; len > 0; ++p, --len) if (*p == ' ') ++nspaces; /* * Allocate plenty of space: * the string, plus potential escaping characters; * nspaces + 2 copies of CSCOPE_RE_SPACE; * ^, $, nul terminator characters. */ *replacedp = 1; len = (p - *ptrnp) * 2 + (nspaces + 2) * sizeof(CSCOPE_RE_SPACE) + 3; GET_SPACE_RETW(sp, bp, blen, len); p = *ptrnp; t = bp; *t++ = '^'; MEMCPY(t, wp, wlen); t += wlen; for (len = *plenp; len > 0; ++p, --len) if (*p == ' ') { MEMCPY(t, wp, wlen); t += wlen; } else { if (STRCHR(L("\\^.[]$*+?()|{}"), *p)) *t++ = '\\'; *t++ = *p; } MEMCPY(t, wp, wlen); t += wlen; *t++ = '$'; *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_error -- * Report a regular expression error. * * PUBLIC: void re_error(SCR *, int, regex_t *); */ void re_error(SCR *sp, int errcode, regex_t *preg) { size_t s; char *oe; s = regerror(errcode, preg, "", 0); MALLOC(sp, oe, s); if (oe != NULL) { (void)regerror(errcode, preg, oe, s); msgq(sp, M_ERR, "RE error: %s", oe); free(oe); } } /* * re_sub -- * Do the substitution for a regular expression. */ static int re_sub( SCR *sp, CHAR_T *ip, /* Input line. */ CHAR_T **lbp, size_t *lbclenp, size_t *lblenp, regmatch_t match[10]) { enum { C_NOTSET, C_LOWER, C_ONELOWER, C_ONEUPPER, C_UPPER } conv; size_t lbclen, lblen; /* Local copies. */ size_t mlen; /* Match length. */ size_t rpl; /* Remaining replacement length. */ CHAR_T *rp; /* Replacement pointer. */ int ch; int no; /* Match replacement offset. */ CHAR_T *p, *t; /* Buffer pointers. */ CHAR_T *lb; /* Local copies. */ lb = *lbp; /* Get local copies. */ lbclen = *lbclenp; lblen = *lblenp; /* * QUOTING NOTE: * * There are some special sequences that vi provides in the * replacement patterns. * & string the RE matched (\& if nomagic set) * \# n-th regular subexpression * \E end \U, \L conversion * \e end \U, \L conversion * \l convert the next character to lower-case * \L convert to lower-case, until \E, \e, or end of replacement * \u convert the next character to upper-case * \U convert to upper-case, until \E, \e, or end of replacement * * Otherwise, since this is the lowest level of replacement, discard * all escaping characters. This (hopefully) matches historic practice. */ -#define OUTCH(ch, nltrans) { \ +#define OUTCH(ch, nltrans) do { \ ARG_CHAR_T __ch = (ch); \ e_key_t __value = KEY_VAL(sp, __ch); \ if (nltrans && (__value == K_CR || __value == K_NL)) { \ NEEDNEWLINE(sp); \ sp->newl[sp->newl_cnt++] = lbclen; \ } else if (conv != C_NOTSET) { \ switch (conv) { \ case C_ONELOWER: \ conv = C_NOTSET; \ /* FALLTHROUGH */ \ case C_LOWER: \ if (ISUPPER(__ch)) \ __ch = TOLOWER(__ch); \ break; \ case C_ONEUPPER: \ conv = C_NOTSET; \ /* FALLTHROUGH */ \ case C_UPPER: \ if (ISLOWER(__ch)) \ __ch = TOUPPER(__ch); \ break; \ default: \ abort(); \ } \ } \ NEEDSP(sp, 1, p); \ *p++ = __ch; \ ++lbclen; \ -} +} while (0) conv = C_NOTSET; for (rp = sp->repl, rpl = sp->repl_len, p = lb + lbclen; rpl--;) { switch (ch = *rp++) { case '&': if (O_ISSET(sp, O_MAGIC)) { no = 0; goto subzero; } break; case '\\': if (rpl == 0) break; --rpl; switch (ch = *rp) { case '&': ++rp; if (!O_ISSET(sp, O_MAGIC)) { no = 0; goto subzero; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': no = *rp++ - '0'; subzero: if (match[no].rm_so == -1 || match[no].rm_eo == -1) break; mlen = match[no].rm_eo - match[no].rm_so; for (t = ip + match[no].rm_so; mlen--; ++t) OUTCH(*t, 0); continue; case 'e': case 'E': ++rp; conv = C_NOTSET; continue; case 'l': ++rp; conv = C_ONELOWER; continue; case 'L': ++rp; conv = C_LOWER; continue; case 'u': ++rp; conv = C_ONEUPPER; continue; case 'U': ++rp; conv = C_UPPER; continue; case '\r': OUTCH(ch, 0); continue; default: ++rp; break; } } OUTCH(ch, 1); } *lbp = lb; /* Update caller's information. */ *lbclenp = lbclen; *lblenp = lblen; return (0); } Index: vendor/nvi/dist/files/config.h.in =================================================================== --- vendor/nvi/dist/files/config.h.in (revision 366306) +++ vendor/nvi/dist/files/config.h.in (revision 366307) @@ -1,17 +1,26 @@ /* Define when using wide characters */ #cmakedefine USE_WIDECHAR /* Define when iconv can be used */ #cmakedefine USE_ICONV /* Define when the 2nd argument of iconv(3) is not const */ #cmakedefine ICONV_TRADITIONAL /* Define if you have */ #cmakedefine HAVE_LIBUTIL_H /* Define if you have */ #cmakedefine HAVE_NCURSES_H +/* Define if you have */ +#cmakedefine HAVE_NCURSESW_NCURSES_H + +/* Define if you have */ +#cmakedefine HAVE_PTY_H + /* Define if you have */ #cmakedefine HAVE_TERM_H + +/* Define if struct dirent has field d_namlen */ +#cmakedefine HAVE_DIRENT_D_NAMLEN Index: vendor/nvi/dist/files/pathnames.h.in =================================================================== --- vendor/nvi/dist/files/pathnames.h.in (revision 366306) +++ vendor/nvi/dist/files/pathnames.h.in (revision 366307) @@ -1,26 +1,25 @@ /* Read standard system paths first. */ #include #ifndef _PATH_EXRC #define _PATH_EXRC ".exrc" #endif #ifndef _PATH_MSGCAT #define _PATH_MSGCAT "@vi_cv_path_msgcat@" #endif #ifndef _PATH_NEXRC #define _PATH_NEXRC ".nexrc" #endif -#ifndef _PATH_PRESERVE -#define _PATH_PRESERVE "@vi_cv_path_preserve@" -#endif +/* On linux _PATH_PRESERVE is only writable by root */ +#define NVI_PATH_PRESERVE "@vi_cv_path_preserve@" #ifndef _PATH_SYSEXRC #define _PATH_SYSEXRC "/etc/vi.exrc" #endif #ifndef _PATH_TAGS #define _PATH_TAGS "tags" #endif Index: vendor/nvi/dist/man/vi.1 =================================================================== --- vendor/nvi/dist/man/vi.1 (revision 366306) +++ vendor/nvi/dist/man/vi.1 (revision 366307) @@ -1,2768 +1,2772 @@ .\" Copyright (c) 1994 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 1994, 1995, 1996 .\" Keith Bostic. All rights reserved. .\" Copyright (c) 2011 .\" Zhihao Yuan. All rights reserved. .\" .\" The vi program is freely redistributable. .\" You are welcome to copy, modify and share it with others .\" under the conditions listed in the LICENSE file. .\" If any company (not individual!) finds vi sufficiently useful .\" that you would have purchased it, or if any company wishes to .\" redistribute it, contributions to the authors would be appreciated. .\" .Dd November 2, 2013 .Dt VI 1 .Os .Sh NAME .Nm ex , vi , view .Nd text editors .Sh SYNOPSIS .Nm ex .Op Fl FRrSsv .Op Fl c Ar cmd .Op Fl t Ar tag .Op Fl w Ar size .Op Ar .Nm vi\ \& .Op Fl eFRrS .Op Fl c Ar cmd .Op Fl t Ar tag .Op Fl w Ar size .Op Ar .Nm view .Op Fl eFrS .Op Fl c Ar cmd .Op Fl t Ar tag .Op Fl w Ar size .Op Ar .Sh DESCRIPTION .Nm vi is a screen-oriented text editor. .Nm ex is a line-oriented text editor. .Nm ex and .Nm vi are different interfaces to the same program, and it is possible to switch back and forth during an edit session. .Nm view is the equivalent of using the .Fl R .Pq read-only option of .Nm vi . .Pp This manual page is the one provided with the .Nm nex Ns / Ns Nm nvi versions of the .Nm ex Ns / Ns Nm vi text editors. .Nm nex Ns / Ns Nm nvi are intended as bug-for-bug compatible replacements for the original Fourth Berkeley Software Distribution .Pq 4BSD .Nm ex and .Nm vi programs. For the rest of this manual page, .Nm nex Ns / Ns Nm nvi is used only when it's necessary to distinguish it from the historic implementations of .Nm ex Ns / Ns Nm vi . .Pp This manual page is intended for users already familiar with .Nm ex Ns / Ns Nm vi . Anyone else should almost certainly read a good tutorial on the editor before this manual page. If you're in an unfamiliar environment, and you absolutely have to get work done immediately, read the section after the options description, entitled .Sx FAST STARTUP . It's probably enough to get you going. .Pp The following options are available: .Bl -tag -width "-w size " .It Fl c Ar cmd Execute .Ar cmd on the first file loaded. Particularly useful for initial positioning in the file, although .Ar cmd is not limited to positioning commands. This is the POSIX 1003.2 interface for the historic .Dq +cmd syntax. .Nm nex Ns / Ns Nm nvi supports both the old and new syntax. .It Fl e Start editing in ex mode, as if the command name were .Nm ex . .It Fl F Don't copy the entire file when first starting to edit. (The default is to make a copy in case someone else modifies the file during your edit session.) .\" .It Fl l .\" Start editing with the lisp and showmatch options set. .It Fl R Start editing in read-only mode, as if the command name was .Nm view , or the .Cm readonly option was set. .It Fl r Recover the specified files, or, if no files are specified, list the files that could be recovered. If no recoverable files by the specified name exist, the file is edited as if the .Fl r option had not been specified. .It Fl S Run with the .Cm secure edit option set, disallowing all access to external programs. .It Fl s Enter batch mode; applicable only to .Nm ex edit sessions. Batch mode is useful when running .Nm ex scripts. Prompts, informative messages and other user oriented messages are turned off, and no startup files or environment variables are read. This is the POSIX 1003.2 interface for the historic .Dq - argument. .Nm nex Ns / Ns Nm nvi supports both the old and new syntax. .It Fl t Ar tag Start editing at the specified .Ar tag .Pq see Xr ctags 1 . .It Fl v Start editing in vi mode, as if the command name was .Nm vi . .It Fl w Ar size Set the initial window size to the specified number of lines. .El .Pp Command input for .Nm ex Ns / Ns Nm vi is read from the standard input. In the .Nm vi interface, it is an error if standard input is not a terminal. In the .Nm ex interface, if standard input is not a terminal, .Nm ex will read commands from it regardless; however, the session will be a batch mode session, exactly as if the .Fl s option had been specified. .Sh FAST STARTUP This section will tell you the minimum amount that you need to do simple editing tasks using .Nm vi . If you've never used any screen editor before, you're likely to have problems even with this simple introduction. In that case you should find someone that already knows .Nm vi and have them walk you through this section. .Pp .Nm vi is a screen editor. This means that it takes up almost the entire screen, displaying part of the file on each screen line, except for the last line of the screen. The last line of the screen is used for you to give commands to .Nm vi , and for .Nm vi to give information to you. .Pp The other fact that you need to understand is that .Nm vi is a modeful editor, i.e., you are either entering text or you are executing commands, and you have to be in the right mode to do one or the other. You will be in command mode when you first start editing a file. There are commands that switch you into input mode. There is only one key that takes you out of input mode, and that is the .Aq escape key. .Pp In this manual, key names are denoted with \(la and \(ra, e.g., .Aq escape means the .Dq escape key, usually labeled .Dq Esc on your terminal's keyboard. If you're ever confused as to which mode you're in, keep entering the .Aq escape key until .Nm vi beeps at you. Generally, .Nm vi will beep at you if you try and do something that's not allowed. It will also display error messages. .Pp To start editing a file, enter the following command: .Pp .Dl $ vi file .Pp The command you should enter as soon as you start editing is: .Pp .Dl :set verbose showmode .Pp This will make the editor give you verbose error messages and display the current mode at the bottom of the screen. .Pp The commands to move around the file are: .Bl -tag -width Ds .It Cm h Move the cursor left one character. .It Cm j Move the cursor down one line. .It Cm k Move the cursor up one line. .It Cm l Move the cursor right one character. .It Aq Cm cursor-arrows The cursor arrow keys should work, too. .It Cm / Ns Ar text Search for the string .Dq Ar text in the file, and move the cursor to its first character. .El .Pp The commands to enter new text are: .Bl -tag -width "" .It Cm a Append new text, after the cursor. .It Cm i Insert new text, before the cursor. .It Cm o Open a new line below the line the cursor is on, and start entering text. .It Cm O Open a new line above the line the cursor is on, and start entering text. .It Aq Cm escape Once you've entered input mode using one of the .Cm a , .Cm i , .Cm o or .Cm O commands, use .Aq Cm escape to quit entering text and return to command mode. .El .Pp The commands to copy text are: .Bl -tag -width Ds .It Cm yy Copy the line the cursor is on. .It Cm p Append the copied line after the line the cursor is on. .El .Pp The commands to delete text are: .Bl -tag -width Ds .It Cm dd Delete the line the cursor is on. .It Cm x Delete the character the cursor is on. .El .Pp The commands to write the file are: .Bl -tag -width Ds .It Cm :w Write the file back to the file with the name that you originally used as an argument on the .Nm vi command line. .It Cm :w Ar file_name Write the file back to the file with the name .Ar file_name . .El .Pp The commands to quit editing and exit the editor are: .Bl -tag -width Ds .It Cm :q Quit editing and leave .Nm vi (if you've modified the file, but not saved your changes, .Nm vi will refuse to quit). .It Cm :q! Quit, discarding any modifications that you may have made. .El .Pp One final caution: Unusual characters can take up more than one column on the screen, and long lines can take up more than a single screen line. The above commands work on .Dq physical characters and lines, i.e., they affect the entire line no matter how many screen lines it takes up and the entire character no matter how many screen columns it takes up. .Sh REGULAR EXPRESSIONS .Nm ex Ns / Ns Nm vi supports regular expressions .Pq REs , as documented in .Xr re_format 7 , for line addresses, as the first part of the .Nm ex Cm substitute , .Cm global and .Cm v commands, and in search patterns. Basic regular expressions .Pq BREs are enabled by default; extended regular expressions .Pq EREs are used if the .Cm extended option is enabled. The use of regular expressions can be largely disabled using the .Cm magic option. .Pp The following strings have special meanings in the .Nm ex Ns / Ns Nm vi version of regular expressions: .Bl -bullet -offset 6u .It An empty regular expression is equivalent to the last regular expression used. .It .Sq \e< matches the beginning of the word. .It .Sq \e> matches the end of the word. .It .Sq \(ti matches the replacement part of the last .Cm substitute command. .El .Sh BUFFERS A buffer is an area where commands can save changed or deleted text for later use. .Nm vi buffers are named with a single character preceded by a double quote, for example .Cm \&" Ns Aq Ar c ; .Nm ex buffers are the same, but without the double quote. .Nm nex Ns / Ns Nm nvi permits the use of any character without another meaning in the position where a buffer name is expected. .Pp All buffers are either in .Em line mode or .Em character mode . Inserting a buffer in line mode into the text creates new lines for each of the lines it contains, while a buffer in character mode creates new lines for any lines .Em other than the first and last lines it contains. The first and last lines are inserted at the current cursor position, becoming part of the current line. If there is more than one line in the buffer, the current line itself will be split. All .Nm ex commands which store text into buffers do so in line mode. The behaviour of .Nm vi commands depend on their associated motion command: .Bl -bullet -offset 6u .It .Aq Cm control-A , .Cm h , .Cm l , .Cm ,\& , .Cm 0 , .Cm B , .Cm E , .Cm F , .Cm T , .Cm W , .Cm \(ha , .Cm b , .Cm e , .Cm f and .Cm t make the destination buffer character-oriented. .It .Cm j , .Aq Cm control-M , .Cm k , .Cm \(aq , .Cm - , .Cm G , .Cm H , .Cm L , .Cm M , .Cm _ and .Cm |\& make the destination buffer line-oriented. .It .Cm $ , .Cm % , .Cm \` , .Cm (\& , .Cm )\& , .Cm / , .Cm ?\& , .Cm [[ , .Cm ]] , .Cm { and .Cm } make the destination buffer character-oriented, unless the starting and end positions are the first and last characters on a line. In that case, the buffer is line-oriented. .El .Pp The .Nm ex command .Cm display buffers displays the current mode for each buffer. .Pp Buffers named .Sq a through .Sq z may be referred to using their uppercase equivalent, in which case new content will be appended to the buffer, instead of replacing it. .Pp Buffers named .Sq 1 through .Sq 9 are special. A region of text modified using the .Cm c .Pq change or .Cm d .Pq delete commands is placed into the numeric buffer .Sq 1 if no other buffer is specified and if it meets one of the following conditions: .Bl -bullet -offset 6u .It It includes characters from more than one line. .It It is specified using a line-oriented motion. .It It is specified using one of the following motion commands: .Aq Cm control-A , .Cm \` Ns Aq Ar character , .Cm n , .Cm N , .Cm % , .Cm / , .Cm { , .Cm } , .Cm \&( , .Cm \&) , and .Cm \&? . .El .Pp Before this copy is done, the previous contents of buffer .Sq 1 are moved into buffer .Sq 2 , .Sq 2 into buffer .Sq 3 , and so on. The contents of buffer .Sq 9 are discarded. Note that this rotation occurs .Em regardless of the user specifying another buffer. In .Nm vi , text may be explicitly stored into the numeric buffers. In this case, the buffer rotation occurs before the replacement of the buffer's contents. The numeric buffers are only available in .Nm vi mode. .Sh VI COMMANDS The following section describes the commands available in the command mode of the .Nm vi editor. The following words have a special meaning in the commands description: .Pp .Bl -tag -width bigword -compact -offset 3u .It Ar bigword A set of non-whitespace characters. .It Ar buffer Temporary area where commands may place text. If not specified, the default buffer is used. See also .Sx BUFFERS , above. .It Ar count A positive number used to specify the desired number of iterations of a command. It defaults to 1 if not specified. .It Ar motion A cursor movement command which indicates the other end of the affected region of text, the first being the current cursor position. Repeating the command character makes it affect the whole current line. .It Ar word A sequence of letters, digits or underscores. .El .Pp .Ar buffer and .Ar count , if both present, may be specified in any order. .Ar motion and .Ar count , if both present, are effectively multiplied together and considered part of the motion. .Pp .Bl -tag -width Ds -compact .It Xo .Aq Cm control-A .Xc Search forward for the word starting at the cursor position. .Pp .It Xo .Op Ar count .Aq Cm control-B .Xc Page backwards .Ar count screens. Two lines of overlap are maintained, if possible. .Pp .It Xo .Op Ar count .Aq Cm control-D .Xc Scroll forward .Ar count lines. If .Ar count is not given, scroll forward the number of lines specified by the last .Aq Cm control-D or .Aq Cm control-U command. If this is the first .Aq Cm control-D command, scroll half the number of lines in the current screen. .Pp .It Xo .Op Ar count .Aq Cm control-E .Xc Scroll forward .Ar count lines, leaving the current line and column as is, if possible. .Pp .It Xo .Op Ar count .Aq Cm control-F .Xc Page forward .Ar count screens. Two lines of overlap are maintained, if possible. .Pp .It Aq Cm control-G Display the following file information: the file name .Pq as given to Nm vi ; whether the file has been modified since it was last written; if the file is read-only; the current line number; the total number of lines in the file; and the current line number as a percentage of the total lines in the file. .Pp .It Xo .Op Ar count .Aq Cm control-H .Xc .It Xo .Op Ar count .Cm h .Xc Move the cursor back .Ar count characters in the current line. .Pp .It Xo .Op Ar count .Aq Cm control-J .Xc .It Xo .Op Ar count .Aq Cm control-N .Xc .It Xo .Op Ar count .Cm j .Xc Move the cursor down .Ar count lines without changing the current column. .Pp .It Aq Cm control-L .It Aq Cm control-R Repaint the screen. .Pp .It Xo .Op Ar count .Aq Cm control-M .Xc .It Xo .Op Ar count .Cm + .Xc Move the cursor down .Ar count lines to the first non-blank character of that line. .Pp .It Xo .Op Ar count .Aq Cm control-P .Xc .It Xo .Op Ar count .Cm k .Xc Move the cursor up .Ar count lines, without changing the current column. .Pp .It Aq Cm control-T Return to the most recent tag context. .Pp .It Xo .Op Ar count .Aq Cm control-U .Xc Scroll backwards .Ar count lines. If .Ar count is not given, scroll backwards the number of lines specified by the last .Aq Cm control-D or .Aq Cm control-U command. If this is the first .Aq Cm control-U command, scroll half the number of lines in the current screen. .Pp .It Aq Cm control-W Switch to the next lower screen in the window, or to the first screen if there are no lower screens in the window. .Pp .It Xo .Op Ar count .Aq Cm control-Y .Xc Scroll backwards .Ar count lines, leaving the current line and column as is, if possible. .Pp .It Aq Cm control-Z Suspend the current editor session. .Pp .It Aq Cm escape Execute the .Nm ex command being entered, or cancel it if it is only partial. .Pp .It Aq Cm control-] Push a tag reference onto the tag stack. .Pp .It Aq Cm control-\(ha Switch to the most recently edited file. .Pp .It Xo .Op Ar count .Aq Cm space .Xc .It Xo .Op Ar count .Cm l .Xc Move the cursor forward .Ar count characters without changing the current line. .Pp .It Xo .Op Ar count .Cm !\& .Ar motion shell-argument(s) .Aq Li carriage-return .Xc Replace the lines spanned by .Ar count and .Ar motion with the output .Pq standard output and standard error of the program named by the .Cm shell option, called with a .Fl c flag followed by the .Ar shell-argument(s) .Pq bundled into a single argument . Within .Ar shell-argument(s) , the .Sq % , .Sq # and .Sq !\& characters are expanded to the current file name, the previous current file name, and the command text of the previous .Cm !\& or .Cm :! commands, respectively. The special meaning of .Sq % , .Sq # and .Sq !\& can be overridden by escaping them with a backslash. .Pp .It Xo .Op Ar count .Cm # .Sm off .Cm # | + | - .Sm on .Xc Increment .Pq trailing So # Sc or So + Sc or decrement .Pq trailing Sq - the number under the cursor by .Ar count , starting at the cursor position or at the first non-blank character following it. Numbers with a leading .Sq 0x or .Sq 0X are interpreted as hexadecimal numbers. Numbers with a leading .Sq 0 are interpreted as octal numbers unless they contain a non-octal digit. Other numbers may be prefixed with a .Sq + or .Sq - sign. .Pp .It Xo .Op Ar count .Cm $ .Xc Move the cursor to the end of a line. If .Ar count is specified, additionally move the cursor down .Ar count \(mi 1 lines. .Pp .It Cm % Move to the .Cm matchchars character matching the one found at the cursor position or the closest to the right of it. .Pp .It Cm & Repeat the previous substitution command on the current line. .Pp .It Xo .Cm \(aq Ns Aq Ar character .Xc .It Xo .Cm \` Ns Aq Ar character .Xc Return to the cursor position marked by the character .Ar character , or, if .Ar character is .Sq \(aq or .Sq \` , to the position of the cursor before the last of the following commands: .Aq Cm control-A , .Aq Cm control-T , .Aq Cm control-] , .Cm % , .Cm \(aq , .Cm \` , .Cm (\& , .Cm )\& , .Cm / , .Cm ?\& , .Cm G , .Cm H , .Cm L , .Cm [[ , .Cm ]] , .Cm { , .Cm } . The first form returns to the first non-blank character of the line marked by .Ar character . The second form returns to the line and column marked by .Ar character . .Pp .It Xo .Op Ar count .Cm \&( .Xc .It Xo .Op Ar count .Cm \&) .Xc Move .Ar count sentences backward or forward, respectively. A sentence is an area of text that begins with the first nonblank character following the previous sentence, paragraph, or section boundary and continues until the next period, exclamation point, or question mark character, followed by any number of closing parentheses, brackets, double or single quote characters, followed by either an end-of-line or two whitespace characters. Groups of empty lines .Pq or lines containing only whitespace characters are treated as a single sentence. .Pp .It Xo .Op Ar count .Cm ,\& .Xc Reverse find character .Pq i.e., the last Cm F , f , T No or Cm t No command .Ar count times. .Pp .It Xo .Op Ar count .Cm - .Xc Move to the first non-blank character of the previous line, .Ar count times. .Pp .It Xo .Op Ar count .Cm .\& .Xc Repeat the last .Nm vi command that modified text. .Ar count replaces both the .Ar count argument of the repeated command and that of the associated .Ar motion . If the .Cm .\& command repeats the .Cm u command, the change log is rolled forward or backward, depending on the action of the .Cm u command. .Pp .It Xo .Pf / Ns Ar RE .Aq Li carriage-return .Xc .It Xo .Pf / Ns Ar RE Ns / .Op Ar offset .Op Cm z .Aq Li carriage-return .Xc .It Xo .Pf ?\& Ns Ar RE .Aq Li carriage-return .Xc .It Xo .Pf ?\& Ns Ar RE Ns ?\& .Op Ar offset .Op Cm z .Aq Li carriage-return .Xc .It Cm N .It Cm n Search forward .Pq Sq / or backward .Pq Sq ?\& for a regular expression. .Cm n and .Cm N repeat the last search in the same or opposite directions, respectively. If .Ar RE is empty, the last search regular expression is used. If .Ar offset is specified, the cursor is placed .Ar offset lines before or after the matched regular expression. If either .Cm n or .Cm N commands are used as motion components for the .Cm !\& command, there will be no prompt for the text of the command and the previous .Cm !\& will be executed. Multiple search patterns may be grouped together by delimiting them with semicolons and zero or more whitespace characters. These patterns are evaluated from left to right with the final cursor position determined by the last search pattern. A .Cm z command may be appended to the closed search expressions to reposition the result line. .Pp .It Cm 0 Move to the first character in the current line. .Pp .It Cm :\& Execute an .Nm ex command. .Pp .It Xo .Op Ar count .Cm ;\& .Xc Repeat the last character find (i.e., the last .Cm F , f , T or .Cm t command) .Ar count times. .Pp .It Xo .Op Ar count .Cm < .Ar motion .Xc .It Xo .Op Ar count .Cm > .Ar motion .Xc Shift .Ar count lines left or right, respectively, by an amount of .Cm shiftwidth . .Pp .It Cm @ Ar buffer Execute a named .Ar buffer as .Nm vi commands. The buffer may include .Nm ex commands too, but they must be expressed as a .Cm \&: command. If .Ar buffer is .Sq @ or .Sq * , then the last buffer executed shall be used. .Pp .It Xo .Op Ar count .Cm A .Xc Enter input mode, appending the text after the end of the line. If a .Ar count argument is given, the characters input are repeated .Ar count \(mi 1 times after input mode is exited. .Pp .It Xo .Op Ar count .Cm B .Xc Move backwards .Ar count bigwords. .Pp .It Xo .Op Ar buffer .Cm C .Xc Change text from the current position to the end-of-line. If .Ar buffer is specified, .Dq yank the deleted text into .Ar buffer . .Pp .It Xo .Op Ar buffer .Cm D .Xc Delete text from the current position to the end-of-line. If .Ar buffer is specified, .Dq yank the deleted text into .Ar buffer . .Pp .It Xo .Op Ar count .Cm E .Xc Move forward .Ar count end-of-bigwords. .Pp .It Xo .Op Ar count .Cm F Aq Ar character .Xc Search .Ar count times backward through the current line for .Aq Ar character . .Pp .It Xo .Op Ar count .Cm G .Xc Move to line .Ar count , or the last line of the file if .Ar count is not specified. .Pp .It Xo .Op Ar count .Cm H .Xc Move to the screen line .Ar count \(mi 1 lines below the top of the screen. .Pp .It Xo .Op Ar count .Cm I .Xc Enter input mode, inserting the text at the beginning of the line. If a .Ar count argument is given, the characters input are repeated .Ar count \(mi 1 more times. .Pp .It Xo .Op Ar count .Cm J .Xc Join .Ar count lines with the current line. The spacing between two joined lines is set to two whitespace characters if the former ends with a question mark, a period or an exclamation point. It is set to one whitespace character otherwise. .Pp .It Xo .Op Ar count .Cm L .Xc Move to the screen line .Ar count \(mi 1 lines above the bottom of the screen. .Pp .It Cm M Move to the screen line in the middle of the screen. .Pp .It Xo .Op Ar count .Cm O .Xc Enter input mode, appending text in a new line above the current line. If a .Ar count argument is given, the characters input are repeated .Ar count \(mi 1 more times. .Pp .It Xo .Op Ar buffer .Cm P .Xc Insert text from .Ar buffer before the current column if .Ar buffer is character-oriented or before the current line if it is line-oriented. .Pp .It Cm Q Exit .Nm vi .Pq or visual mode and switch to .Nm ex mode. .Pp .It Xo .Op Ar count .Cm R .Xc Enter input mode, replacing the characters in the current line. If a .Ar count argument is given, the characters input are repeated .Ar count \(mi 1 more times upon exit from insert mode. .Pp .It Xo .Op Ar buffer .Op Ar count .Cm S .Xc Substitute .Ar count lines. If .Ar buffer is specified, .Dq yank the deleted text into .Ar buffer . .Pp .It Xo .Op Ar count .Cm T .Aq Ar character .Xc Search backwards, .Ar count times, through the current line for the character after the specified .Aq Ar character . .Pp .It Cm U Restore the current line to its state before the cursor last moved to it. .Pp .It Xo .Op Ar count .Cm W .Xc Move forward .Ar count bigwords. .Pp .It Xo .Op Ar buffer .Op Ar count .Cm X .Xc Delete .Ar count characters before the cursor, on the current line. If .Ar buffer is specified, .Dq yank the deleted text into .Ar buffer . .Pp .It Xo .Op Ar buffer .Op Ar count .Cm Y .Xc Copy .Pq or Dq yank .Ar count lines into .Ar buffer . .Pp .It Cm ZZ Write the file and exit .Nm vi if there are no more files to edit. Entering two .Dq quit commands in a row ignores any remaining file to edit. .Pp .It Xo .Op Ar count .Cm [[ .Xc Back up .Ar count section boundaries. .Pp .It Xo .Op Ar count .Cm ]] .Xc Move forward .Ar count section boundaries. .Pp .It Cm \(ha Move to the first non-blank character on the current line. .Pp .It Xo .Op Ar count .Cm _ .Xc Move down .Ar count \(mi 1 lines, to the first non-blank character. .Pp .It Xo .Op Ar count .Cm a .Xc Enter input mode, appending the text after the cursor. If a .Ar count argument is given, the characters input are repeated .Ar count number of times. .Pp .It Xo .Op Ar count .Cm b .Xc Move backwards .Ar count words. .Pp .It Xo .Op Ar buffer .Op Ar count .Cm c .Ar motion .Xc Change the region of text described by .Ar count and .Ar motion . If .Ar buffer is specified, .Dq yank the changed text into .Ar buffer . .Pp .It Xo .Op Ar buffer .Op Ar count .Cm d .Ar motion .Xc Delete the region of text described by .Ar count and .Ar motion . If .Ar buffer is specified, .Dq yank the deleted text into .Ar buffer . .Pp .It Xo .Op Ar count .Cm e .Xc Move forward .Ar count end-of-words. .Pp .It Xo .Op Ar count .Cm f Aq Ar character .Xc Search forward, .Ar count times, through the rest of the current line for .Aq Ar character . .Pp .It Xo .Op Ar count .Cm i .Xc Enter input mode, inserting the text before the cursor. If a .Ar count argument is given, the characters input are repeated .Ar count number of times. .Pp .It Xo .Cm m .Aq Ar character .Xc Save the current context .Pq line and column as .Aq Ar character . .Pp .It Xo .Op Ar count .Cm o .Xc Enter input mode, appending text in a new line under the current line. If a .Ar count argument is given, the characters input are repeated .Ar count \(mi 1 more times. .Pp .It Xo .Op Ar buffer .Cm p .Xc Append text from .Ar buffer . Text is appended after the current column if .Ar buffer is character oriented, or after the current line otherwise. .Pp .It Xo .Op Ar count .Cm r .Aq Ar character .Xc Replace .Ar count characters with .Ar character . .Pp .It Xo .Op Ar buffer .Op Ar count .Cm s .Xc Substitute .Ar count characters in the current line starting with the current character. If .Ar buffer is specified, .Dq yank the substituted text into .Ar buffer . .Pp .It Xo .Op Ar count .Cm t .Aq Ar character .Xc Search forward, .Ar count times, through the current line for the character immediately before .Aq Ar character . .Pp .It Cm u Undo the last change made to the file. If repeated, the .Cm u command alternates between these two states. The .Cm .\& command, when used immediately after .Cm u , causes the change log to be rolled forward or backward, depending on the action of the .Cm u command. .Pp .It Xo .Op Ar count .Cm w .Xc Move forward .Ar count words. .Pp .It Xo .Op Ar buffer .Op Ar count .Cm x .Xc Delete .Ar count characters at the current cursor position, but no more than there are till the end of the line. .Pp .It Xo .Op Ar buffer .Op Ar count .Cm y .Ar motion .Xc Copy .Pq or Dq yank a text region specified by .Ar count and .Ar motion into a buffer. .Pp .It Xo .Op Ar count1 .Cm z .Op Ar count2 .Cm type .Xc Redraw, optionally repositioning and resizing the screen. If .Ar count2 is specified, limit the screen size to .Ar count2 lines. The following .Cm type characters may be used: .Bl -tag -width Ds .It Cm + If .Ar count1 is specified, place the line .Ar count1 at the top of the screen. Otherwise, display the screen after the current screen. .It Aq Cm carriage-return Place the line .Ar count1 at the top of the screen. .It Cm .\& Place the line .Ar count1 in the center of the screen. .It Cm - Place the line .Ar count1 at the bottom of the screen. .It Cm \(ha If .Ar count1 is given, display the screen before the screen before .Ar count1 .Pq i.e., 2 screens before . Otherwise, display the screen before the current screen. .El .Pp .It Xo .Op Ar count .Cm {\& .Xc Move backward .Ar count paragraphs. .Pp .It Xo .Op Ar column .Cm |\& .Xc Move to a specific .Ar column position on the current line. If .Ar column is omitted, move to the start of the current line. .Pp .It Xo .Op Ar count .Cm }\& .Xc Move forward .Ar count paragraphs. .Pp .It Xo .Op Ar count .Cm \(ti .Ar motion .Xc If the .Cm tildeop option is not set, reverse the case of the next .Ar count character(s) and no .Ar motion can be specified. Otherwise .Ar motion is mandatory and .Cm \(ti reverses the case of the characters in a text region specified by the .Ar count and .Ar motion . .Pp .It Aq Cm interrupt Interrupt the current operation. The .Aq interrupt character is usually .Aq control-C . .El .Sh VI TEXT INPUT COMMANDS The following section describes the commands available in the text input mode of the .Nm vi editor. .Pp .Bl -tag -width Ds -compact .It Aq Cm nul Replay the previous input. .Pp .It Aq Cm control-D Erase to the previous .Ar shiftwidth column boundary. .Pp .It Cm \(ha Ns Aq Cm control-D Erase all of the autoindent characters, and reset the autoindent level. .Pp .It Cm 0 Ns Aq Cm control-D Erase all of the autoindent characters. .Pp .It Aq Cm control-T Insert sufficient .Aq tab and .Aq space characters to move forward to the next .Ar shiftwidth column boundary. If the .Cm expandtab option is set, only insert .Aq space characters. .Pp .It Aq Cm erase .It Aq Cm control-H Erase the last character. .Pp .It Aq Cm literal next Escape the next character from any special meaning. The .Aq literal\ \&next character is usually .Aq control-V . .Pp .It Aq Cm escape Resolve all text input into the file, and return to command mode. .Pp .It Aq Cm line erase Erase the current line. .Pp .It Aq Cm control-W .It Aq Cm word erase Erase the last word. The definition of word is dependent on the .Cm altwerase and .Cm ttywerase options. .Pp .Sm off .It Xo .Aq Cm control-X .Bq Cm 0-9A-Fa-f .Cm + .Xc .Sm on Insert a character with the specified hexadecimal value into the text. .Pp .It Aq Cm interrupt Interrupt text input mode, returning to command mode. The .Aq interrupt character is usually .Aq control-C . .El .Sh EX COMMANDS The following section describes the commands available in the .Nm ex editor. In each entry below, the tag line is a usage synopsis for the command. .Pp .Bl -tag -width Ds -compact .It Aq Cm end-of-file Scroll the screen. .Pp .It Cm !\& Ar argument(s) .It Xo .Op Ar range .Cm !\& .Ar argument(s) .Xc Execute a shell command, or filter lines through a shell command. .Pp .It Cm \&" A comment. .Pp .It Xo .Op Ar range .Cm nu Ns Op Cm mber .Op Ar count .Op Ar flags .Xc .It Xo .Op Ar range .Cm # .Op Ar count .Op Ar flags .Xc Display the selected lines, each preceded with its line number. .Pp .It Cm @ Ar buffer .It Cm * Ar buffer Execute a buffer. .Pp .It Xo .Op Ar range .Cm < Ns Op Cm < ... .Op Ar count .Op Ar flags .Xc Shift lines left. .Pp .It Xo .Op Ar line .Cm = .Op Ar flags .Xc Display the line number of .Ar line . If .Ar line is not specified, display the line number of the last line in the file. .Pp .It Xo .Op Ar range .Cm > Ns Op Cm > ... .Op Ar count .Op Ar flags .Xc Shift lines right. .Pp .It Xo .Cm ab Ns Op Cm breviate .Ar lhs rhs .Xc .Nm vi only. Add .Ar lhs as an abbreviation for .Ar rhs to the abbreviation list. .Pp .It Xo .Op Ar line .Cm a Ns Op Cm ppend Ns .Op Cm !\& .Xc The input text is appended after the specified line. .Pp .It Cm ar Ns Op Cm gs Display the argument list. .Pp .It Cm bg .Nm vi only. Background the current screen. .Pp .It Xo .Op Ar range .Cm c Ns Op Cm hange Ns .Op Cm !\& .Op Ar count .Xc The input text replaces the specified range. .Pp .It Xo .Cm chd Ns Op Cm ir Ns .Op Cm !\& .Op Ar directory .Xc .It Xo .Cm cd Ns Op Cm !\& .Op Ar directory .Xc Change the current working directory. .Pp .It Xo .Op Ar range .Cm co Ns Op Cm py .Ar line .Op Ar flags .Xc .It Xo .Op Ar range .Cm t .Ar line .Op Ar flags .Xc Copy the specified lines after the destination .Ar line . .Pp .It Xo .Cm cs Ns Op Cm cope .Cm add | find | help | kill | reset .Xc Execute a Cscope command. .Pp .It Xo .Op Ar range .Cm d Ns Op Cm elete .Op Ar buffer .Op Ar count .Op Ar flags .Xc Delete the lines from the file. .Pp .It Xo .Cm di Ns Op Cm splay .Cm b Ns Oo Cm uffers Oc | .Cm c Ns Oo Cm onnections Oc | .Cm s Ns Oo Cm creens Oc | .Cm t Ns Op Cm ags .Xc Display buffers, Cscope connections, screens or tags. .Pp .It Xo .Op Cm Ee Ns .Op Cm dit Ns .Op Cm !\& .Op Ar +cmd .Op Ar file .Xc .It Xo .Op Cm Ee Ns .Cm x Ns Op Cm !\& .Op Ar +cmd .Op Ar file .Xc Edit a different file. .Pp .It Xo .Cm exu Ns Op Cm sage .Op Ar command .Xc Display usage for an .Nm ex command. .Pp .It Xo .Cm f Ns Op Cm ile .Op Ar file .Xc Display and optionally change the file name. .Pp .It Xo .Op Cm Ff Ns .Cm g .Op Ar name .Xc .Nm vi mode only. Foreground the specified screen. .Pp .It Xo .Op Ar range .Cm g Ns Op Cm lobal .No / Ns Ar pattern Ns / .Op Ar commands .Xc .It Xo .Op Ar range .Cm v .No / Ns Ar pattern Ns / .Op Ar commands .Xc Apply commands to lines matching .Pq Sq global or not matching .Pq Sq v a pattern. .Pp .It Cm he Ns Op Cm lp Display a help message. .Pp .It Xo .Op Ar line .Cm i Ns Op Cm nsert Ns .Op Cm !\& .Xc The input text is inserted before the specified line. .Pp .It Xo .Op Ar range .Cm j Ns Op Cm oin Ns .Op Cm !\& .Op Ar count .Op Ar flags .Xc Join lines of text together. .Pp .It Xo .Op Ar range .Cm l Ns Op Cm ist .Op Ar count .Op Ar flags .Xc Display the lines unambiguously. .Pp .It Xo .Cm map Ns Op Cm !\& .Op Ar lhs rhs .Xc Define or display maps .Pq for Nm vi No only . .Pp .It Xo .Op Ar line .Cm ma Ns Op Cm rk .Aq Ar character .Xc .It Xo .Op Ar line .Cm k Aq Ar character .Xc Mark the line with the mark .Aq Ar character . .Pp .It Xo .Op Ar range .Cm m Ns Op Cm ove .Ar line .Xc Move the specified lines after the target line. .Pp .It Xo .Cm mk Ns Op Cm exrc Ns .Op Cm !\& .Ar file .Xc Write the abbreviations, editor options and maps to the specified .Ar file . .Pp .It Xo .Op Cm Nn Ns .Op Cm ext Ns .Op Cm !\& .Op Ar .Xc Edit the next file from the argument list. .\" .Pp .\" .It Xo .\" .Op Ar line .\" .Cm o Ns Op Cm pen .\" .No / Ns Ar pattern Ns / .\" .Op Ar flags .\" .Xc .\" Enter open mode. .Pp .It Cm pre Ns Op Cm serve Save the file in a form that can later be recovered using the .Nm ex .Fl r option. .Pp .It Xo .Op Cm \&Pp Ns .Cm rev Ns Op Cm ious Ns .Op Cm !\& .Xc Edit the previous file from the argument list. .Pp .It Xo .Op Ar range .Cm p Ns Op Cm rint .Op Ar count .Op Ar flags .Xc Display the specified lines. .Pp .It Xo .Op Ar line .Cm pu Ns Op Cm t .Op Ar buffer .Xc Append buffer contents to the current line. .Pp .It Xo .Cm q Ns Op Cm uit Ns .Op Cm !\& .Xc End the editing session. .Pp .It Xo .Op Ar line .Cm r Ns Op Cm ead Ns .Op Cm !\& .Op Ar file .Xc Read a file. .Pp .It Xo .Cm rec Ns Op Cm over .Ar file .Xc Recover .Ar file if it was previously saved. .Pp .It Xo .Cm res Ns Op Cm ize .Op Cm + Ns | Ns Cm - Ns .Ar size .Xc .Nm vi mode only. Grow or shrink the current screen. .Pp .It Xo .Cm rew Ns Op Cm ind Ns .Op Cm !\& .Xc Rewind the argument list. .Pp .It Xo .Cm se Ns Op Cm t .Sm off .Op option Oo = Oo value Oc Oc \ \&... .Sm on .Pf \ \& Op nooption ... .Op option? ... .Op Ar all .Xc Display or set editor options. .Pp .It Cm sh Ns Op Cm ell Run a shell program. .Pp .It Xo .Cm so Ns Op Cm urce .Ar file .Xc Read and execute .Nm ex commands from a file. .Pp .It Xo .Op Ar range .Cm s Ns Op Cm ubstitute .Sm off .Op / Ar pattern No / Ar replace No / .Sm on .Pf \ \& Op Ar options .Op Ar count .Op Ar flags .Xc .It Xo .Op Ar range .Cm & .Op Ar options .Op Ar count .Op Ar flags .Xc .It Xo .Op Ar range .Cm \(ti .Op Ar options .Op Ar count .Op Ar flags .Xc Make substitutions. The .Ar replace field may contain any of the following sequences: .Bl -tag -width Ds .It Sq \*(Am The text matched by .Ar pattern . .It Sq \(ti The replacement part of the previous .Cm substitute command. .It Sq % If this is the entire .Ar replace pattern, the replacement part of the previous .Cm substitute command. .It Sq \e Ns Ar \(sh Where .Sq Ar \(sh is an integer from 1 to 9, the text matched by the .Ar # Ns 'th subexpression in .Ar pattern . .It Sq \eL Causes the characters up to the end of the line of the next occurrence of .Sq \eE or .Sq \ee to be converted to lowercase. .It Sq \el Causes the next character to be converted to lowercase. .It Sq \eU Causes the characters up to the end of the line of the next occurrence of .Sq \eE or .Sq \ee to be converted to uppercase. .It Sq \eu Causes the next character to be converted to uppercase. .El .Pp .It Xo .Cm su Ns Op Cm spend Ns .Op Cm !\& .Xc .It Xo .Cm st Ns Op Cm op Ns .Op Cm !\& .Xc .It Aq Cm suspend Suspend the edit session. The .Aq suspend character is usually .Aq control-Z . .Pp .It Xo .Op Cm Tt Ns .Cm a Ns Op Cm g Ns .Op Cm !\& .Ar tagstring .Xc Edit the file containing the specified tag. .Pp .It Xo .Cm tagn Ns Op Cm ext Ns .Op Cm !\& .Xc Edit the file containing the next context for the current tag. .Pp .It Xo .Cm tagp Ns Op Cm op Ns .Op Cm !\& .Op Ar file | number .Xc Pop to the specified tag in the tags stack. .Pp .It Xo .Cm tagpr Ns Op Cm ev Ns .Op Cm !\& .Xc Edit the file containing the previous context for the current tag. .Pp .It Xo .Cm tagt Ns Op Cm op Ns .Op Cm !\& .Xc Pop to the least recent tag on the tags stack, clearing the stack. .Pp .It Xo .Cm una Ns Op Cm bbreviate .Ar lhs .Xc .Nm vi only. Delete an abbreviation. .Pp .It Cm u Ns Op Cm ndo Undo the last change made to the file. .Pp .It Xo .Cm unm Ns Op Cm ap Ns .Op Cm !\& .Ar lhs .Xc Unmap a mapped string. .Pp .It Cm ve Ns Op Cm rsion Display the version of the .Nm ex Ns / Ns Nm vi editor. .Pp .It Xo .Op Ar line .Cm vi Ns Op Cm sual .Op Ar type .Op Ar count .Op Ar flags .Xc .Nm ex mode only. Enter .Nm vi . .Pp .It Xo .Op Cm Vi Ns .Cm i Ns Op Cm sual Ns .Op Cm !\& .Op Ar +cmd .Op Ar file .Xc .Nm vi mode only. Edit a new file. .Pp .It Xo .Cm viu Ns Op Cm sage .Op Ar command .Xc Display usage for a .Nm vi command. .Pp .It Xo .Op Ar range .Cm w Ns Op Cm rite Ns .Op Cm !\& .Op >> .Op Ar file .Xc .It Xo .Op Ar range .Cm w Ns Op Cm rite .Op Cm !\& .Op Ar file .Xc .It Xo .Op Ar range .Cm wn Ns Op Cm !\& .Op >> .Op Ar file .Xc .It Xo .Op Ar range .Cm wq Ns Op Cm !\& .Op >> .Op Ar file .Xc Write the file. .Pp .It Xo .Op Ar range .Cm x Ns Op Cm it Ns .Op Cm !\& .Op Ar file .Xc Exit the editor, writing the file if it has been modified. .Pp .It Xo .Op Ar range .Cm ya Ns Op Cm nk .Op Ar buffer .Op Ar count .Xc Copy the specified lines to a buffer. .Pp .It Xo .Op Ar line .Cm z .Op Ar type .Op Ar count .Op Ar flags .Xc Adjust the window. .El .Sh SET OPTIONS There are a large number of options that may be set .Pq or unset to change the editor's behavior. This section describes the options, their abbreviations and their default values. .Pp In each entry below, the first part of the tag line is the full name of the option, followed by any equivalent abbreviations. The part in square brackets is the default value of the option. Most of the options are boolean, i.e., they are either on or off, and do not have an associated value. .Pp Options apply to both .Nm ex and .Nm vi modes, unless otherwise specified. .Bl -tag -width Ds .It Cm altwerase Bq off .Nm vi only. Select an alternate word erase algorithm. .It Cm autoindent , ai Bq off Automatically indent new lines. .It Cm autoprint , ap Bq on .Nm ex only. Display the current line automatically. .It Cm autowrite , aw Bq off Write modified files automatically when changing files or suspending the editor session. .It Cm backup Bq \&"\&" Back up files before they are overwritten. .It Cm beautify , bf Bq off Discard control characters. .It Cm cdpath Bo environment variable Ev CDPATH , or current directory Bc The directory paths used as path prefixes for the .Cm cd command. .It Cm cedit Bq no default Set the character to edit the colon command-line history. .It Cm columns , co Bq 80 Set the number of columns in the screen. .It Cm comment Bq off .Nm vi only. Skip leading comments in shell, C and C++ language files. .It Cm directory , dir Bo environment variable Ev TMPDIR , or Pa /tmp Bc The directory where temporary files are created. .It Cm edcompatible , ed Bq off Remember the values of the .Sq c and .Sq g suffixes to the .Cm substitute commands, instead of initializing them as unset for each new command. .It Cm errorbells , eb Bq off .Nm ex only. Announce error messages with a bell. .It Cm escapetime Bq 1 The tenths of a second .Nm ex Ns / Ns Nm vi waits for a subsequent key to complete an .Aq escape key mapping. .It Cm expandtab , et Bq off Expand .Aq tab characters to .Aq space when inserting, replacing or shifting text, autoindenting, indenting with .Aq Ic control-T , outdenting with .Aq Ic control-D , or when filtering lines with the .Cm !\& command. .It Cm exrc , ex Bq off Read the startup files in the local directory. .It Cm extended Bq off Use extended regular expressions .Pq EREs rather than basic regular expressions .Pq BREs . See .Xr re_format 7 for more information on regular expressions. .It Cm filec Bq Aq tab Set the character to perform file path completion on the colon command line. .It Cm fileencoding , fe Bq auto detect Set the encoding of the current file. .It Cm flash Bq on Flash the screen instead of beeping the keyboard on error. .It Cm hardtabs, ht Bq 0 Set the spacing between hardware tab settings. This option currently has no effect. .It Cm iclower Bq off Makes all regular expressions case-insensitive, as long as an upper-case letter does not appear in the search string. .It Cm ignorecase , ic Bq off Ignore case differences in regular expressions. .It Cm inputencoding , ie Bq locale Set the encoding of your input characters. .It Cm keytime Bq 6 The tenths of a second .Nm ex Ns / Ns Nm vi waits for a subsequent key to complete a key mapping. .It Cm leftright Bq off .Nm vi only. Do left-right scrolling. .It Cm lines , li Bq 24 .Nm vi only. Set the number of lines in the screen. .It Cm lisp Bq off .Nm vi only. Modify various search commands and options to work with Lisp. This option is not yet implemented. .It Cm list Bq off Display lines in an unambiguous fashion. .It Cm lock Bq on Attempt to get an exclusive lock on any file being edited, read or written. .It Cm magic Bq on When turned off, all regular expression characters except for .Sq \(ha and .Sq \(Do are treated as ordinary characters. Preceding individual characters by .Sq \e re-enables them. .It Cm matchchars Bq []{}() Character pairs looked for by the .Cm % command. .It Cm matchtime Bq 7 .Nm vi only. The tenths of a second .Nm ex Ns / Ns Nm vi pauses on the matching character when the .Cm showmatch option is set. .It Cm mesg Bq on Permit messages from other users. .It Cm msgcat Bq /usr/share/vi/catalog/ Selects a message catalog to be used to display error and informational messages in a specified language. .It Cm modelines , modeline Bq off Read the first and last few lines of each file for .Nm ex commands. This option will never be implemented. .It Cm noprint Bq \&"\&" Characters that are never handled as printable characters. .It Cm number , nu Bq off Precede each line displayed with its current line number. .It Cm octal Bq off Display unknown characters as octal numbers, instead of the default hexadecimal. .It Cm open Bq on .Nm ex only. If this option is not set, the .Cm open and .Cm visual commands are disallowed. .It Cm optimize , opt Bq on .Nm vi only. Optimize text throughput to dumb terminals. This option is not yet implemented. .It Cm paragraphs , para Bq "IPLPPPQPP LIpplpipbp" .Nm vi only. Define additional paragraph boundaries for the .Cm {\& and .Cm }\& commands. .It Cm path Bq \&"\&" Define additional directories to search for files being edited. .It Cm print Bq \&"\&" Characters that are always handled as printable characters. .It Cm prompt Bq on .Nm ex only. Display a command prompt. .It Cm readonly , ro Bq off Mark the file and session as read-only. .It Cm recdir Bq /var/tmp/vi.recover The directory where recovery files are stored. .It Cm redraw , re Bq off .Nm vi only. Simulate an intelligent terminal on a dumb one. This option is not yet implemented. .It Cm remap Bq on Remap keys until resolved. .It Cm report Bq 5 Set the number of lines about which the editor reports changes or yanks. .It Cm ruler Bq off .Nm vi only. Display a row/column ruler on the colon command line. .It Cm scroll , scr Bq "window size / 2" Set the number of lines scrolled. .It Cm searchincr Bq off Makes the .Cm / and .Cm ?\& commands incremental. .It Cm sections , sect Bq "NHSHH HUnhsh" .Nm vi only. Define additional section boundaries for the .Cm [[ and .Cm ]] commands. .It Cm secure Bq off Turns off all access to external programs. .It Cm shell , sh Bo environment variable Ev SHELL , or Pa /bin/sh Bc Select the shell used by the editor. .It Cm shellmeta Bq \(ti{[*?$\`\(aq\&"\e Set the meta characters checked to determine if file name expansion is necessary. .It Cm shiftwidth , sw Bq 8 Set the autoindent and shift command indentation width. .It Cm showmatch , sm Bq off .Nm vi only. Note the left matching characters when the right ones are inserted. .It Cm showmode , smd Bq off .Nm vi only. Display the current editor mode and a .Dq modified flag. .It Cm sidescroll Bq 16 .Nm vi only. Set the amount a left-right scroll will shift. .It Cm slowopen , slow Bq off Delay display updating during text input. This option is not yet implemented. .It Cm sourceany Bq off Read startup files not owned by the current user. This option will never be implemented. .It Cm tabstop , ts Bq 8 This option sets tab widths for the editor display. .It Cm taglength , tl Bq 0 Set the number of significant characters in tag names. .It Cm tags , tag Bq tags Set the list of tags files. .It Xo .Cm term , ttytype , tty .Bq environment variable Ev TERM .Xc Set the terminal type. .It Cm terse Bq off This option has historically made editor messages less verbose. It has no effect in this implementation. .It Cm tildeop Bq off Modify the .Cm \(ti command to take an associated motion. .It Cm timeout , to Bq on Time out on keys which may be mapped. .It Cm ttywerase Bq off .Nm vi only. Select an alternate erase algorithm. .It Cm verbose Bq off .Nm vi only. Display an error message for every error. .It Cm w300 Bq no default .Nm vi only. Set the window size if the baud rate is less than 1200 baud. .It Cm w1200 Bq no default .Nm vi only. Set the window size if the baud rate is equal to 1200 baud. .It Cm w9600 Bq no default .Nm vi only. Set the window size if the baud rate is greater than 1200 baud. .It Cm warn Bq on .Nm ex only. This option causes a warning message to be printed on the terminal if the file has been modified since it was last written, before a .Cm !\& command. .It Xo .Cm window , w , wi .Bq environment variable Ev LINES No \(mi 1 .Xc Set the window size for the screen. .It Cm windowname Bq off Change the icon/window name to the current file name. .It Cm wraplen , wl Bq 0 .Nm vi only. Break lines automatically, the specified number of columns from the left-hand margin. If both the .Cm wraplen and .Cm wrapmargin edit options are set, the .Cm wrapmargin value is used. .It Cm wrapmargin , wm Bq 0 .Nm vi only. Break lines automatically, the specified number of columns from the right-hand margin. If both the .Cm wraplen and .Cm wrapmargin edit options are set, the .Cm wrapmargin value is used. .It Cm wrapscan , ws Bq on Set searches to wrap around the end or beginning of the file. .It Cm writeany , wa Bq off Turn off file-overwriting checks. .El .Sh ENVIRONMENT .Bl -tag -width "COLUMNS" .It Ev COLUMNS The number of columns on the screen. This value overrides any system or terminal specific values. If the .Ev COLUMNS environment variable is not set when .Nm ex Ns / Ns Nm vi runs, or the .Cm columns option is explicitly reset by the user, .Nm ex Ns / Ns Nm vi enters the value into the environment. .It Ev EXINIT A list of .Nm ex startup commands, read after .Pa /etc/vi.exrc unless the variable .Ev NEXINIT is also set. .It Ev HOME The user's home directory, used as the initial directory path for the startup .Pa $HOME/.nexrc and .Pa $HOME/.exrc files. This value is also used as the default directory for the .Cm cd command. .It Ev LINES The number of rows on the screen. This value overrides any system or terminal specific values. If the .Ev LINES environment variable is not set when .Nm ex Ns / Ns Nm vi runs, or the .Cm lines option is explicitly reset by the user, .Nm ex Ns / Ns Nm vi enters the value into the environment. .It Ev NEXINIT A list of .Nm ex startup commands, read after .Pa /etc/vi.exrc . .It Ev SHELL The user's shell of choice .Pq see also the Cm shell No option . .It Ev TERM The user's terminal type. The default is the type .Dq unknown . If the .Ev TERM environment variable is not set when .Nm ex Ns / Ns Nm vi runs, or the .Cm term option is explicitly reset by the user, .Nm ex Ns / Ns Nm vi enters the value into the environment. .It Ev TMPDIR The location used to store temporary files .Pq see also the Cm directory No edit option . .El .Sh ASYNCHRONOUS EVENTS .Bl -tag -width "SIGWINCH" -compact .It Dv SIGALRM .Nm vi Ns / Ns Nm ex uses this signal for periodic backups of file modifications and to display .Dq busy messages when operations are likely to take a long time. .Pp .It Dv SIGHUP .It Dv SIGTERM If the current buffer has changed since it was last written in its entirety, the editor attempts to save the modified file so it can be later recovered. See the .Nm vi Ns / Ns Nm ex reference manual section .Sx Recovery for more information. .Pp .It Dv SIGINT When an interrupt occurs, the current operation is halted and the editor returns to the command level. If interrupted during text input, the text already input is resolved into the file as if the text input had been normally terminated. .Pp .It Dv SIGWINCH The screen is resized. See the .Nm vi Ns / Ns Nm ex reference manual section .Sx Sizing the Screen for more information. .\" .Pp .\" .It Dv SIGCONT .\" .It Dv SIGTSTP .\" .Nm vi Ns / Ns Nm ex .\" ignores these signals. .El .Sh FILES .Bl -tag -width "/var/tmp/vi.recover" .It Pa /bin/sh The default user shell. .It Pa /etc/vi.exrc System-wide .Nm vi startup file. It is read for .Nm ex commands first in the startup sequence. Must be owned by root or the user, and writable only by the owner. .It Pa /tmp Temporary file directory. .It Pa /var/tmp/vi.recover The default recovery file directory. .It Pa $HOME/.nexrc First choice for user's home directory startup file, read for .Nm ex commands right after .Pa /etc/vi.exrc unless either .Ev NEXINIT or .Ev EXINIT are set. Must be owned by root or the user, and writable only by the owner. .It Pa $HOME/.exrc Second choice for user's home directory startup file, read for .Nm ex commands under the same conditions as .Pa $HOME/.nexrc . .It Pa .nexrc First choice for local directory startup file, read for .Nm ex commands at the end of the startup sequence if the .Cm exrc option was turned on earlier. Must be owned by the user and writable only by the owner. .It Pa .exrc Second choice for local directory startup file, read for .Nm ex commands under the same conditions as .Pa .nexrc . .El .Sh EXIT STATUS The .Nm ex and .Nm vi utilities exit 0 on success, and \*(Gt0 if an error occurs. .Sh SEE ALSO .Xr ctags 1 , .Xr iconv 1 , .Xr re_format 7 +.Rs +.%T vi/ex reference manual +.%U https://docs.freebsd.org/44doc/usd/13.viref/paper.pdf +.Re .Sh STANDARDS .Nm nex Ns / Ns Nm nvi is close to .St -p1003.1-2008 . That document differs from historical .Nm ex Ns / Ns Nm vi practice in several places; there are changes to be made on both sides. .Sh HISTORY The .Nm ex editor first appeared in .Bx 1 . The .Nm nex Ns / Ns Nm nvi replacements for the .Nm ex Ns / Ns Nm vi editor first appeared in .Bx 4.4 . .Sh AUTHORS .An Bill Joy wrote the original version of .Nm ex in 1977. Index: vendor/nvi/dist/regex/engine.c =================================================================== --- vendor/nvi/dist/regex/engine.c (revision 366306) +++ vendor/nvi/dist/regex/engine.c (revision 366307) @@ -1,1036 +1,1036 @@ /* $NetBSD: engine.c,v 1.7 2011/11/19 17:45:11 tnozaki Exp $ */ /*- * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer of the University of Toronto. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)engine.c 8.4 (Berkeley) 3/19/94 */ /* * The matching engine and friends. This file is #included by regexec.c * after suitable #defines of a variety of macros used herein, so that * different state representations can be used without duplicating masses * of code. */ #ifdef SNAMES #define matcher smatcher #define fast sfast #define slow sslow #define dissect sdissect #define backref sbackref #define step sstep #define print sprint #define at sat #define match smat #endif #ifdef LNAMES #define matcher lmatcher #define fast lfast #define slow lslow #define dissect ldissect #define backref lbackref #define step lstep #define print lprint #define at lat #define match lmat #endif /* another structure passed up and down to avoid zillions of parameters */ struct match { struct re_guts *g; int eflags; regmatch_t *pmatch; /* [nsub+1] (0 element unused) */ const RCHAR_T *offp; /* offsets work from here */ const RCHAR_T *beginp; /* start of string -- virtual NUL precedes */ const RCHAR_T *endp; /* end of string -- virtual NUL here */ const RCHAR_T *coldp; /* can be no match starting before here */ const RCHAR_T **lastpos; /* [nplus+1] */ STATEVARS; states st; /* current states */ states fresh; /* states for a fresh start */ states tmp; /* temporary */ states empty; /* empty set of states */ }; /* ========= begin header generated by ./mkh ========= */ #ifdef __cplusplus extern "C" { #endif /* === engine.c === */ static int matcher(struct re_guts *g, const RCHAR_T *string, size_t nmatch, regmatch_t pmatch[], int eflags); static const RCHAR_T *dissect(struct match *m, const RCHAR_T *start, const RCHAR_T *stop, sopno startst, sopno stopst); static const RCHAR_T *backref(struct match *m, const RCHAR_T *start, const RCHAR_T *stop, sopno startst, sopno stopst, sopno lev); static const RCHAR_T *fast(struct match *m, const RCHAR_T *start, const RCHAR_T *stop, sopno startst, sopno stopst); static const RCHAR_T *slow(struct match *m, const RCHAR_T *start, const RCHAR_T *stop, sopno startst, sopno stopst); static states step(struct re_guts *g, sopno start, sopno stop, states bef, int flag, RCHAR_T ch, states aft); #define BOL (1) #define EOL (BOL+1) #define BOLEOL (BOL+2) #define NOTHING (BOL+3) #define BOW (BOL+4) #define EOW (BOL+5) #ifdef REDEBUG static void print(struct match *m, char *caption, states st, int ch, FILE *d); #endif #ifdef REDEBUG static void at(struct match *m, char *title, char *start, char *stop, sopno startst, sopno stopst); #endif #ifdef REDEBUG static char *pchar(int ch); #endif #ifdef __cplusplus } #endif /* ========= end header generated by ./mkh ========= */ #ifdef REDEBUG #define SP(t, s, c) print(m, t, s, c, stdout) #define AT(t, p1, p2, s1, s2) at(m, t, p1, p2, s1, s2) -#define NOTE(str) { if (m->eflags®_TRACE) printf("=%s\n", (str)); } +#define NOTE(str) do { if (m->eflags®_TRACE) printf("=%s\n", (str)); } while(0); #else #define SP(t, s, c) /* nothing */ #define AT(t, p1, p2, s1, s2) /* nothing */ #define NOTE(s) /* nothing */ #endif /* - matcher - the actual matching engine */ static int /* 0 success, REG_NOMATCH failure */ matcher(struct re_guts *g, const RCHAR_T *string, size_t nmatch, regmatch_t pmatch[], int eflags) { const RCHAR_T *endp; size_t i; struct match mv; struct match *m = &mv; const RCHAR_T *dp; const sopno gf = g->firststate+1; /* +1 for OEND */ const sopno gl = g->laststate; const RCHAR_T *start; const RCHAR_T *stop; /* simplify the situation where possible */ if (g->cflags®_NOSUB) nmatch = 0; if (eflags®_STARTEND) { start = string + pmatch[0].rm_so; stop = string + pmatch[0].rm_eo; } else { start = string; stop = start + STRLEN(start); } if (stop < start) return(REG_INVARG); /* prescreening; this does wonders for this rather slow code */ if (g->must != NULL) { for (dp = start; dp < stop; dp++) if (*dp == g->must[0] && (size_t)(stop - dp) >= g->mlen && MEMCMP(dp, g->must, g->mlen) == 0) break; if (dp == stop) /* we didn't find g->must */ return(REG_NOMATCH); } /* match struct setup */ m->g = g; m->eflags = eflags; m->pmatch = NULL; m->lastpos = NULL; m->offp = string; m->beginp = start; m->endp = stop; STATESETUP(m, 4); SETUP(m->st); SETUP(m->fresh); SETUP(m->tmp); SETUP(m->empty); CLEAR(m->empty); /* this loop does only one repetition except for backrefs */ for (;;) { endp = fast(m, start, stop, gf, gl); if (endp == NULL) { /* a miss */ STATETEARDOWN(m); return(REG_NOMATCH); } if (nmatch == 0 && !g->backrefs) break; /* no further info needed */ /* where? */ assert(m->coldp != NULL); for (;;) { NOTE("finding start"); endp = slow(m, m->coldp, stop, gf, gl); if (endp != NULL) break; assert(m->coldp < m->endp); m->coldp++; } if (nmatch == 1 && !g->backrefs) break; /* no further info needed */ /* oh my, he wants the subexpressions... */ if (m->pmatch == NULL) m->pmatch = (regmatch_t *)malloc((m->g->nsub + 1) * sizeof(regmatch_t)); if (m->pmatch == NULL) { STATETEARDOWN(m); return(REG_ESPACE); } for (i = 1; i <= m->g->nsub; i++) m->pmatch[i].rm_so = m->pmatch[i].rm_eo = -1; if (!g->backrefs && !(m->eflags®_BACKR)) { NOTE("dissecting"); dp = dissect(m, m->coldp, endp, gf, gl); } else { if (g->nplus > 0 && m->lastpos == NULL) m->lastpos = (const RCHAR_T **)malloc((g->nplus+1) * sizeof(const RCHAR_T *)); if (g->nplus > 0 && m->lastpos == NULL) { free(m->pmatch); STATETEARDOWN(m); return(REG_ESPACE); } NOTE("backref dissect"); dp = backref(m, m->coldp, endp, gf, gl, (sopno)0); } if (dp != NULL) break; /* uh-oh... we couldn't find a subexpression-level match */ assert(g->backrefs); /* must be back references doing it */ assert(g->nplus == 0 || m->lastpos != NULL); for (;;) { if (dp != NULL || endp <= m->coldp) break; /* defeat */ NOTE("backoff"); endp = slow(m, m->coldp, endp-1, gf, gl); if (endp == NULL) break; /* defeat */ /* try it on a shorter possibility */ #ifndef NDEBUG for (i = 1; i <= m->g->nsub; i++) { assert(m->pmatch[i].rm_so == -1); assert(m->pmatch[i].rm_eo == -1); } #endif NOTE("backoff dissect"); dp = backref(m, m->coldp, endp, gf, gl, (sopno)0); } assert(dp == NULL || dp == endp); if (dp != NULL) /* found a shorter one */ break; /* despite initial appearances, there is no match here */ NOTE("false alarm"); start = m->coldp + 1; /* recycle starting later */ assert(start <= stop); } /* fill in the details if requested */ if (nmatch > 0) { pmatch[0].rm_so = m->coldp - m->offp; pmatch[0].rm_eo = endp - m->offp; } if (nmatch > 1) { assert(m->pmatch != NULL); for (i = 1; i < nmatch; i++) if (i <= m->g->nsub) pmatch[i] = m->pmatch[i]; else { pmatch[i].rm_so = -1; pmatch[i].rm_eo = -1; } } if (m->pmatch != NULL) free((char *)m->pmatch); if (m->lastpos != NULL) free((char *)m->lastpos); STATETEARDOWN(m); return(0); } /* - dissect - figure out what matched what, no back references */ static const RCHAR_T * /* == stop (success) always */ dissect(struct match *m, const RCHAR_T *start, const RCHAR_T *stop, sopno startst, sopno stopst) { int i; sopno ss; /* start sop of current subRE */ sopno es; /* end sop of current subRE */ const RCHAR_T *sp; /* start of string matched by it */ const RCHAR_T *stp; /* string matched by it cannot pass here */ const RCHAR_T *rest; /* start of rest of string */ const RCHAR_T *tail; /* string unmatched by rest of RE */ sopno ssub; /* start sop of subsubRE */ sopno esub; /* end sop of subsubRE */ const RCHAR_T *ssp; /* start of string matched by subsubRE */ const RCHAR_T *sep; /* end of string matched by subsubRE */ const RCHAR_T *oldssp; /* previous ssp */ const RCHAR_T *dp; AT("diss", start, stop, startst, stopst); sp = start; for (ss = startst; ss < stopst; ss = es) { /* identify end of subRE */ es = ss; switch (m->g->strip[es]) { case OPLUS_: case OQUEST_: es += m->g->stripdata[es]; break; case OCH_: while (m->g->strip[es] != O_CH) es += m->g->stripdata[es]; break; } es++; /* figure out what it matched */ switch (m->g->strip[ss]) { case OEND: assert(nope); break; case OCHAR: sp++; break; case OBOL: case OEOL: case OBOW: case OEOW: break; case OANY: case OANYOF: sp++; break; case OBACK_: case O_BACK: assert(nope); break; /* cases where length of match is hard to find */ case OQUEST_: stp = stop; for (;;) { /* how long could this one be? */ rest = slow(m, sp, stp, ss, es); assert(rest != NULL); /* it did match */ /* could the rest match the rest? */ tail = slow(m, rest, stop, es, stopst); if (tail == stop) break; /* yes! */ /* no -- try a shorter match for this one */ stp = rest - 1; assert(stp >= sp); /* it did work */ } ssub = ss + 1; esub = es - 1; /* did innards match? */ if (slow(m, sp, rest, ssub, esub) != NULL) { dp = dissect(m, sp, rest, ssub, esub); assert(dp == rest); } else /* no */ assert(sp == rest); sp = rest; break; case OPLUS_: stp = stop; for (;;) { /* how long could this one be? */ rest = slow(m, sp, stp, ss, es); assert(rest != NULL); /* it did match */ /* could the rest match the rest? */ tail = slow(m, rest, stop, es, stopst); if (tail == stop) break; /* yes! */ /* no -- try a shorter match for this one */ stp = rest - 1; assert(stp >= sp); /* it did work */ } ssub = ss + 1; esub = es - 1; ssp = sp; oldssp = ssp; for (;;) { /* find last match of innards */ sep = slow(m, ssp, rest, ssub, esub); if (sep == NULL || sep == ssp) break; /* failed or matched null */ oldssp = ssp; /* on to next try */ ssp = sep; } if (sep == NULL) { /* last successful match */ sep = ssp; ssp = oldssp; } assert(sep == rest); /* must exhaust substring */ assert(slow(m, ssp, sep, ssub, esub) == rest); dp = dissect(m, ssp, sep, ssub, esub); assert(dp == sep); sp = rest; break; case OCH_: stp = stop; for (;;) { /* how long could this one be? */ rest = slow(m, sp, stp, ss, es); assert(rest != NULL); /* it did match */ /* could the rest match the rest? */ tail = slow(m, rest, stop, es, stopst); if (tail == stop) break; /* yes! */ /* no -- try a shorter match for this one */ stp = rest - 1; assert(stp >= sp); /* it did work */ } ssub = ss + 1; esub = ss + m->g->stripdata[ss] - 1; assert(m->g->strip[esub] == OOR1); for (;;) { /* find first matching branch */ if (slow(m, sp, rest, ssub, esub) == rest) break; /* it matched all of it */ /* that one missed, try next one */ assert(m->g->strip[esub] == OOR1); esub++; assert(m->g->strip[esub] == OOR2); ssub = esub + 1; esub += m->g->stripdata[esub]; if (m->g->strip[esub] == OOR2) esub--; else assert(m->g->strip[esub] == O_CH); } dp = dissect(m, sp, rest, ssub, esub); assert(dp == rest); sp = rest; break; case O_PLUS: case O_QUEST: case OOR1: case OOR2: case O_CH: assert(nope); break; case OLPAREN: i = m->g->stripdata[ss]; assert(0 < i && i <= m->g->nsub); m->pmatch[i].rm_so = sp - m->offp; break; case ORPAREN: i = m->g->stripdata[ss]; assert(0 < i && i <= m->g->nsub); m->pmatch[i].rm_eo = sp - m->offp; break; default: /* uh oh */ assert(nope); break; } } assert(sp == stop); return(sp); } /* - backref - figure out what matched what, figuring in back references */ static const RCHAR_T * /* == stop (success) or NULL (failure) */ backref(struct match *m, const RCHAR_T *start, const RCHAR_T *stop, sopno startst, sopno stopst, sopno lev) /* PLUS nesting level */ { int i; sopno ss; /* start sop of current subRE */ const RCHAR_T *sp; /* start of string matched by it */ sopno ssub; /* start sop of subsubRE */ sopno esub; /* end sop of subsubRE */ const RCHAR_T *ssp; /* start of string matched by subsubRE */ const RCHAR_T *dp; size_t len; int hard; sop s; RCHAR_T d; regoff_t offsave; cset *cs; AT("back", start, stop, startst, stopst); sp = start; /* get as far as we can with easy stuff */ hard = 0; for (ss = startst; !hard && ss < stopst; ss++) { s = m->g->strip[ss]; d = m->g->stripdata[ss]; switch (s) { case OCHAR: if (sp == stop || *sp++ != d) return(NULL); break; case OANY: if (sp == stop) return(NULL); sp++; break; case OANYOF: cs = &m->g->sets[d]; if (sp == stop || !CHIN(cs, *sp++)) return(NULL); break; case OBOL: if ( (sp == m->beginp && !(m->eflags®_NOTBOL)) || (sp < m->endp && *(sp-1) == '\n' && (m->g->cflags®_NEWLINE)) ) { /* yes */ } else return(NULL); break; case OEOL: if ( (sp == m->endp && !(m->eflags®_NOTEOL)) || (sp < m->endp && *sp == '\n' && (m->g->cflags®_NEWLINE)) ) { /* yes */ } else return(NULL); break; case OBOW: if (( (sp == m->beginp && !(m->eflags®_NOTBOL)) || (sp < m->endp && *(sp-1) == '\n' && (m->g->cflags®_NEWLINE)) || (sp > m->beginp && !ISWORD(*(sp-1))) ) && (sp < m->endp && ISWORD(*sp)) ) { /* yes */ } else return(NULL); break; case OEOW: if (( (sp == m->endp && !(m->eflags®_NOTEOL)) || (sp < m->endp && *sp == '\n' && (m->g->cflags®_NEWLINE)) || (sp < m->endp && !ISWORD(*sp)) ) && (sp > m->beginp && ISWORD(*(sp-1))) ) { /* yes */ } else return(NULL); break; case O_QUEST: break; case OOR1: /* matches null but needs to skip */ ss++; s = m->g->strip[ss]; d = m->g->stripdata[ss]; do { assert(s == OOR2); ss += d; s = m->g->strip[ss]; d = m->g->stripdata[ss]; } while (s != O_CH); /* note that the ss++ gets us past the O_CH */ break; default: /* have to make a choice */ hard = 1; break; } } if (!hard) { /* that was it! */ if (sp != stop) return(NULL); return(sp); } ss--; /* adjust for the for's final increment */ /* the hard stuff */ AT("hard", sp, stop, ss, stopst); s = m->g->strip[ss]; d = m->g->stripdata[ss]; switch (s) { case OBACK_: /* the vilest depths */ i = d; assert(0 < i && i <= m->g->nsub); if (m->pmatch[i].rm_eo == -1) return(NULL); assert(m->pmatch[i].rm_so != -1); len = m->pmatch[i].rm_eo - m->pmatch[i].rm_so; assert(stop - m->beginp >= len); if (sp > stop - len) return(NULL); /* not enough left to match */ ssp = m->offp + m->pmatch[i].rm_so; if (memcmp(sp, ssp, len) != 0) return(NULL); while (m->g->strip[ss] != O_BACK || m->g->stripdata[ss] != i) ss++; return(backref(m, sp+len, stop, ss+1, stopst, lev)); break; case OQUEST_: /* to null or not */ dp = backref(m, sp, stop, ss+1, stopst, lev); if (dp != NULL) return(dp); /* not */ return(backref(m, sp, stop, ss+d+1, stopst, lev)); break; case OPLUS_: assert(m->lastpos != NULL); assert(lev+1 <= m->g->nplus); m->lastpos[lev+1] = sp; return(backref(m, sp, stop, ss+1, stopst, lev+1)); break; case O_PLUS: if (sp == m->lastpos[lev]) /* last pass matched null */ return(backref(m, sp, stop, ss+1, stopst, lev-1)); /* try another pass */ m->lastpos[lev] = sp; dp = backref(m, sp, stop, ss-d+1, stopst, lev); if (dp == NULL) return(backref(m, sp, stop, ss+1, stopst, lev-1)); else return(dp); break; case OCH_: /* find the right one, if any */ ssub = ss + 1; esub = ss + d - 1; assert(m->g->strip[esub] == OOR1); for (;;) { /* find first matching branch */ dp = backref(m, sp, stop, ssub, esub, lev); if (dp != NULL) return(dp); /* that one missed, try next one */ if (m->g->strip[esub] == O_CH) return(NULL); /* there is none */ esub++; assert(m->g->strip[esub] == OOR2); ssub = esub + 1; esub += m->g->stripdata[esub]; if (m->g->strip[esub] == OOR2) esub--; else assert(m->g->strip[esub] == O_CH); } break; case OLPAREN: /* must undo assignment if rest fails */ i = d; assert(0 < i && i <= m->g->nsub); offsave = m->pmatch[i].rm_so; m->pmatch[i].rm_so = sp - m->offp; dp = backref(m, sp, stop, ss+1, stopst, lev); if (dp != NULL) return(dp); m->pmatch[i].rm_so = offsave; return(NULL); break; case ORPAREN: /* must undo assignment if rest fails */ i = d; assert(0 < i && i <= m->g->nsub); offsave = m->pmatch[i].rm_eo; m->pmatch[i].rm_eo = sp - m->offp; dp = backref(m, sp, stop, ss+1, stopst, lev); if (dp != NULL) return(dp); m->pmatch[i].rm_eo = offsave; return(NULL); break; default: /* uh oh */ assert(nope); break; } /* "can't happen" */ assert(nope); /* NOTREACHED */ return NULL; } /* - fast - step through the string at top speed */ static const RCHAR_T * /* where tentative match ended, or NULL */ fast(struct match *m, const RCHAR_T *start, const RCHAR_T *stop, sopno startst, sopno stopst) { states st = m->st; states fresh = m->fresh; states tmp = m->tmp; const RCHAR_T *p = start; RCHAR_T c = (start == m->beginp) ? OUT : *(start-1); RCHAR_T lastc; /* previous c */ int flag; int i; const RCHAR_T *coldp; /* last p after which no match was underway */ CLEAR(st); SET1(st, startst); st = step(m->g, startst, stopst, st, NOTHING, OUT, st); ASSIGN(fresh, st); SP("start", st, *p); coldp = NULL; for (;;) { /* next character */ lastc = c; c = (p == m->endp) ? OUT : *p; if (EQ(st, fresh)) coldp = p; /* is there an EOL and/or BOL between lastc and c? */ flag = 0; i = 0; if ( (lastc == '\n' && m->g->cflags®_NEWLINE) || (lastc == OUT && !(m->eflags®_NOTBOL)) ) { flag = BOL; i = m->g->nbol; } if ( (c == '\n' && m->g->cflags®_NEWLINE) || (c == OUT && !(m->eflags®_NOTEOL)) ) { flag = (flag == BOL) ? BOLEOL : EOL; i += m->g->neol; } if (i != 0) { for (; i > 0; i--) st = step(m->g, startst, stopst, st, flag, OUT, st); SP("boleol", st, c); } /* how about a word boundary? */ if ( (flag == BOL || (lastc != OUT && !ISWORD(lastc))) && (c != OUT && ISWORD(c)) ) { flag = BOW; } if ( (lastc != OUT && ISWORD(lastc)) && (flag == EOL || (c != OUT && !ISWORD(c))) ) { flag = EOW; } if (flag == BOW || flag == EOW) { st = step(m->g, startst, stopst, st, flag, OUT, st); SP("boweow", st, c); } /* are we done? */ if (ISSET(st, stopst) || p == stop) break; /* NOTE BREAK OUT */ /* no, we must deal with this character */ ASSIGN(tmp, st); ASSIGN(st, fresh); assert(c != OUT); st = step(m->g, startst, stopst, tmp, 0, c, st); SP("aft", st, c); assert(EQ(step(m->g, startst, stopst, st, NOTHING, OUT, st), st)); p++; } assert(coldp != NULL); m->coldp = coldp; if (ISSET(st, stopst)) return(p+1); else return(NULL); } /* - slow - step through the string more deliberately */ static const RCHAR_T * /* where it ended */ slow(struct match *m, const RCHAR_T *start, const RCHAR_T *stop, sopno startst, sopno stopst) { states st = m->st; states empty = m->empty; states tmp = m->tmp; const RCHAR_T *p = start; RCHAR_T c = (start == m->beginp) ? OUT : *(start-1); RCHAR_T lastc; /* previous c */ int flag; int i; const RCHAR_T *matchp; /* last p at which a match ended */ AT("slow", start, stop, startst, stopst); CLEAR(st); SET1(st, startst); SP("sstart", st, *p); st = step(m->g, startst, stopst, st, NOTHING, OUT, st); matchp = NULL; for (;;) { /* next character */ lastc = c; c = (p == m->endp) ? OUT : *p; /* is there an EOL and/or BOL between lastc and c? */ flag = 0; i = 0; if ( (lastc == '\n' && m->g->cflags®_NEWLINE) || (lastc == OUT && !(m->eflags®_NOTBOL)) ) { flag = BOL; i = m->g->nbol; } if ( (c == '\n' && m->g->cflags®_NEWLINE) || (c == OUT && !(m->eflags®_NOTEOL)) ) { flag = (flag == BOL) ? BOLEOL : EOL; i += m->g->neol; } if (i != 0) { for (; i > 0; i--) st = step(m->g, startst, stopst, st, flag, OUT, st); SP("sboleol", st, c); } /* how about a word boundary? */ if ( (flag == BOL || (lastc != OUT && !ISWORD(lastc))) && (c != OUT && ISWORD(c)) ) { flag = BOW; } if ( (lastc != OUT && ISWORD(lastc)) && (flag == EOL || (c != OUT && !ISWORD(c))) ) { flag = EOW; } if (flag == BOW || flag == EOW) { st = step(m->g, startst, stopst, st, flag, OUT, st); SP("sboweow", st, c); } /* are we done? */ if (ISSET(st, stopst)) matchp = p; if (EQ(st, empty) || p == stop) break; /* NOTE BREAK OUT */ /* no, we must deal with this character */ ASSIGN(tmp, st); ASSIGN(st, empty); assert(c != OUT); st = step(m->g, startst, stopst, tmp, 0, c, st); SP("saft", st, c); assert(EQ(step(m->g, startst, stopst, st, NOTHING, OUT, st), st)); p++; } return(matchp); } /* - step - map set of states reachable before char to set reachable after */ static states step(struct re_guts *g, sopno start, /* start state within strip */ sopno stop, /* state after stop state within strip */ states bef, /* states reachable before */ int flag, /* NONCHAR flag */ RCHAR_T ch, /* character code */ states aft) /* states already known reachable after */ { cset *cs; sop s; RCHAR_T d; sopno pc; onestate here; /* note, macros know this name */ sopno look; int i; for (pc = start, INIT(here, pc); pc != stop; pc++, INC(here)) { s = g->strip[pc]; d = g->stripdata[pc]; switch (s) { case OEND: assert(pc == stop-1); break; case OCHAR: /* only characters can match */ assert(!flag || ch != d); if (ch == d) FWD(aft, bef, 1); break; case OBOL: if (flag == BOL || flag == BOLEOL) FWD(aft, bef, 1); break; case OEOL: if (flag == EOL || flag == BOLEOL) FWD(aft, bef, 1); break; case OBOW: if (flag == BOW) FWD(aft, bef, 1); break; case OEOW: if (flag == EOW) FWD(aft, bef, 1); break; case OANY: if (!flag) FWD(aft, bef, 1); break; case OANYOF: cs = &g->sets[d]; if (!flag && CHIN(cs, ch)) FWD(aft, bef, 1); break; case OBACK_: /* ignored here */ case O_BACK: FWD(aft, aft, 1); break; case OPLUS_: /* forward, this is just an empty */ FWD(aft, aft, 1); break; case O_PLUS: /* both forward and back */ FWD(aft, aft, 1); i = ISSETBACK(aft, d); BACK(aft, aft, d); if (!i && ISSETBACK(aft, d)) { /* oho, must reconsider loop body */ pc -= d + 1; INIT(here, pc); } break; case OQUEST_: /* two branches, both forward */ FWD(aft, aft, 1); FWD(aft, aft, d); break; case O_QUEST: /* just an empty */ FWD(aft, aft, 1); break; case OLPAREN: /* not significant here */ case ORPAREN: FWD(aft, aft, 1); break; case OCH_: /* mark the first two branches */ FWD(aft, aft, 1); assert(OP(g->strip[pc+d]) == OOR2); FWD(aft, aft, d); break; case OOR1: /* done a branch, find the O_CH */ if (ISSTATEIN(aft, here)) { for (look = 1; /**/; look += d) { s = g->strip[pc+look]; d = g->stripdata[pc+look]; if (s == O_CH) break; assert(s == OOR2); } FWD(aft, aft, look); } break; case OOR2: /* propagate OCH_'s marking */ FWD(aft, aft, 1); if (g->strip[pc+d] != O_CH) { assert(g->strip[pc+d] == OOR2); FWD(aft, aft, d); } break; case O_CH: /* just empty */ FWD(aft, aft, 1); break; default: /* ooooops... */ assert(nope); break; } } return(aft); } #ifdef REDEBUG /* - print - print a set of states */ static void print(struct match *m, char *caption, states st, int ch, FILE *d) { struct re_guts *g = m->g; int i; int first = 1; if (!(m->eflags®_TRACE)) return; fprintf(d, "%s", caption); if (ch != '\0') fprintf(d, " %s", pchar(ch)); for (i = 0; i < g->nstates; i++) if (ISSET(st, i)) { fprintf(d, "%s%d", (first) ? "\t" : ", ", i); first = 0; } fprintf(d, "\n"); } /* - at - print current situation */ static void at(struct match *m, char *title, char *start, char *stop, sopno startst, sopno stopst) { if (!(m->eflags®_TRACE)) return; printf("%s %s-", title, pchar(*start)); printf("%s ", pchar(*stop)); printf("%ld-%ld\n", (long)startst, (long)stopst); } #ifndef PCHARDONE #define PCHARDONE /* never again */ /* - pchar - make a character printable * * Is this identical to regchar() over in debug.c? Well, yes. But a * duplicate here avoids having a debugging-capable regexec.o tied to * a matching debug.o, and this is convenient. It all disappears in * the non-debug compilation anyway, so it doesn't matter much. */ static char * /* -> representation */ pchar(int ch) { static char pbuf[10]; if (isprint(ch) || ch == ' ') snprintf(pbuf, sizeof(pbuf), "%c", ch); else snprintf(pbuf, sizeof(pbuf), "\\%o", ch); return(pbuf); } #endif #endif #undef matcher #undef fast #undef slow #undef dissect #undef backref #undef step #undef print #undef at #undef match Index: vendor/nvi/dist/regex/regexec.c =================================================================== --- vendor/nvi/dist/regex/regexec.c (revision 366306) +++ vendor/nvi/dist/regex/regexec.c (revision 366307) @@ -1,173 +1,173 @@ /* $NetBSD: regexec.c,v 1.4 2009/10/31 20:11:53 dsl Exp $ */ /*- * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer of the University of Toronto. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)regexec.c 8.2 (Berkeley) 3/16/94 */ #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)regexec.c 8.2 (Berkeley) 3/16/94"; #endif /* LIBC_SCCS and not lint */ /* * the outer shell of regexec() * * This file includes engine.c *twice*, after muchos fiddling with the * macros that code uses. This lets the same code operate on two different * representations for state sets. */ #include #include #include #include #include #include #include #include "utils.h" #include "regex2.h" /* macros for manipulating states, small version */ #define states int #define states1 int /* for later use in regexec() decision */ #define CLEAR(v) ((v) = 0) #define SET0(v, n) ((v) &= ~(1 << (n))) #define SET1(v, n) ((v) |= 1 << (n)) #define ISSET(v, n) ((v) & (1 << (n))) #define ASSIGN(d, s) ((d) = (s)) #define EQ(a, b) ((a) == (b)) #define STATEVARS int dummy /* dummy version */ #define STATESETUP(m, n) /* nothing */ #define STATETEARDOWN(m) /* nothing */ #define SETUP(v) ((v) = 0) #define onestate int #define INIT(o, n) ((o) = (unsigned)1 << (n)) #define INC(o) ((o) <<= 1) #define ISSTATEIN(v, o) ((v) & (o)) /* some abbreviations; note that some of these know variable names! */ /* do "if I'm here, I can also be there" etc without branches */ #define FWD(dst, src, n) ((dst) |= ((unsigned)(src)&(here)) << (n)) #define BACK(dst, src, n) ((dst) |= ((unsigned)(src)&(here)) >> (n)) #define ISSETBACK(v, n) ((v) & ((unsigned)here >> (n))) /* function names */ #define SNAMES /* engine.c looks after details */ #include "engine.c" /* now undo things */ #undef states #undef CLEAR #undef SET0 #undef SET1 #undef ISSET #undef ASSIGN #undef EQ #undef STATEVARS #undef STATESETUP #undef STATETEARDOWN #undef SETUP #undef onestate #undef INIT #undef INC #undef ISSTATEIN #undef FWD #undef BACK #undef ISSETBACK #undef SNAMES /* macros for manipulating states, large version */ #define states char * #define CLEAR(v) memset(v, 0, m->g->nstates) #define SET0(v, n) ((v)[n] = 0) #define SET1(v, n) ((v)[n] = 1) #define ISSET(v, n) ((v)[n]) #define ASSIGN(d, s) memcpy(d, s, m->g->nstates) #define EQ(a, b) (memcmp(a, b, m->g->nstates) == 0) #define STATEVARS int vn; char *space -#define STATESETUP(m, nv) { (m)->space = malloc((nv)*(m)->g->nstates); \ +#define STATESETUP(m, nv) do { (m)->space = malloc((nv)*(m)->g->nstates); \ if ((m)->space == NULL) return(REG_ESPACE); \ - (m)->vn = 0; } -#define STATETEARDOWN(m) { free((m)->space); } + (m)->vn = 0; } while (0) +#define STATETEARDOWN(m) free((m)->space) #define SETUP(v) ((v) = &m->space[m->vn++ * m->g->nstates]) #define onestate int #define INIT(o, n) ((o) = (n)) #define INC(o) ((o)++) #define ISSTATEIN(v, o) ((v)[o]) /* some abbreviations; note that some of these know variable names! */ /* do "if I'm here, I can also be there" etc without branches */ #define FWD(dst, src, n) ((dst)[here+(n)] |= (src)[here]) #define BACK(dst, src, n) ((dst)[here-(n)] |= (src)[here]) #define ISSETBACK(v, n) ((v)[here - (n)]) /* function names */ #define LNAMES /* flag */ #include "engine.c" /* - regexec - interface for matching = extern int regexec(const regex_t *, const char *, size_t, \ = regmatch_t [], int); = #define REG_NOTBOL 00001 = #define REG_NOTEOL 00002 = #define REG_STARTEND 00004 = #define REG_TRACE 00400 // tracing of execution = #define REG_LARGE 01000 // force large representation = #define REG_BACKR 02000 // force use of backref code * * We put this here so we can exploit knowledge of the state representation * when choosing which matcher to call. Also, by this point the matchers * have been prototyped. */ int /* 0 success, REG_NOMATCH failure */ regexec(const regex_t *preg, const RCHAR_T *string, size_t nmatch, regmatch_t *pmatch, int eflags) { struct re_guts *g = preg->re_g; #ifdef REDEBUG # define GOODFLAGS(f) (f) #else # define GOODFLAGS(f) ((f)&(REG_NOTBOL|REG_NOTEOL|REG_STARTEND)) #endif if (preg->re_magic != MAGIC1 || g->magic != MAGIC2) return(REG_BADPAT); assert(!(g->iflags&BAD)); if (g->iflags&BAD) /* backstop for no-debug case */ return(REG_BADPAT); eflags = GOODFLAGS(eflags); if (g->nstates <= (int)(CHAR_BIT*sizeof(states1)) && !(eflags®_LARGE)) return(smatcher(g, string, nmatch, pmatch, eflags)); else return(lmatcher(g, string, nmatch, pmatch, eflags)); } Index: vendor/nvi/dist/vi/v_itxt.c =================================================================== --- vendor/nvi/dist/vi/v_itxt.c (revision 366306) +++ vendor/nvi/dist/vi/v_itxt.c (revision 366307) @@ -1,510 +1,510 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #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 { \ +#define LOG_CORRECT do { \ if (!MAPPED_KEYS_WAITING(sp)) \ (void)log_cursor(sp); \ -} +} while (0) static u_int32_t set_txt_std(SCR *, VICMD *, u_int32_t); /* * v_iA -- [count]A * Append text to the end of the line. * * PUBLIC: int v_iA(SCR *, VICMD *); */ int v_iA(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(SCR *, VICMD *); */ int v_ia(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *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(SCR *, VICMD *); */ int v_iI(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(SCR *, VICMD *); */ int v_ii(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *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(SCR *, VICMD *, enum which); /* * v_iO -- [count]O * Insert text above this line. * * PUBLIC: int v_iO(SCR *, VICMD *); */ int v_iO(SCR *sp, VICMD *vp) { return (io(sp, vp, O_cmd)); } /* * v_io -- [count]o * Insert text after this line. * * PUBLIC: int v_io(SCR *, VICMD *); */ int v_io(SCR *sp, VICMD *vp) { return (io(sp, vp, o_cmd)); } static int io(SCR *sp, VICMD *vp, enum which cmd) { recno_t ai_line, lno; size_t len; u_int32_t flags; CHAR_T *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 = L(""); 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(SCR *, VICMD *); */ int v_change(SCR *sp, VICMD *vp) { size_t blen, len; u_int32_t flags; int isempty, lmode, rval; CHAR_T *bp; CHAR_T *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_RETW(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_SPACEW(sp, bp, blen); return (rval); } /* * v_Replace -- [count]R * Overwrite multiple characters. * * PUBLIC: int v_Replace(SCR *, VICMD *); */ int v_Replace(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *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(SCR *, VICMD *); */ int v_subst(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; CHAR_T *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(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 .... * :map K aABC DEF ^VKKKKK * :map K 5aABC DEF ^VK * * 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); } Index: vendor/nvi/dist/vi/v_paragraph.c =================================================================== --- vendor/nvi/dist/vi/v_paragraph.c (revision 366306) +++ vendor/nvi/dist/vi/v_paragraph.c (revision 366307) @@ -1,335 +1,337 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" -#define INTEXT_CHECK { \ +#define INTEXT_CHECK do { \ 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; \ -} +} while (0) /* * 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(SCR *, VICMD *); */ int v_paragraphf(SCR *sp, VICMD *vp) { enum { P_INTEXT, P_INBLANK } pstate; size_t lastlen, len; recno_t cnt, lastlno, lno; int isempty; CHAR_T *p; char *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 (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(SCR *, VICMD *); */ int v_paragraphb(SCR *sp, VICMD *vp) { enum { P_INTEXT, P_INBLANK } pstate; size_t len; recno_t cnt, lno; CHAR_T *p; char *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 (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(SCR *, char *, char *); */ int v_buildps(SCR *sp, char *p_p, char *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, p_len + s_len + 1); vip = VIP(sp); 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); } Index: vendor/nvi/dist/vi/v_section.c =================================================================== --- vendor/nvi/dist/vi/v_section.c (revision 366306) +++ vendor/nvi/dist/vi/v_section.c (revision 366307) @@ -1,246 +1,247 @@ /*- * 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" #include #include #include #include #include #include #include #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(SCR *, VICMD *); */ int v_sectionf(SCR *sp, VICMD *vp) { recno_t cnt, lno; size_t len; CHAR_T *p; char *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 (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(SCR *, VICMD *); */ int v_sectionb(SCR *sp, VICMD *vp) { size_t len; recno_t cnt, lno; CHAR_T *p; char *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-. */ 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); } Index: vendor/nvi/dist/vi/v_sentence.c =================================================================== --- vendor/nvi/dist/vi/v_sentence.c (revision 366306) +++ vendor/nvi/dist/vi/v_sentence.c (revision 366307) @@ -1,351 +1,352 @@ /*- * 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" #include #include #include #include #include #include #include #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(SCR *, VICMD *); */ int v_sentencef(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(SCR *, VICMD *); */ int v_sentenceb(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 (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); } Index: vendor/nvi/dist/vi/v_txt.c =================================================================== --- vendor/nvi/dist/vi/v_txt.c (revision 366306) +++ vendor/nvi/dist/vi/v_txt.c (revision 366307) @@ -1,2897 +1,2898 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int txt_abbrev(SCR *, TEXT *, CHAR_T *, int, int *, int *); static void txt_ai_resolve(SCR *, TEXT *, int *); static TEXT *txt_backup(SCR *, TEXTH *, TEXT *, u_int32_t *); static int txt_dent(SCR *, TEXT *, int, int); static int txt_emark(SCR *, TEXT *, size_t); static void txt_err(SCR *, TEXTH *); static int txt_fc(SCR *, TEXT *, int *); static int txt_fc_col(SCR *, int, ARGS **); static int txt_hex(SCR *, TEXT *); static int txt_insch(SCR *, TEXT *, CHAR_T *, u_int); static int txt_isrch(SCR *, VICMD *, TEXT *, u_int8_t *); static int txt_map_end(SCR *); static int txt_map_init(SCR *); static int txt_margin(SCR *, TEXT *, TEXT *, int *, u_int32_t); static void txt_nomorech(SCR *); static void txt_Rresolve(SCR *, TEXTH *, TEXT *, const size_t); static int txt_resolve(SCR *, TEXTH *, u_int32_t); static int txt_showmatch(SCR *, TEXT *); static void txt_unmap(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(SCR *, VICMD *, ARG_CHAR_T, u_int); */ int v_tcmd(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(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(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) { \ +#define UPDATE_POSITION(sp, tp) do { \ (sp)->lno = (tp)->lno; \ (sp)->cno = (tp)->cno; \ -} +} while (0) /* * v_txt -- * Vi text input. * * PUBLIC: int v_txt(SCR *, VICMD *, MARK *, * PUBLIC: const CHAR_T *, size_t, ARG_CHAR_T, recno_t, u_long, u_int32_t); */ int v_txt( SCR *sp, VICMD *vp, MARK *tm, /* To MARK. */ const CHAR_T *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 = NULL; /* Current event. */ EVENT fc; /* File name completion event. */ GS *gp; TEXT *ntp, *tp; /* Input text structures. */ TEXT ait; /* Autoindent text structure. */ TEXT wmt = {{ 0 }}; /* 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; int nochange; CHAR_T *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 (!TAILQ_EMPTY(tiqh)) { tp = TAILQ_FIRST(tiqh); if (TAILQ_NEXT(tp, q) != NULL || tp->lb_len < (len + 32) * sizeof(CHAR_T)) { text_lfree(tiqh); goto newtp; } tp->ai = tp->insert = tp->offset = tp->owrite = 0; if (lp != NULL) { tp->len = len; BINC_RETW(sp, tp->lb, tp->lb_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); TAILQ_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 * 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 character causes * wrapmargin to split the line, the next user entered character is * discarded if it's a 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., "Iab", * 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; nochange = 0; 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, 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: /* 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 (LF_ISSET(TXT_RECORD) && rcol == 0 && 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, EVENT, vip->rep, vip->rep_len, (rcol + 1) * sizeof(EVENT)); vip->rep[rcol++] = *evp; } replay: if (LF_ISSET(TXT_REPLAY)) { if (rcol == vip->rep_cnt) goto k_escape; 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, replace the placeholder * (a carat) with the new character. We've already adjusted the cursor * because it has to appear on top of the placeholder character. * Historic practice. * * Skip tests for abbreviations; ":ab xa XA" followed by "ixa^V" * doesn't perform an abbreviation. Special case, ^V^J (not ^V^M) is * the same as ^J, historically. */ if (quote == Q_VTHIS) { FL_CLR(ec_flags, EC_QUOTED); if (LF_ISSET(TXT_MAPINPUT)) FL_SET(ec_flags, EC_MAPINPUT); if (evp->e_value != K_NL) { quote = Q_NOTSET; goto insl_ch; } quote = Q_NOTSET; } /* * !!! * Translate "[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 . */ 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 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, L("\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 { \ +#define LINE_RESOLVE do { \ /* \ * 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; \ } \ -} +} while (0) 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 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; TAILQ_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 (nochange) { nochange = 0; if (v_txt_auto(sp, OOBLNO, &ait, ait.ai, ntp)) goto err; FREE_SPACEW(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_GOTOW(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_GOTOW(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; vip->rep_cnt = rcol; rcol = 0; abb = AB_NOTSET; LF_CLR(TXT_RECORD); LF_SET(TXT_REPLAY); /* * Some commands (e.g. 'o') need a 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_GOTOW(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_GOTOW(sp, ait.lb, ait.lb_len, tp->ai); MEMMOVE(ait.lb, tp->lb, tp->ai); ait.ai = ait.len = tp->ai; carat = C_NOTSET; nochange = 1; 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, O_SHIFTWIDTH, 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 * 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 * 's, so, 6 words. The historic tty interface used * '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 * '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) { if (ISBLANK(tp->lb[tp->cno - 1])) break; --tp->cno; ++tp->owrite; if (FL_ISSET(is_flags, IS_RUNNING)) tp->lb[tp->cno] = ' '; } else { if (LF_ISSET(TXT_ALTWERASE)) { --tp->cno; ++tp->owrite; if (FL_ISSET(is_flags, IS_RUNNING)) tp->lb[tp->cno] = ' '; } if (tp->cno > max) tmp = inword(tp->lb[tp->cno - 1]); while (tp->cno > max) { if (tmp != inword(tp->lb[tp->cno - 1]) || ISBLANK(tp->lb[tp->cno - 1])) break; --tp->cno; ++tp->owrite; if (FL_ISSET(is_flags, IS_RUNNING)) tp->lb[tp->cno] = ' '; } } /* 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, O_SHIFTWIDTH, 1)) goto err; goto ebuf_chk; 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" doesn't perform the abbreviation. */ goto insl_ch; case K_HEXCHAR: hexcnt = 1; goto insq_ch; case K_TAB: if (sp->showmode != SM_COMMAND && quote != Q_VTHIS && O_ISSET(sp, O_EXPANDTAB)) { if (txt_dent(sp, tp, O_TABSTOP, 1)) goto err; goto ebuf_chk; } goto insq_ch; default: /* Insert the character. */ if (LF_ISSET(TXT_SHOWMATCH)) { CHAR_T *match_chars, *cp; match_chars = VIP(sp)->mcs; cp = STRCHR(match_chars, evp->e_c); if (cp != NULL && (cp - match_chars) & 1) showmatch = 1; } 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 , and , as that's what the original * 4BSD documentation said. This is obviously wrong, * however, as 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 * or . */ 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 "[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 . */ if (hexcnt != 0 && hexcnt++ == 3) { 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. * 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_GOTOW(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_VNEXT) quote = Q_VTHIS; break; } #ifdef DEBUG if (tp->cno + tp->insert + tp->owrite != tp->len) { msgq(sp, M_ERR, "len %zu != cno: %zu ai: %zu insert %zu overwrite %zu", 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: F_CLR(sp, SC_TINPUT); txt_err(sp, sp->tiq); return (1); } /* * txt_abbrev -- * Handle abbreviations. */ static int txt_abbrev(SCR *sp, TEXT *tp, CHAR_T *pushcp, int isinfoline, int *didsubp, int *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", when the * 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 * 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 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) +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(SCR *sp, TEXT *tp, u_int32_t *ec_flagsp) { size_t len, off; CHAR_T *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 * 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 , review autoindent characters. */ static void txt_ai_resolve(SCR *sp, TEXT *tp, int *changedp) { u_long ts; int del; size_t cno, len, new, old, scno, spaces, tab_after_sp, tabs; CHAR_T *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 characters * in the line are resolved into the minimum number of characters. * Historic practice. */ ts = O_VAL(sp, O_TABSTOP); /* Figure out the last 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. * Keep analysing if expandtab is set. */ if ((!spaces || (!tab_after_sp && spaces < ts)) && !O_ISSET(sp, O_EXPANDTAB)) return; /* Count up spaces/tabs needed to get to the target. */ cno = 0; tabs = 0; if (!O_ISSET(sp, O_EXPANDTAB)) { for (; 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(SCR *, recno_t, TEXT *, size_t, TEXT *); */ int v_txt_auto(SCR *sp, recno_t lno, TEXT *aitp, size_t len, TEXT *tp) { size_t nlen; CHAR_T *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_RETW(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(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 = TAILQ_PREV(tp, _texth, q)) == NULL) { 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. */ TAILQ_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^Tx^Tx^Tx^Dx^Dx^Dx * 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 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_COL 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(SCR *sp, TEXT *tp, int swopt, int isindent) { CHAR_T ch; u_long sw, ts; size_t cno, current, spaces, target, tabs; int ai_reset; ts = O_VAL(sp, O_TABSTOP); sw = O_VAL(sp, swopt); /* * 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 , which doesn't need a full shiftwidth number * of columns because it's preceded by 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_COL(sp, tp->lb[cno]); target = current; if (isindent) target += COL_OFF(target, sw); else { --target; 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 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_COL(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=4iai^T^D * * Otherwise, count up the total spaces/tabs needed to get from the * beginning of the line (or the last non- character) to the * target. */ if (current >= target) spaces = tabs = 0; else { cno = current; tabs = 0; if (!O_ISSET(sp, O_EXPANDTAB)) { for (; 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 to replace N . */ 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 and ex command completion. */ static int txt_fc(SCR *sp, TEXT *tp, int *redrawp) { struct stat sb; ARGS **argv; EXCMD cmd; size_t indx, len, nlen, off; int argc; CHAR_T *p, *t, *bp; char *np, *epd = NULL; size_t nplen; int fstwd = 1; *redrawp = 0; ex_cinit(sp, &cmd, 0, 0, OOBLNO, OOBLNO, 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 { CHAR_T *ap; for (len = 0, off = MAX(tp->ai, tp->offset), ap = tp->lb + off, p = ap; off < tp->cno; ++off, ++ap) { if (IS_ESCAPE(sp, &cmd, *ap)) { if (++off == tp->cno) break; ++ap; len += 2; } else if (cmdskip(*ap)) { p = ap + 1; if (len > 0) fstwd = 0; len = 0; } else ++len; } } /* * If we are at the first word, do ex command completion instead of * file name completion. */ if (fstwd) (void)argv_flt_ex(sp, &cmd, p, len); else { if ((bp = argv_uesc(sp, &cmd, p, len)) == NULL) return (1); if (argv_flt_path(sp, &cmd, bp, STRLEN(bp))) { FREE_SPACEW(sp, bp, 0); return (0); } FREE_SPACEW(sp, bp, 0); } argc = cmd.argc; argv = cmd.argv; switch (argc) { case 0: /* No matches. */ (void)sp->gp->scr_bell(sp); return (0); case 1: /* One match. */ /* Always overwrite the old text. */ nlen = STRLEN(cmd.argv[0]->bp); break; 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; } /* Escape the matched part of the path. */ if (fstwd) bp = cmd.argv[0]->bp; else { if ((bp = argv_esc(sp, &cmd, cmd.argv[0]->bp, nlen)) == NULL) return (1); nlen = STRLEN(bp); } /* Overwrite the expanded text first. */ for (t = 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_RETW(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 (!fstwd) FREE_SPACEW(sp, bp, 0); /* If not a single match of path, we've done. */ if (argc != 1 || fstwd) return (0); /* If a single match and it's a directory, append a '/'. */ INT2CHAR(sp, cmd.argv[0]->bp, cmd.argv[0]->len + 1, np, nplen); if ((epd = expanduser(np)) != NULL) np = epd; if (!stat(np, &sb) && S_ISDIR(sb.st_mode)) { if (tp->owrite == 0) { off = p - tp->lb; BINC_RETW(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++ = '/'; } free(epd); return (0); } /* * txt_fc_col -- * Display file names for file name completion. */ static int txt_fc_col(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; char *np, *pp; size_t nlen; gp = sp->gp; /* Trim any directory prefix common to all of the files. */ INT2CHAR(sp, argv[0]->bp, argv[0]->len + 1, np, nlen); if ((pp = strrchr(np, '/')) == NULL) prefix = 0; else { prefix = (pp - np) + 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_COL(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) { for (ac = argc, av = argv; ac > 0; --ac, ++av) { INT2CHAR(sp, av[0]->bp+prefix, av[0]->len+1-prefix, np, nlen); pp = msg_print(sp, np, &nf); (void)ex_printf(sp, "%s\n", pp); if (nf) FREE_SPACE(sp, pp, 0); if (F_ISSET(gp, G_INTERRUPTED)) break; } 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) { INT2CHAR(sp, argv[base]->bp+prefix, argv[base]->len+1-prefix, np, nlen); pp = msg_print(sp, np, &nf); cnt = ex_printf(sp, "%s", pp); if (nf) FREE_SPACE(sp, pp, 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(SCR *sp, TEXT *tp, size_t cno) { CHAR_T ch; u_char *kp; size_t chlen, nlen, olen; CHAR_T *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_COL(sp, ch); if (tp->lb[cno] == '\t') (void)vs_columns(sp, tp->lb, tp->lno, &cno, &olen); else olen = KEY_COL(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_RETW(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' || KEY_NEEDSWIDE(sp, tp->lb[cno])) for (cno += chlen; chlen--;) *p++ = ' '; else for (kp = (u_char *) 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(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 = TAILQ_FIRST(tiqh)->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(SCR *sp, TEXT *tp) { CHAR_T savec; size_t len, off; u_long value; CHAR_T *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 > UCHAR_MAX) { 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 "iabcd0C", 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 * "iabcd0R", the "bcd" disappears, and magically reappears * on the second 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(SCR *sp, TEXT *tp, CHAR_T *chp, u_int flags) { u_char *kp; CHAR_T savech; size_t chlen, cno, copydown, olen, nlen; CHAR_T *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_COL(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_COL(sp, tp->lb[cno]); if (olen == nlen) { nlen = 0; break; } if (olen < nlen) { ++copydown; nlen -= olen; } else { BINC_RETW(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' || KEY_NEEDSWIDE(sp, tp->lb[cno])) for (p = tp->lb + cno + 1; chlen--;) *p++ = ' '; else for (kp = (u_char *) 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_RETW(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(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 (IS_SHELLMETA(sp, tp->lb[tp->cno - 1]) && (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(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 = TAILQ_FIRST(tiqh); 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 = TAILQ_NEXT(tp, q)) != NULL; ++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(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 = STRCHR(VIP(sp)->mcs, endc = cs.cs_ch)[-1]; /* 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(SCR *sp, TEXT *tp, TEXT *wmtp, int *didbreak, u_int32_t flags) { VI_PRIVATE *vip; size_t len, off; CHAR_T *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(SCR *sp, TEXTH *tiqh, TEXT *tp, const size_t orig_len) { TEXT *ttp; size_t input_len, retain; CHAR_T *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 /s. */ for (ttp = TAILQ_FIRST(tiqh), input_len = 0;;) { input_len += ttp == tp ? tp->cno : ttp->len + ttp->R_erase; if ((ttp = TAILQ_NEXT(ttp, q)) == NULL) 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, TAILQ_FIRST(tiqh)->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(SCR *sp) { msgq(sp, M_BERR, "194|No more characters to erase"); } Index: vendor/nvi/dist/vi/vi.c =================================================================== --- vendor/nvi/dist/vi/vi.c (revision 366306) +++ vendor/nvi/dist/vi/vi.c (revision 366307) @@ -1,1240 +1,1240 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #include #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(SCR *, VICMD *, VIKEYS const *); static gcret_t v_cmd(SCR *, VICMD *, VICMD *, VICMD *, int *, int *); static int v_count(SCR *, ARG_CHAR_T, u_long *); static void v_dtoh(SCR *); static int v_init(SCR *); static gcret_t v_key(SCR *, int, EVENT *, u_int32_t); static int v_motion(SCR *, VICMD *, VICMD *, int *); #if defined(DEBUG) && defined(COMLOG) static void v_comlog(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(SCR **); */ int vi(SCR **spp) { GS *gp; MARK abs; SCR *next, *sp; VICMD cmd = { 0 }, *vp; VI_PRIVATE *vip; int comcount, mapped, rval; /* Get the first screen. */ sp = *spp; gp = sp->gp; /* Point to the command structure. */ vp = &cmd; /* 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 characters at the beginning of * a map wasn't considered an error -- in fact, users would * put leading 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: 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); gp->scr_discard(sp, NULL); break; } } if (0) ret: rval = 1; return (rval); } -#define KEY(key, ec_flags) { \ +#define KEY(key, ec_flags) do { \ 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; \ -} +} while (0) /* * 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( SCR *sp, VICMD *dp, VICMD *vp, VICMD *ismotion, /* Previous key if getting motion component. */ int *comcountp, int *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. * * 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 an 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 an 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 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 , 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); } /* Pick up required trailing character. */ if (LF_ISSET(V_CHAR)) KEY(vp->character, 0); /* Get any associated cursor word. */ if (F_ISSET(kp, V_KEYW) && v_curword(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( SCR *sp, VICMD *dm, VICMD *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" or "!" 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(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", (u_int)sp->t_rows); } sp->t_maxrows = sp->rows - 1; } else sp->t_maxrows = 1; sp->roff = sp->coff = 0; /* Create a screen map. */ CALLOC_RET(sp, HMAP, 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(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 = TAILQ_FIRST(gp->dq)) != NULL; ++hidden) { free(_HMAP(tsp)); _HMAP(tsp) = NULL; TAILQ_REMOVE(gp->dq, tsp, q); TAILQ_INSERT_TAIL(gp->hq, tsp, q); /* XXXX Change if hidden screens per window */ gp->scr_discard(tsp, NULL); } /* Move current screen back to the display queue. */ TAILQ_REMOVE(gp->hq, sp, q); TAILQ_INSERT_TAIL(gp->dq, sp, q); if (hidden > 1) msgq(sp, M_INFO, "319|%d screens backgrounded; use :display to list them", hidden - 1); } /* * v_curword -- * Get the word (tagstring, actually) the cursor is on. * * PUBLIC: int v_curword(SCR *); */ int v_curword(SCR *sp) { VI_PRIVATE *vip; size_t beg, end, len; int moved; CHAR_T *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. * * !!! * Historically, vi accepted any non-blank as initial character * when building up a tagstring. Required by IEEE 1003.1-2001. */ for (end = beg; ++end < len && inword(p[end]);); vip = VIP(sp); vip->klen = len = (end - beg); BINC_RETW(sp, vip->keyw, vip->keywlen, len+1); 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( 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( 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( 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); 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( SCR *sp, VICMD *vp) { TRACE(sp, "vcmd: "WC, vp->key); if (F_ISSET(vp, VC_BUFFER)) TRACE(sp, " buffer: "WC, 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 Index: vendor/nvi/dist/vi/vs_line.c =================================================================== --- vendor/nvi/dist/vi/vs_line.c (revision 366306) +++ vendor/nvi/dist/vi/vs_line.c (revision 366307) @@ -1,534 +1,536 @@ /*- * 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" #include #include #include #include #include #include #include #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(SCR *, SMAP *, size_t *, size_t *); */ int vs_line(SCR *sp, SMAP *smp, size_t *yp, size_t *xp) { u_char *kp; GS *gp; SMAP *tsmp; size_t chlen = 0, 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 dne, is_cached, is_partial, is_tab, no_draw; int list_tab, list_dollar; CHAR_T *p; CHAR_T *cbp, *ecbp, cbuf[128]; ARG_CHAR_T ch = '\0'; #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. */ no_draw = 0; if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1) no_draw = 1; if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp)) no_draw = 1; /* * 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 || no_draw)) 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((char*)cbuf, sizeof(cbuf), O_NUMBER_FMT, (u_long)smp->lno); (void)gp->scr_addstr(sp, (char*)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 || no_draw) 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 (skip_cols == 0) { if (dne) { if (smp->lno == 1) { if (list_dollar) { ch = '$'; goto empty; } } else { ch = '~'; goto empty; } - } else + } 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 shortened this line in another screen, the cursor * position may have fallen off. */ if (sp->lno == smp->lno && sp->cno >= len) sp->cno = len - 1; /* * 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 = *p++) == '\t' && !list_tab ? TAB_OFF(scno) : KEY_COL(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 (offset_in_line >= len) { smp->c_sboff = offset_in_line; smp->c_scoff = 255; } else 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 = *p++) == '\t' && !list_tab ? TAB_OFF(scno) : KEY_COL(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 || no_draw) goto ret1; } else cno_cnt = (sp->cno - offset_in_line) + 1; /* This is the loop that actually displays characters. */ ecbp = (cbp = cbuf) + SIZE(cbuf) - 1; for (is_partial = 0, scno = 0; offset_in_line < len; ++offset_in_line, offset_in_char = 0) { if ((ch = *p++) == '\t' && !list_tab) { scno += chlen = TAB_OFF(scno) - offset_in_char; is_tab = 1; } else { scno += chlen = KEY_COL(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)) if (is_partial) *xp = scno - smp->c_ecsize; else *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 || no_draw) goto ret1; } /* If the line is on the screen, don't display anything. */ if (is_cached || no_draw) continue; -#define FLUSH { \ +#define FLUSH do { \ *cbp = '\0'; \ (void)gp->scr_waddstr(sp, cbuf, cbp - cbuf); \ cbp = cbuf; \ -} +} while (0) /* * 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; /* don't display half a wide character */ if (is_partial && CHAR_WIDTH(sp, ch) > 1) { *cbp++ = ' '; break; } if (KEY_NEEDSWIDE(sp, ch)) *cbp++ = ch; else for (kp = (u_char *) 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_COL(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 = (u_char *) 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(SCR *); */ int vs_number(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, (u_long)smp->lno); (void)gp->scr_addstr(sp, nbuf, len); } (void)gp->scr_move(sp, oldy, oldx); return (0); } Index: vendor/nvi/dist/vi/vs_msg.c =================================================================== --- vendor/nvi/dist/vi/vs_msg.c (revision 366306) +++ vendor/nvi/dist/vi/vs_msg.c (revision 366307) @@ -1,895 +1,898 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #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(SCR *); static void vs_msgsave(SCR *, mtype_t, char *, size_t); static void vs_output(SCR *, mtype_t, const char *, int); static void vs_scroll(SCR *, int *, sw_t); static void vs_wait(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(SCR *, const char *, busy_t); */ void vs_busy(SCR *sp, const char *msg, busy_t btype) { GS *gp; VI_PRIVATE *vip; static const char flagc[] = "|/-\\"; struct timespec ts, ts_diff; const struct timespec ts_min = { 0, 125000000 }; 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; timepoint_steady(&vip->busy_ts); /* 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. */ timepoint_steady(&ts); ts_diff = ts; timespecsub(&ts_diff, &vip->busy_ts); if (timespeccmp(&ts_diff, &ts_min, <)) return; vip->busy_ts = ts; /* 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(SCR *); */ void vs_home(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(SCR *, const char *, const CHAR_T *); */ void vs_update(SCR *sp, const char *m1, const CHAR_T *m2) { GS *gp; size_t len, mlen, oldx, oldy; CONST char *np; size_t nlen; 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)) { if (m2 != NULL) INT2CHAR(sp, m2, STRLEN(m2) + 1, np, nlen); (void)ex_printf(sp, "%s\n", m1 == NULL? "" : m1, m2 == NULL ? "" : np); (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_waddstr(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(SCR *, mtype_t, char *, size_t); */ void vs_msg(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_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_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 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 . * 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 (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((u_char)*s); --len, ++s); if (len == 0) break; if (len + vip->lcontinue > maxcols) { for (e = s + (maxcols - vip->lcontinue); e > s && !isblank((u_char)*e); --e); if (e == s) e = t = s + (maxcols - vip->lcontinue); else for (t = e; isblank((u_char)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(SCR *sp, mtype_t mtype, const char *line, int llen) { GS *gp; VI_PRIVATE *vip; size_t notused; int 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 { \ +#define FLUSH do { \ *cbp = '\0'; \ (void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \ cbp = cbuf; \ -} +} while (0) ecbp = (cbp = cbuf) + sizeof(cbuf) - 1; for (t = line, tlen = len; tlen--; ++t) { /* * Replace tabs with spaces, there are places in * ex that do column calculations without looking * at -- and all routines that care about * do their own expansions. This catches * in things like tag search strings. */ if (cbp + 1 >= ecbp) FLUSH; *cbp++ = *t == '\t' ? ' ' : *t; } 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(SCR *, int *); */ int vs_ex_resolve(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 (TAILQ_NEXT(sp, q) != NULL) 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(SCR *, SCR *, int); */ int vs_resolve(SCR *sp, SCR *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 (!SLIST_EMPTY(gp->msgq)) { if (!F_ISSET(sp, SC_SCR_VI) && vs_refresh(sp, 1)) return (1); while ((mp = SLIST_FIRST(gp->msgq)) != NULL) { gp->scr_msg(sp, mp->mtype, mp->buf, mp->len); SLIST_REMOVE_HEAD(gp->msgq, 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(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 (TAILQ_NEXT(sp, q) != NULL) { (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(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(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(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, 1, sizeof(MSGS)); MALLOC_GOTO(sp, mp_n->buf, len); memmove(mp_n->buf, p, len); mp_n->len = len; mp_n->mtype = mt; gp = sp->gp; if (SLIST_EMPTY(gp->msgq)) { SLIST_INSERT_HEAD(gp->msgq, mp_n, q); } else { SLIST_FOREACH(mp_c, gp->msgq, q) if (SLIST_NEXT(mp_c, q) == NULL) break; SLIST_INSERT_AFTER(mp_c, mp_n, q); } return; alloc_err: free(mp_n); (void)fprintf(stderr, "%.*s\n", (int)len, p); } Index: vendor/nvi/dist/vi/vs_refresh.c =================================================================== --- vendor/nvi/dist/vi/vs_refresh.c (revision 366306) +++ vendor/nvi/dist/vi/vs_refresh.c (revision 366307) @@ -1,883 +1,886 @@ /*- * 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" #include #include #include #include #include #include #include #include #include #include #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(SCR *); static int vs_paint(SCR *, u_int); /* * v_repaint -- * Repaint selected lines from the screen. * * PUBLIC: int vs_repaint(SCR *, EVENT *); */ int vs_repaint( 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(SCR *, int); */ int vs_refresh( SCR *sp, int forcepaint) { GS *gp; SCR *tsp; int need_refresh = 0; 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)) TAILQ_FOREACH(tsp, gp->dq, q) 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; TAILQ_FOREACH(tsp, gp->dq, q) 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. */ TAILQ_FOREACH(tsp, gp->dq, q) 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( 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 = 0, didpaint, isempty, leftright_warp; CHAR_T *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 (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; } else goto top; /* XXX No such line. */ /* * 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 (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 (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 (%zu) >= len (%zu)", basename(__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 slow way. */ for (cwtotal = 0; cnt--; cwtotal += KEY_COL(sp, ch)) if ((ch = *(UCHAR_T *)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_COL(sp, ch) > 1) cwtotal -= KEY_COL(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 slow way. If we cross a * screen boundary, we can quit. */ for (cwtotal = SCNO; cnt--;) { if ((ch = *(UCHAR_T *)p++) == '\t') goto slow; if ((cwtotal += KEY_COL(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(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 = NULL; int ellipsis; char 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)) { CHAR_T *wp, *p; size_t l; CHAR2INT(sp, sp->frp->name, strlen(sp->frp->name) + 1, wp, l); p = wp + l; for (ellipsis = 0, cols = sp->cols / 2; --p > wp;) { if (*p == '/') { ++p; break; } if ((curlen += KEY_COL(sp, *p)) > cols) { ellipsis = 3; curlen += KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' '); while (curlen > cols) { ++p; curlen -= KEY_COL(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_COL(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", (u_long)sp->lno, (u_long)(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); } } } Index: vendor/nvi/dist/vi/vs_relative.c =================================================================== --- vendor/nvi/dist/vi/vs_relative.c (revision 366306) +++ vendor/nvi/dist/vi/vs_relative.c (revision 366307) @@ -1,290 +1,291 @@ /*- * 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" #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * vs_column -- * Return the logical column of the cursor in the line. * * PUBLIC: int vs_column(SCR *, size_t *); */ int vs_column(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(SCR *, recno_t, size_t *); */ size_t vs_screens(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(SCR *, CHAR_T *, recno_t, size_t *, size_t *); */ size_t vs_columns(SCR *sp, CHAR_T *lp, recno_t lno, size_t *cnop, size_t *diffp) { size_t chlen, cno, curoff, last = 0, len, scno; int ch, leftright, listset; CHAR_T *p; /* * Initialize the screen offset. */ scno = 0; /* Leading number if O_NUMBER option set. */ if (O_ISSET(sp, O_NUMBER)) scno += O_NUMBER_LENGTH; /* 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 scno; } /* 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 current offset. */ p = lp; curoff = scno; /* Macro to return the display length of any signal character. */ #define CHLEN(val) (ch = *(UCHAR_T *)p++) == '\t' && \ !listset ? TAB_OFF(val) : KEY_COL(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 { \ +#define TAB_RESET do { \ curoff += chlen; \ - if (!leftright && curoff >= sp->cols) \ + if (!leftright && curoff >= sp->cols) { \ if (ch == '\t') { \ curoff = 0; \ scno -= scno % sp->cols; \ } else \ curoff -= sp->cols; \ -} + } \ +} while (0) 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(SCR *, recno_t, int); */ size_t vs_rcm(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(SCR *, recno_t, size_t); */ size_t vs_colpos(SCR *sp, recno_t lno, size_t cno) { size_t chlen, curoff, len, llen, off, scno; int ch = 0, leftright, listset; CHAR_T *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); } Index: vendor/nvi/dist/vi/vs_smap.c =================================================================== --- vendor/nvi/dist/vi/vs_smap.c (revision 366306) +++ vendor/nvi/dist/vi/vs_smap.c (revision 366307) @@ -1,1238 +1,1238 @@ /*- * 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" #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int vs_deleteln(SCR *, int); static int vs_insertln(SCR *, int); static int vs_sm_delete(SCR *, recno_t); static int vs_sm_down(SCR *, MARK *, recno_t, scroll_t, SMAP *); static int vs_sm_erase(SCR *); static int vs_sm_insert(SCR *, recno_t); static int vs_sm_reset(SCR *, recno_t); static int vs_sm_up(SCR *, MARK *, recno_t, scroll_t, SMAP *); /* * vs_change -- * Make a change to the screen. * * PUBLIC: int vs_change(SCR *, recno_t, lnop_t); */ int vs_change(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); if (sp->lno > lno) --sp->lno; F_SET(vip, VIP_N_RENUMBER); break; case LINE_INSERT: if (vs_sm_insert(sp, lno)) return (1); if (sp->lno > lno) ++sp->lno; 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(SCR *, recno_t, pos_t); */ int vs_sm_fill(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; } else { /* * If number of lines HMAP->lno (top line) spans * changed due to, say reformatting, and now is * fewer than HMAP->soff, reset so the line is * redrawn at the top of the screen. */ cnt = vs_screens(sp, HMAP->lno, NULL); if (cnt < HMAP->soff) 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) { \ +#define HANDLE_WEIRDNESS(cnt) do { \ if (cnt >= sp->t_rows) { \ F_SET(sp, SC_SCR_REFORMAT); \ return (0); \ } \ -} +} while (0) /* * vs_sm_delete -- * Delete a line out of the SMAP. */ static int vs_sm_delete(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(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(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(SCR *, MARK *, recno_t, scroll_t); */ int vs_sm_scroll(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(SCR *sp, MARK *rp, recno_t count, scroll_t scmd, 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_scoff == 255 ? 0 : smp->c_sboff; return (0); } /* * vs_sm_1up -- * Scroll the SMAP up one. * * PUBLIC: int vs_sm_1up(SCR *); */ int vs_sm_1up(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(SCR *sp, int cnt) { GS *gp; size_t oldy, oldx; gp = sp->gp; /* If the screen is vertically split, we can't scroll it. */ if (IS_VSPLIT(sp)) { F_SET(sp, SC_SCR_REDRAW); return (0); } 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(SCR *sp, MARK *rp, recno_t count, scroll_t scmd, SMAP *smp) { 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_scoff == 255 ? 0 : smp->c_sboff; return (0); } /* * vs_sm_erase -- * Erase the small screen area for the scrolling functions. */ static int vs_sm_erase(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(SCR *); */ int vs_sm_1down(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(SCR *sp, int cnt) { GS *gp; size_t oldy, oldx; gp = sp->gp; /* If the screen is vertically split, we can't scroll it. */ if (IS_VSPLIT(sp)) { F_SET(sp, SC_SCR_REDRAW); return (0); } 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(SCR *, SMAP *, SMAP *); */ int vs_sm_next(SCR *sp, SMAP *p, SMAP *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(SCR *, SMAP *, SMAP *); */ int vs_sm_prev(SCR *sp, SMAP *p, SMAP *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(SCR *, SMAP **); */ int vs_sm_cursor(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(SCR *, MARK *, u_long, pos_t); */ int vs_sm_position(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(SCR *, SMAP *, recno_t, size_t); */ recno_t vs_sm_nlines(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); }