diff --git a/.gitignore b/.gitignore index aac7860d7299..2b79229e57e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,14 @@ *.swp *~ *.orig *.core extern.h *_def.h version.h tags build/ + +# Ignore files by the GNU Global source code tagging system. +/GPATH +/GRTAGS +/GTAGS diff --git a/CMakeLists.txt b/CMakeLists.txt index 996e0e72de99..66d3ca2aafb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,210 +1,242 @@ 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(CheckSymbolExists) include(CheckStructHasMember) include(CheckCSourceCompiles) +include(CheckCCompilerFlag) 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) +check_c_compiler_flag(-fcolor-diagnostics USE_FCOLOR_DIAGNOSTICS) +if(USE_FCOLOR_DIAGNOSTICS) + add_compile_options(-fcolor-diagnostics) +endif() + add_compile_options($<$:-Wall>) add_compile_options($<$:-Wno-parentheses>) add_compile_options($<$:-Wno-uninitialized>) add_compile_options($<$:-Wmissing-prototypes>) -add_compile_options($<$:-Wsystem-headers>) +if (NOT APPLE) + add_compile_options($<$:-Wsystem-headers>) +endif() 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() +check_symbol_exists(asprintf "stdio.h" ASPRINTF_IN_STDIO_H) +if(NOT ASPRINTF_IN_STDIO_H) + target_compile_definitions(nvi PRIVATE _GNU_SOURCE) +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} ${TERMINFO_LIBRARY}) +target_link_libraries(nvi PRIVATE ${CURSES_LIBRARY}) +if(TERMINFO_LIBRARY) + target_link_libraries(nvi PRIVATE ${TERMINFO_LIBRARY}) +endif() if(USE_ICONV) 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() +if (APPLE) + # Avoid using an incompatible db.h installed to /usr/local (since this is + # part of the default search path on macOS) + set(DB_H_GUESS "${CMAKE_OSX_SYSROOT}/usr/include/db.h") + if (NOT EXISTS ${DB_H_GUESS}) + message(FATAL_ERROR "Could not find db.h at the expected path (${DB_H_GUESS}).") + endif() + add_definitions("-DDB_H_ABS_PATH=<${DB_H_GUESS}>") +else() + find_path(DB_INCLUDE_DIR db.h PATH_SUFFIXES db1) + target_include_directories(nvi PRIVATE ${DB_INCLUDE_DIR}) +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) +check_struct_has_member("struct stat" st_mtimespec + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_MTIMESPEC LANGUAGE C) +check_struct_has_member("struct stat" st_mtim + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_MTIM 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) diff --git a/catalog/dump.c b/catalog/dump.c index 248f718d719b..ef893d647edf 100644 --- a/catalog/dump.c +++ b/catalog/dump.c @@ -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) do { \ +#define TESTD(s) { \ if ((s = getc(fp)) == EOF) \ return; \ if (!isdigit(s)) \ continue; \ -} while (0) -#define TESTP do { \ +} +#define TESTP { \ if ((ch = getc(fp)) == EOF) \ return; \ if (ch != '|') \ continue; \ -} while (0) -#define MOVEC(t) do { \ +} +#define MOVEC(t) { \ 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); } diff --git a/cl/cl.h b/cl/cl.h index d230a55a56d7..a04c9e9f1b0d 100644 --- a/cl/cl.h +++ b/cl/cl.h @@ -1,83 +1,80 @@ /*- * 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_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" diff --git a/cl/cl_read.c b/cl/cl_read.c index ddf3acc1918c..8dc1d3cbebc9 100644 --- a/cl/cl_read.c +++ b/cl/cl_read.c @@ -1,327 +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); + a.len = SPRINTF(b1, SIZE(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); + a.len = SPRINTF(b1, SIZE(b1), L("columns=%lu"), (u_long)columns); if (opts_set(sp, argv, NULL)) return (1); return (0); } diff --git a/cl/extern.h b/cl/extern.h deleted file mode 100644 index 7b01ccd3f8cf..000000000000 --- a/cl/extern.h +++ /dev/null @@ -1,31 +0,0 @@ -int cl_waddstr(SCR *, const CHAR_T *, size_t); -int cl_addstr(SCR *, const char *, size_t); -int cl_attr(SCR *, scr_attr_t, int); -int cl_baud(SCR *, u_long *); -int cl_bell(SCR *); -int cl_clrtoeol(SCR *); -int cl_cursor(SCR *, size_t *, size_t *); -int cl_deleteln(SCR *); -int cl_discard(SCR *, SCR **); -int cl_ex_adjust(SCR *, exadj_t); -int cl_insertln(SCR *); -int cl_keyval(SCR *, scr_keyval_t, CHAR_T *, int *); -int cl_move(SCR *, size_t, size_t); -int cl_refresh(SCR *, int); -int cl_rename(SCR *, char *, int); -void cl_setname(GS *, char *); -int cl_split(SCR *, SCR *); -int cl_suspend(SCR *, int *); -void cl_usage(void); -int sig_init(GS *, SCR *); -int cl_event(SCR *, EVENT *, u_int32_t, int); -int cl_screen(SCR *, u_int32_t); -int cl_quit(GS *); -int cl_getcap(SCR *, char *, char **); -int cl_term_init(SCR *); -int cl_term_end(GS *); -int cl_fmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t); -int cl_optchange(SCR *, int, char *, u_long *); -int cl_omesg(SCR *, CL_PRIVATE *, int); -int cl_ssize(SCR *, int, size_t *, size_t *, int *); -int cl_putchar(int); diff --git a/common/common.h b/common/common.h index dc4155610225..45f22fb49d1b 100644 --- a/common/common.h +++ b/common/common.h @@ -1,94 +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. */ +#ifdef DB_H_ABS_PATH +#include DB_H_ABS_PATH #else -#include "/usr/include/db.h" /* Only include db1. */ +#include #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" diff --git a/common/conv.h b/common/conv.h index ee3efb5a8308..4daa8221ff8f 100644 --- a/common/conv.h +++ b/common/conv.h @@ -1,55 +1,55 @@ /*- * 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. * Copyright (c) 2011, 2012 * Zhihao Yuan. All rights reserved. * * See the LICENSE file for redistribution information. */ #ifdef USE_ICONV #include #ifdef ICONV_TRADITIONAL typedef char ** iconv_src_t; #else typedef char const ** iconv_src_t; #endif #else typedef int iconv_t; #endif /* * XXX * We can not use MB_CUR_MAX here, since UTF-8 may report it as 6, but * a sequence longer than 4 is deprecated by RFC 3629. */ #define KEY_NEEDSWIDE(sp, ch) \ (INTISWIDE(ch) && KEY_LEN(sp, ch) <= 4) #define KEY_COL(sp, ch) \ - (KEY_NEEDSWIDE(sp, ch) ? CHAR_WIDTH(sp, ch) : KEY_LEN(sp, ch)) + (KEY_NEEDSWIDE(sp, ch) ? XCHAR_WIDTH(sp, ch) : KEY_LEN(sp, ch)) enum { IC_FE_CHAR2INT, IC_FE_INT2CHAR, IC_IE_CHAR2INT, IC_IE_TO_UTF16 }; struct _conv_win { union { char *c; CHAR_T *wc; } bp1; size_t blen1; }; typedef int (*char2wchar_t) (SCR *, const char *, ssize_t, struct _conv_win *, size_t *, CHAR_T **); typedef int (*wchar2char_t) (SCR *, const CHAR_T *, ssize_t, struct _conv_win *, size_t *, char **); struct _conv { char2wchar_t sys2int; wchar2char_t int2sys; char2wchar_t file2int; wchar2char_t int2file; char2wchar_t input2int; iconv_t id[IC_IE_TO_UTF16 + 1]; }; diff --git a/common/exf.c b/common/exf.c index f9eb2150276d..49e39c242648 100644 --- a/common/exf.c +++ b/common/exf.c @@ -1,1479 +1,1506 @@ /*- * 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); +#if defined HAVE_STRUCT_STAT_ST_MTIMESPEC + ep->mtim = sb.st_mtimespec; +#elif defined HAVE_STRUCT_STAT_ST_MTIM ep->mtim = sb.st_mtim; +#else + ep->mtim.tv_sec = sb.st_mtime; + ep->mtim.tv_nsec = 0; +#endif } 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; +#if defined HAVE_STRUCT_STAT_ST_MTIMESPEC + ep->mtim = sb.st_mtimespec; +#elif defined HAVE_STRUCT_STAT_ST_MTIM ep->mtim = sb.st_mtim; +#else + ep->mtim.tv_sec = sb.st_mtime; + ep->mtim.tv_nsec = 0; +#endif 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)) || +#if defined HAVE_STRUCT_STAT_ST_MTIMESPEC + timespeccmp(&sb.st_mtimespec, &ep->mtim, !=))) { +#elif defined HAVE_STRUCT_STAT_ST_MTIM timespeccmp(&sb.st_mtim, &ep->mtim, !=))) { +#else + sb.st_mtime != ep->mtim.tv_sec)) { +#endif 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 (stat(name, &sb)) timepoint_system(&ep->mtim); else { F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; +#if defined HAVE_STRUCT_STAT_ST_MTIMESPEC + ep->mtim = sb.st_mtimespec; +#elif defined HAVE_STRUCT_STAT_ST_MTIM ep->mtim = sb.st_mtim; +#else + ep->mtim.tv_sec = sb.st_mtime; + ep->mtim.tv_nsec = 0; +#endif } } /* * 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 (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 (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); } diff --git a/common/extern.h b/common/extern.h deleted file mode 100644 index c887696080de..000000000000 --- a/common/extern.h +++ /dev/null @@ -1,131 +0,0 @@ -char * codeset(void); -void conv_init(SCR *, SCR *); -int conv_enc(SCR *, int, char *); -void conv_end(SCR *); -int cut(SCR *, CHAR_T *, MARK *, MARK *, int); -int cut_line(SCR *, recno_t, size_t, size_t, CB *); -void cut_close(GS *); -TEXT *text_init(SCR *, const CHAR_T *, size_t, size_t); -void text_lfree(TEXTH *); -void text_free(TEXT *); -int del(SCR *, MARK *, MARK *, int); -int looks_utf8(const char *, size_t); -int looks_utf16(const char *, size_t); -int decode_utf8(const char *); -int decode_utf16(const char *, int); -FREF *file_add(SCR *, char *); -int file_init(SCR *, FREF *, char *, int); -int file_end(SCR *, EXF *, int); -int file_write(SCR *, MARK *, MARK *, char *, int); -int file_m1(SCR *, int, int); -int file_m2(SCR *, int); -int file_m3(SCR *, int); -int file_aw(SCR *, int); -void set_alt_name(SCR *, char *); -lockr_t file_lock(SCR *, char *, int, int); -int v_key_init(SCR *); -void v_key_ilookup(SCR *); -size_t v_key_len(SCR *, ARG_CHAR_T); -char *v_key_name(SCR *, ARG_CHAR_T); -e_key_t v_key_val(SCR *, ARG_CHAR_T); -int v_event_push(SCR *, EVENT *, CHAR_T *, size_t, u_int); -int v_event_get(SCR *, EVENT *, int, u_int32_t); -void v_event_err(SCR *, EVENT *); -int v_event_flush(SCR *, u_int); -int db_eget(SCR *, recno_t, CHAR_T **, size_t *, int *); -int db_get(SCR *, recno_t, u_int32_t, CHAR_T **, size_t *); -int db_delete(SCR *, recno_t); -int db_append(SCR *, int, recno_t, CHAR_T *, size_t); -int db_insert(SCR *, recno_t, CHAR_T *, size_t); -int db_set(SCR *, recno_t, CHAR_T *, size_t); -int db_exist(SCR *, recno_t); -int db_last(SCR *, recno_t *); -int db_rget(SCR *, recno_t, char **, size_t *); -int db_rset(SCR *, recno_t, char *, size_t); -void db_err(SCR *, recno_t); -int log_init(SCR *, EXF *); -int log_end(SCR *, EXF *); -int log_cursor(SCR *); -int log_line(SCR *, recno_t, u_int); -int log_mark(SCR *, LMARK *); -int log_backward(SCR *, MARK *); -int log_setline(SCR *); -int log_forward(SCR *, MARK *); -int editor(GS *, int, char *[]); -void v_end(GS *); -int mark_init(SCR *, EXF *); -int mark_end(SCR *, EXF *); -int mark_get(SCR *, ARG_CHAR_T, MARK *, mtype_t); -int mark_set(SCR *, ARG_CHAR_T, MARK *, int); -int mark_insdel(SCR *, lnop_t, recno_t); -void msgq(SCR *, mtype_t, const char *, ...); -void msgq_wstr(SCR *, mtype_t, const CHAR_T *, const char *); -void msgq_str(SCR *, mtype_t, const char *, const char *); -void mod_rpt(SCR *); -void msgq_status(SCR *, recno_t, u_int); -int msg_open(SCR *, char *); -void msg_close(GS *); -const char *msg_cmsg(SCR *, cmsg_t, size_t *); -const char *msg_cat(SCR *, const char *, size_t *); -char *msg_print(SCR *, const char *, int *); -int opts_init(SCR *, int *); -int opts_set(SCR *, ARGS *[], char *); -int o_set(SCR *, int, u_int, char *, u_long); -int opts_empty(SCR *, int, int); -void opts_dump(SCR *, enum optdisp); -int opts_save(SCR *, FILE *); -OPTLIST const *opts_search(CHAR_T *); -void opts_nomatch(SCR *, CHAR_T *); -int opts_copy(SCR *, SCR *); -void opts_free(SCR *); -int f_altwerase(SCR *, OPTION *, char *, u_long *); -int f_columns(SCR *, OPTION *, char *, u_long *); -int f_lines(SCR *, OPTION *, char *, u_long *); -int f_lisp(SCR *, OPTION *, char *, u_long *); -int f_msgcat(SCR *, OPTION *, char *, u_long *); -int f_print(SCR *, OPTION *, char *, u_long *); -int f_readonly(SCR *, OPTION *, char *, u_long *); -int f_recompile(SCR *, OPTION *, char *, u_long *); -int f_reformat(SCR *, OPTION *, char *, u_long *); -int f_ttywerase(SCR *, OPTION *, char *, u_long *); -int f_w300(SCR *, OPTION *, char *, u_long *); -int f_w1200(SCR *, OPTION *, char *, u_long *); -int f_w9600(SCR *, OPTION *, char *, u_long *); -int f_window(SCR *, OPTION *, char *, u_long *); -int f_encoding(SCR *, OPTION *, char *, u_long *); -int put(SCR *, CB *, CHAR_T *, MARK *, MARK *, int); -int rcv_tmp(SCR *, EXF *, char *); -int rcv_init(SCR *); -int rcv_sync(SCR *, u_int); -int rcv_list(SCR *); -int rcv_read(SCR *, FREF *); -int screen_init(GS *, SCR *, SCR **); -int screen_end(SCR *); -SCR *screen_next(SCR *); -int f_search(SCR *, - MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int); -int b_search(SCR *, - MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int); -void search_busy(SCR *, busy_t); -int seq_set(SCR *, CHAR_T *, - size_t, CHAR_T *, size_t, CHAR_T *, size_t, seq_t, int); -int seq_delete(SCR *, CHAR_T *, size_t, seq_t); -int seq_free(SEQ *); -SEQ *seq_find - (SCR *, SEQ **, EVENT *, CHAR_T *, size_t, seq_t, int *); -void seq_close(GS *); -int seq_dump(SCR *, seq_t, int); -int seq_save(SCR *, FILE *, char *, seq_t); -int e_memcmp(CHAR_T *, EVENT *, size_t); -void *binc(SCR *, void *, size_t *, size_t); -int nonblank(SCR *, recno_t, size_t *); -char *join(char *, char *); -char *expanduser(char *); -char *quote(char *); -char *v_strdup(SCR *, const char *, size_t); -CHAR_T *v_wstrdup(SCR *, const CHAR_T *, size_t); -enum nresult nget_uslong(u_long *, const CHAR_T *, CHAR_T **, int); -enum nresult nget_slong(long *, const CHAR_T *, CHAR_T **, int); -void timepoint_steady(struct timespec *); -void timepoint_system(struct timespec *); -void TRACE(SCR *, const char *, ...); diff --git a/common/key.h b/common/key.h index 3e55d3044129..ae0ba92d1ce5 100644 --- a/common/key.h +++ b/common/key.h @@ -1,240 +1,240 @@ /*- * 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 "multibyte.h" #ifdef USE_WIDECHAR #define FILE2INT5(sp,buf,n,nlen,w,wlen) \ sp->conv.file2int(sp, n, nlen, &buf, &wlen, &w) #define INT2FILE(sp,w,wlen,n,nlen) \ sp->conv.int2file(sp, w, wlen, &sp->cw, &nlen, &n) #define CHAR2INT5(sp,buf,n,nlen,w,wlen) \ sp->conv.sys2int(sp, n, nlen, &buf, &wlen, &w) #define INT2CHAR(sp,w,wlen,n,nlen) \ sp->conv.int2sys(sp, w, wlen, &sp->cw, &nlen, &n) #define INPUT2INT5(sp,cw,n,nlen,w,wlen) \ sp->conv.input2int(sp, n, nlen, &(cw), &wlen, &w) #define CONST #define INTISWIDE(c) (wctob(c) == EOF) -#define CHAR_WIDTH(sp, ch) wcwidth(ch) -#define CAN_PRINT(sp, ch) (CHAR_WIDTH(sp, ch) > 0) +#define XCHAR_WIDTH(sp, ch) wcwidth(ch) +#define CAN_PRINT(sp, ch) (XCHAR_WIDTH(sp, ch) > 0) #else #define FILE2INT5(sp,buf,n,nlen,w,wlen) \ (w = n, wlen = nlen, 0) #define INT2FILE(sp,w,wlen,n,nlen) \ (n = w, nlen = wlen, 0) #define CHAR2INT5(sp,buf,n,nlen,w,wlen) \ (w = n, wlen = nlen, 0) #define INT2CHAR(sp,w,wlen,n,nlen) \ (n = w, nlen = wlen, 0) #define INPUT2INT5(sp,buf,n,nlen,w,wlen) \ (w = n, wlen = nlen, 0) #define CONST const #define INTISWIDE(c) 0 -#define CHAR_WIDTH(sp, ch) 1 +#define XCHAR_WIDTH(sp, ch) 1 #define CAN_PRINT(sp, ch) isprint(ch) #endif #define FILE2INT(sp,n,nlen,w,wlen) \ FILE2INT5(sp,sp->cw,n,nlen,w,wlen) #define CHAR2INT(sp,n,nlen,w,wlen) \ CHAR2INT5(sp,sp->cw,n,nlen,w,wlen) /* The maximum number of columns any character can take up on a screen. */ #define MAX_CHARACTER_COLUMNS 7 /* * Event types. * * The program structure depends on the event loop being able to return * E_EOF/E_ERR multiple times -- eventually enough things will end due * to the events that vi will reach the command level for the screen, at * which point the exit flags will be set and vi will exit. */ typedef enum { E_NOTUSED = 0, /* Not set. */ E_CHARACTER, /* Input character: e_c set. */ E_EOF, /* End of input (NOT ^D). */ E_ERR, /* Input error. */ E_INTERRUPT, /* Interrupt. */ E_REPAINT, /* Repaint: e_flno, e_tlno set. */ E_SIGHUP, /* SIGHUP. */ E_SIGTERM, /* SIGTERM. */ E_STRING, /* Input string: e_csp, e_len set. */ E_TIMEOUT, /* Timeout. */ E_WRESIZE, /* Window resize. */ } e_event_t; /* * Character values. */ typedef enum { K_NOTUSED = 0, /* Not set. */ K_BACKSLASH, /* \ */ K_CARAT, /* ^ */ K_CNTRLD, /* ^D */ K_CNTRLR, /* ^R */ K_CNTRLT, /* ^T */ K_CNTRLZ, /* ^Z */ K_COLON, /* : */ K_CR, /* \r */ K_ESCAPE, /* ^[ */ K_FORMFEED, /* \f */ K_HEXCHAR, /* ^X */ K_NL, /* \n */ K_RIGHTBRACE, /* } */ K_RIGHTPAREN, /* ) */ K_TAB, /* \t */ K_VERASE, /* set from tty: default ^H */ K_VKILL, /* set from tty: default ^U */ K_VLNEXT, /* set from tty: default ^V */ K_VWERASE, /* set from tty: default ^W */ K_ZERO /* 0 */ } e_key_t; struct _event { TAILQ_ENTRY(_event) q; /* Linked list of events. */ e_event_t e_event; /* Event type. */ union { struct { /* Input character. */ CHAR_T c; /* Character. */ e_key_t value; /* Key type. */ #define CH_ABBREVIATED 0x01 /* Character is from an abbreviation. */ #define CH_MAPPED 0x02 /* Character is from a map. */ #define CH_NOMAP 0x04 /* Do not map the character. */ #define CH_QUOTED 0x08 /* Character is already quoted. */ u_int8_t flags; } _e_ch; #define e_ch _u_event._e_ch /* !!! The structure, not the char. */ #define e_c _u_event._e_ch.c #define e_value _u_event._e_ch.value #define e_flags _u_event._e_ch.flags struct { /* Screen position, size. */ size_t lno1; /* Line number. */ size_t cno1; /* Column number. */ size_t lno2; /* Line number. */ size_t cno2; /* Column number. */ } _e_mark; #define e_lno _u_event._e_mark.lno1 /* Single location. */ #define e_cno _u_event._e_mark.cno1 #define e_flno _u_event._e_mark.lno1 /* Text region. */ #define e_fcno _u_event._e_mark.cno1 #define e_tlno _u_event._e_mark.lno2 #define e_tcno _u_event._e_mark.cno2 struct { /* Input string. */ CHAR_T *asp; /* Allocated string. */ CHAR_T *csp; /* String. */ size_t len; /* String length. */ } _e_str; #define e_asp _u_event._e_str.asp #define e_csp _u_event._e_str.csp #define e_len _u_event._e_str.len } _u_event; }; typedef struct _keylist { e_key_t value; /* Special value. */ int ch; /* Key. */ } KEYLIST; extern KEYLIST keylist[]; /* Return if more keys in queue. */ #define KEYS_WAITING(sp) ((sp)->gp->i_cnt != 0) #define MAPPED_KEYS_WAITING(sp) \ (KEYS_WAITING(sp) && \ F_ISSET(&sp->gp->i_event[sp->gp->i_next].e_ch, CH_MAPPED)) /* * Ex/vi commands are generally separated by whitespace characters. We * can't use the standard isspace(3) macro because it returns true for * characters like ^K in the ASCII character set. The POSIX isblank(3) * has the same problem for non-ASCII locale, so we need a standalone one. */ static __inline int cmdskip(CHAR_T ch) { return ch == ' ' || ch == '\t'; } /* The "standard" tab width, for displaying things to users. */ #define STANDARD_TAB 6 /* Various special characters, messages. */ #define CH_BSEARCH '?' /* Backward search prompt. */ #define CH_CURSOR ' ' /* Cursor character. */ #define CH_ENDMARK '$' /* End of a range. */ #define CH_EXPROMPT ':' /* Ex prompt. */ #define CH_FSEARCH '/' /* Forward search prompt. */ #define CH_HEX '\030' /* Leading hex character. */ #define CH_LITERAL '\026' /* ASCII ^V. */ #define CH_NO 'n' /* No. */ #define CH_NOT_DIGIT 'a' /* A non-isdigit() character. */ #define CH_QUIT 'q' /* Quit. */ #define CH_YES 'y' /* Yes. */ /* * Checking for interrupts means that we look at the bit that gets set if the * screen code supports asynchronous events, and call back into the event code * so that non-asynchronous screens get a chance to post the interrupt. * * INTERRUPT_CHECK is the number of lines "operated" on before checking for * interrupts. */ #define INTERRUPT_CHECK 100 #define INTERRUPTED(sp) \ (F_ISSET((sp)->gp, G_INTERRUPTED) || \ (!v_event_get(sp, NULL, 0, EC_INTERRUPT) && \ F_ISSET((sp)->gp, G_INTERRUPTED))) #define CLR_INTERRUPT(sp) \ F_CLR((sp)->gp, G_INTERRUPTED) /* Flags describing types of characters being requested. */ #define EC_INTERRUPT 0x001 /* Checking for interrupts. */ #define EC_MAPCOMMAND 0x002 /* Apply the command map. */ #define EC_MAPINPUT 0x004 /* Apply the input map. */ #define EC_MAPNODIGIT 0x008 /* Return to a digit. */ #define EC_QUOTED 0x010 /* Try to quote next character */ #define EC_RAW 0x020 /* Any next character. XXX: not used. */ #define EC_TIMEOUT 0x040 /* Timeout to next character. */ /* Flags describing text input special cases. */ #define TXT_ADDNEWLINE 0x00000001 /* Replay starts on a new line. */ #define TXT_AICHARS 0x00000002 /* Leading autoindent chars. */ #define TXT_ALTWERASE 0x00000004 /* Option: altwerase. */ #define TXT_APPENDEOL 0x00000008 /* Appending after EOL. */ #define TXT_AUTOINDENT 0x00000010 /* Autoindent set this line. */ #define TXT_BACKSLASH 0x00000020 /* Backslashes escape characters. */ #define TXT_BEAUTIFY 0x00000040 /* Only printable characters. */ #define TXT_BS 0x00000080 /* Backspace returns the buffer. */ #define TXT_CEDIT 0x00000100 /* Can return TERM_CEDIT. */ #define TXT_CNTRLD 0x00000200 /* Control-D is a command. */ #define TXT_CNTRLT 0x00000400 /* Control-T is an indent special. */ #define TXT_CR 0x00000800 /* CR returns the buffer. */ #define TXT_DOTTERM 0x00001000 /* Leading '.' terminates the input. */ #define TXT_EMARK 0x00002000 /* End of replacement mark. */ #define TXT_EOFCHAR 0x00004000 /* ICANON set, return EOF character. */ #define TXT_ESCAPE 0x00008000 /* Escape returns the buffer. */ #define TXT_FILEC 0x00010000 /* Option: filec. */ #define TXT_INFOLINE 0x00020000 /* Editing the info line. */ #define TXT_MAPINPUT 0x00040000 /* Apply the input map. */ #define TXT_NLECHO 0x00080000 /* Echo the newline. */ #define TXT_NUMBER 0x00100000 /* Number the line. */ #define TXT_OVERWRITE 0x00200000 /* Overwrite characters. */ #define TXT_PROMPT 0x00400000 /* Display a prompt. */ #define TXT_RECORD 0x00800000 /* Record for replay. */ #define TXT_REPLACE 0x01000000 /* Replace; don't delete overwrite. */ #define TXT_REPLAY 0x02000000 /* Replay the last input. */ #define TXT_RESOLVE 0x04000000 /* Resolve the text into the file. */ #define TXT_SEARCHINCR 0x08000000 /* Incremental search. */ #define TXT_SHOWMATCH 0x10000000 /* Option: showmatch. */ #define TXT_TTYWERASE 0x20000000 /* Option: ttywerase. */ #define TXT_WRAPMARGIN 0x40000000 /* Option: wrapmargin. */ diff --git a/common/options.h b/common/options.h index 5ed12d39c342..d723a51217bc 100644 --- a/common/options.h +++ b/common/options.h @@ -1,100 +1,104 @@ /*- * 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. */ /* * Edit option information. Historically, if you set a boolean or numeric * edit option value to its "default" value, it didn't show up in the :set * display, i.e. it wasn't considered "changed". String edit options would * show up as changed, regardless. We maintain a parallel set of values * which are the default values and never consider an edit option changed * if it was reset to the default value. * * Macros to retrieve boolean, integral and string option values, and to * set, clear and test boolean option values. Some options (secure, lines, * columns, terminal type) are global in scope, and are therefore stored * in the global area. The offset in the global options array is stored * in the screen's value field. This is set up when the options are first * initialized. */ #define O_V(sp, o, fld) \ (F_ISSET(&(sp)->opts[(o)], OPT_GLOBAL) ? \ (sp)->gp->opts[(sp)->opts[(o)].o_cur.val].fld : \ (sp)->opts[(o)].fld) /* Global option macros. */ #define OG_CLR(gp, o) ((gp)->opts[(o)].o_cur.val) = 0 #define OG_SET(gp, o) ((gp)->opts[(o)].o_cur.val) = 1 #define OG_STR(gp, o) ((gp)->opts[(o)].o_cur.str) #define OG_VAL(gp, o) ((gp)->opts[(o)].o_cur.val) #define OG_ISSET(gp, o) OG_VAL(gp, o) #define OG_D_STR(gp, o) ((gp)->opts[(o)].o_def.str) #define OG_D_VAL(gp, o) ((gp)->opts[(o)].o_def.val) /* * Flags to o_set(); need explicit OS_STR as can be setting the value to * NULL. */ #define OS_DEF 0x01 /* Set the default value. */ #define OS_NOFREE 0x02 /* Don't free the old string. */ #define OS_STR 0x04 /* Set to string argument. */ #define OS_STRDUP 0x08 /* Copy then set to string argument. */ struct _option { union { u_long val; /* Value or boolean. */ char *str; /* String. */ } o_cur; #define O_CLR(sp, o) o_set(sp, o, 0, NULL, 0) #define O_SET(sp, o) o_set(sp, o, 0, NULL, 1) #define O_STR(sp, o) O_V(sp, o, o_cur.str) #define O_VAL(sp, o) O_V(sp, o, o_cur.val) #define O_ISSET(sp, o) O_VAL(sp, o) union { u_long val; /* Value or boolean. */ char *str; /* String. */ } o_def; #define O_D_CLR(sp, o) o_set(sp, o, OS_DEF, NULL, 0) #define O_D_SET(sp, o) o_set(sp, o, OS_DEF, NULL, 1) #define O_D_STR(sp, o) O_V(sp, o, o_def.str) #define O_D_VAL(sp, o) O_V(sp, o, o_def.val) #define O_D_ISSET(sp, o) O_D_VAL(sp, o) #define OPT_GLOBAL 0x01 /* Option is global. */ #define OPT_SELECTED 0x02 /* Selected for display. */ u_int8_t flags; }; /* List of option names, associated update functions and information. */ struct _optlist { CHAR_T *name; /* Name. */ /* Change function. */ int (*func)(SCR *, OPTION *, char *, u_long *); /* Type of object. */ enum { OPT_0BOOL, OPT_1BOOL, OPT_NUM, OPT_STR } type; #define OPT_ADISP 0x001 /* Always display the option. */ #define OPT_ALWAYS 0x002 /* Always call the support function. */ #define OPT_NDISP 0x004 /* Never display the option. */ #define OPT_NOSAVE 0x008 /* Mkexrc command doesn't save. */ #define OPT_NOSET 0x010 /* Option may not be set. */ #define OPT_NOUNSET 0x020 /* Option may not be unset. */ #define OPT_NOZERO 0x040 /* Option may not be set to 0. */ #define OPT_PAIRS 0x080 /* String with even length. */ u_int8_t flags; }; /* Option argument to opts_dump(). */ enum optdisp { NO_DISPLAY, ALL_DISPLAY, CHANGED_DISPLAY, SELECT_DISPLAY }; /* Options array. */ extern OPTLIST const optlist[]; +#ifdef O_PATH +#undef O_PATH /* bits/fcntl-linux.h may have defined O_PATH. */ +#endif + #include "options_def.h" diff --git a/common/options_def.h b/common/options_def.h deleted file mode 100644 index 54dd6c20c891..000000000000 --- a/common/options_def.h +++ /dev/null @@ -1,84 +0,0 @@ -#define O_ALTWERASE 0 -#define O_AUTOINDENT 1 -#define O_AUTOPRINT 2 -#define O_AUTOWRITE 3 -#define O_BACKUP 4 -#define O_BEAUTIFY 5 -#define O_CDPATH 6 -#define O_CEDIT 7 -#define O_COLUMNS 8 -#define O_COMBINED 9 -#define O_COMMENT 10 -#define O_TMPDIR 11 -#define O_EDCOMPATIBLE 12 -#define O_ERRORBELLS 13 -#define O_ESCAPETIME 14 -#define O_EXPANDTAB 15 -#define O_EXRC 16 -#define O_EXTENDED 17 -#define O_FILEC 18 -#define O_FILEENCODING 19 -#define O_FLASH 20 -#define O_HARDTABS 21 -#define O_ICLOWER 22 -#define O_IGNORECASE 23 -#define O_INPUTENCODING 24 -#define O_KEYTIME 25 -#define O_LEFTRIGHT 26 -#define O_LINES 27 -#define O_LISP 28 -#define O_LIST 29 -#define O_LOCKFILES 30 -#define O_MAGIC 31 -#define O_MATCHCHARS 32 -#define O_MATCHTIME 33 -#define O_MESG 34 -#define O_MODELINE 35 -#define O_MSGCAT 36 -#define O_NOPRINT 37 -#define O_NUMBER 38 -#define O_OCTAL 39 -#define O_OPEN 40 -#define O_OPTIMIZE 41 -#define O_PARAGRAPHS 42 -#define O_PATH 43 -#define O_PRINT 44 -#define O_PROMPT 45 -#define O_READONLY 46 -#define O_RECDIR 47 -#define O_REDRAW 48 -#define O_REMAP 49 -#define O_REPORT 50 -#define O_RULER 51 -#define O_SCROLL 52 -#define O_SEARCHINCR 53 -#define O_SECTIONS 54 -#define O_SECURE 55 -#define O_SHELL 56 -#define O_SHELLMETA 57 -#define O_SHIFTWIDTH 58 -#define O_SHOWMATCH 59 -#define O_SHOWMODE 60 -#define O_SIDESCROLL 61 -#define O_SLOWOPEN 62 -#define O_SOURCEANY 63 -#define O_TABSTOP 64 -#define O_TAGLENGTH 65 -#define O_TAGS 66 -#define O_TERM 67 -#define O_TERSE 68 -#define O_TILDEOP 69 -#define O_TIMEOUT 70 -#define O_TTYWERASE 71 -#define O_VERBOSE 72 -#define O_W1200 73 -#define O_W300 74 -#define O_W9600 75 -#define O_WARN 76 -#define O_WINDOW 77 -#define O_WINDOWNAME 78 -#define O_WRAPLEN 79 -#define O_WRAPMARGIN 80 -#define O_WRAPSCAN 81 -#define O_WRITEANY 82 -#define O_OPTIONCOUNT 83 diff --git a/common/recover.c b/common/recover.c index 120cf4f60b19..cf222bfb5200 100644 --- a/common/recover.c +++ b/common/recover.c @@ -1,940 +1,952 @@ /*- * 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 (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 || +#if defined HAVE_STRUCT_STAT_ST_MTIMESPEC + timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) { +#elif defined HAVE_STRUCT_STAT_ST_MTIM timespeccmp(&rec_mtim, &sb.st_mtim, <)) { +#else + rec_mtim.tv_sec < sb.st_mtime) { +#endif p = recp; t = pathp; recp = recpath; pathp = path; if (p != NULL) { free(p); free(t); } +#if defined HAVE_STRUCT_STAT_ST_MTIMESPEC + rec_mtim = sb.st_mtimespec; +#elif defined HAVE_STRUCT_STAT_ST_MTIM rec_mtim = sb.st_mtim; +#else + rec_mtim.tv_sec = sb.st_mtime; +#endif 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; 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); } diff --git a/ex/ex.c b/ex/ex.c index 343131537afa..fd920a8df9a1 100644 --- a/ex/ex.c +++ b/ex/ex.c @@ -1,2367 +1,2368 @@ /*- * 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]) { 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)) { 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) { 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)) + if (STRLEN(cp->name) >= len && + !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 diff --git a/ex/ex_cscope.c b/ex/ex_cscope.c index 74d7f8af95be..e07aa64df802 100644 --- a/ex/ex_cscope.c +++ b/ex/ex_cscope.c @@ -1,1086 +1,1100 @@ /*- * 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); +#if defined HAVE_STRUCT_STAT_ST_MTIMESPEC + csc->mtim = sb.st_mtimespec; +#elif defined HAVE_STRUCT_STAT_ST_MTIM csc->mtim = sb.st_mtim; +#else + csc->mtim.tv_sec = sb.st_mtime; + csc->mtim.tv_nsec = 0; +#endif /* 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; } 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); +#if defined HAVE_STRUCT_STAT_ST_MTIMESPEC + *isolderp = timespeccmp( + &sb.st_mtimespec, &csc->mtim, <); +#elif defined HAVE_STRUCT_STAT_ST_MTIM *isolderp = timespeccmp( &sb.st_mtim, &csc->mtim, <); +#else + *isolderp = sb.st_mtime < csc->mtim.tv_sec; +#endif 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 ((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); } diff --git a/ex/ex_def.h b/ex/ex_def.h deleted file mode 100644 index 7afb7b19d677..000000000000 --- a/ex/ex_def.h +++ /dev/null @@ -1,76 +0,0 @@ -#define C_SCROLL 0 -#define C_BANG 1 -#define C_HASH 2 -#define C_SUBAGAIN 3 -#define C_STAR 4 -#define C_SHIFTL 5 -#define C_EQUAL 6 -#define C_SHIFTR 7 -#define C_AT 8 -#define C_APPEND 9 -#define C_ABBR 10 -#define C_ARGS 11 -#define C_BG 12 -#define C_CHANGE 13 -#define C_CD 14 -#define C_CHDIR 15 -#define C_COPY 16 -#define C_CSCOPE 17 -#define C_DELETE 18 -#define C_DISPLAY 19 -#define C_EDIT 20 -#define C_EX 21 -#define C_EXUSAGE 22 -#define C_FILE 23 -#define C_FG 24 -#define C_GLOBAL 25 -#define C_HELP 26 -#define C_INSERT 27 -#define C_JOIN 28 -#define C_K 29 -#define C_LIST 30 -#define C_MOVE 31 -#define C_MARK 32 -#define C_MAP 33 -#define C_MKEXRC 34 -#define C_NEXT 35 -#define C_NUMBER 36 -#define C_OPEN 37 -#define C_PRINT 38 -#define C_PRESERVE 39 -#define C_PREVIOUS 40 -#define C_PUT 41 -#define C_QUIT 42 -#define C_READ 43 -#define C_RECOVER 44 -#define C_RESIZE 45 -#define C_REWIND 46 -#define C_SUBSTITUTE 47 -#define C_SCRIPT 48 -#define C_SET 49 -#define C_SHELL 50 -#define C_SOURCE 51 -#define C_STOP 52 -#define C_SUSPEND 53 -#define C_T 54 -#define C_TAG 55 -#define C_TAGNEXT 56 -#define C_TAGPOP 57 -#define C_TAGPREV 58 -#define C_TAGTOP 59 -#define C_UNDO 60 -#define C_UNABBREVIATE 61 -#define C_UNMAP 62 -#define C_V 63 -#define C_VERSION 64 -#define C_VISUAL_EX 65 -#define C_VISUAL_VI 66 -#define C_VIUSAGE 67 -#define C_VSPLIT 68 -#define C_WRITE 69 -#define C_WN 70 -#define C_WQ 71 -#define C_XIT 72 -#define C_YANK 73 -#define C_Z 74 -#define C_SUBTILDE 75 diff --git a/ex/extern.h b/ex/extern.h deleted file mode 100644 index 9d7b1d674c3c..000000000000 --- a/ex/extern.h +++ /dev/null @@ -1,131 +0,0 @@ -int ex(SCR **); -int ex_cmd(SCR *); -int ex_range(SCR *, EXCMD *, int *); -int ex_is_abbrev(CHAR_T *, size_t); -int ex_is_unmap(CHAR_T *, size_t); -void ex_badaddr - (SCR *, EXCMDLIST const *, enum badaddr, enum nresult); -int ex_abbr(SCR *, EXCMD *); -int ex_unabbr(SCR *, EXCMD *); -int ex_append(SCR *, EXCMD *); -int ex_change(SCR *, EXCMD *); -int ex_insert(SCR *, EXCMD *); -int ex_next(SCR *, EXCMD *); -int ex_prev(SCR *, EXCMD *); -int ex_rew(SCR *, EXCMD *); -int ex_args(SCR *, EXCMD *); -char **ex_buildargv(SCR *, EXCMD *, char *); -int argv_init(SCR *, EXCMD *); -int argv_exp0(SCR *, EXCMD *, CHAR_T *, size_t); -int argv_exp1(SCR *, EXCMD *, CHAR_T *, size_t, int); -int argv_exp2(SCR *, EXCMD *, CHAR_T *, size_t); -int argv_exp3(SCR *, EXCMD *, CHAR_T *, size_t); -int argv_flt_ex(SCR *, EXCMD *, CHAR_T *, size_t); -int argv_free(SCR *); -int argv_flt_path(SCR *, EXCMD *, CHAR_T *, size_t); -CHAR_T *argv_esc(SCR *, EXCMD *, CHAR_T *, size_t); -CHAR_T *argv_uesc(SCR *, EXCMD *, CHAR_T *, size_t); -int ex_at(SCR *, EXCMD *); -int ex_bang(SCR *, EXCMD *); -int ex_cd(SCR *, EXCMD *); -int ex_cscope(SCR *, EXCMD *); -int cscope_end(SCR *); -int cscope_display(SCR *); -int cscope_search(SCR *, TAGQ *, TAG *); -int ex_delete(SCR *, EXCMD *); -int ex_display(SCR *, EXCMD *); -int ex_edit(SCR *, EXCMD *); -int ex_equal(SCR *, EXCMD *); -int ex_file(SCR *, EXCMD *); -int ex_filter(SCR *, - EXCMD *, MARK *, MARK *, MARK *, CHAR_T *, enum filtertype); -int ex_global(SCR *, EXCMD *); -int ex_v(SCR *, EXCMD *); -int ex_g_insdel(SCR *, lnop_t, recno_t); -int ex_screen_copy(SCR *, SCR *); -int ex_screen_end(SCR *); -int ex_optchange(SCR *, int, char *, u_long *); -int ex_exrc(SCR *); -int ex_run_str(SCR *, char *, CHAR_T *, size_t, int, int); -int ex_join(SCR *, EXCMD *); -int ex_map(SCR *, EXCMD *); -int ex_unmap(SCR *, EXCMD *); -int ex_mark(SCR *, EXCMD *); -int ex_mkexrc(SCR *, EXCMD *); -int ex_copy(SCR *, EXCMD *); -int ex_move(SCR *, EXCMD *); -int ex_open(SCR *, EXCMD *); -int ex_preserve(SCR *, EXCMD *); -int ex_recover(SCR *, EXCMD *); -int ex_list(SCR *, EXCMD *); -int ex_number(SCR *, EXCMD *); -int ex_pr(SCR *, EXCMD *); -int ex_print(SCR *, EXCMD *, MARK *, MARK *, u_int32_t); -int ex_ldisplay(SCR *, const CHAR_T *, size_t, size_t, u_int); -int ex_scprint(SCR *, MARK *, MARK *); -int ex_printf(SCR *, const char *, ...); -int ex_puts(SCR *, const char *); -int ex_fflush(SCR *sp); -int ex_put(SCR *, EXCMD *); -int ex_quit(SCR *, EXCMD *); -int ex_read(SCR *, EXCMD *); -int ex_readfp(SCR *, char *, FILE *, MARK *, recno_t *, int); -int ex_bg(SCR *, EXCMD *); -int ex_fg(SCR *, EXCMD *); -int ex_resize(SCR *, EXCMD *); -int ex_sdisplay(SCR *); -int ex_script(SCR *, EXCMD *); -int sscr_exec(SCR *, recno_t); -int sscr_input(SCR *); -int sscr_end(SCR *); -int ex_set(SCR *, EXCMD *); -int ex_shell(SCR *, EXCMD *); -int ex_exec_proc(SCR *, EXCMD *, char *, const char *, int); -int proc_wait(SCR *, long, const char *, int, int); -int ex_shiftl(SCR *, EXCMD *); -int ex_shiftr(SCR *, EXCMD *); -int ex_retab(SCR *, EXCMD *); -int ex_source(SCR *, EXCMD *); -int ex_stop(SCR *, EXCMD *); -int ex_s(SCR *, EXCMD *); -int ex_subagain(SCR *, EXCMD *); -int ex_subtilde(SCR *, EXCMD *); -int re_compile(SCR *, - CHAR_T *, size_t, CHAR_T **, size_t *, regex_t *, u_int); -void re_error(SCR *, int, regex_t *); -int ex_tag_first(SCR *, CHAR_T *); -int ex_tag_push(SCR *, EXCMD *); -int ex_tag_next(SCR *, EXCMD *); -int ex_tag_prev(SCR *, EXCMD *); -int ex_tag_nswitch(SCR *, TAG *, int); -int ex_tag_Nswitch(SCR *, TAG *, int); -int ex_tag_pop(SCR *, EXCMD *); -int ex_tag_top(SCR *, EXCMD *); -int ex_tag_display(SCR *); -int ex_tag_copy(SCR *, SCR *); -int tagq_free(SCR *, TAGQ *); -int tagq_push(SCR*, TAGQ*, int, int ); -void tag_msg(SCR *, tagmsg_t, char *); -int ex_tagf_alloc(SCR *, char *); -int ex_tag_free(SCR *); -int ex_txt(SCR *, TEXTH *, ARG_CHAR_T, u_int32_t); -int ex_undo(SCR *, EXCMD *); -int ex_help(SCR *, EXCMD *); -int ex_usage(SCR *, EXCMD *); -int ex_viusage(SCR *, EXCMD *); -void ex_cinit(SCR *, EXCMD *, int, int, recno_t, recno_t, int); -int ex_getline(SCR *, FILE *, size_t *); -int ex_ncheck(SCR *, int); -int ex_init(SCR *); -void ex_wemsg(SCR *, CHAR_T *, exm_t); -void ex_emsg(SCR *, char *, exm_t); -int ex_version(SCR *, EXCMD *); -int ex_visual(SCR *, EXCMD *); -int ex_wn(SCR *, EXCMD *); -int ex_wq(SCR *, EXCMD *); -int ex_write(SCR *, EXCMD *); -int ex_xit(SCR *, EXCMD *); -int ex_writefp(SCR *, - char *, FILE *, MARK *, MARK *, u_long *, u_long *, int); -int ex_yank(SCR *, EXCMD *); -int ex_z(SCR *, EXCMD *); diff --git a/ex/version.h b/ex/version.h deleted file mode 100644 index 742b145ebd3f..000000000000 --- a/ex/version.h +++ /dev/null @@ -1 +0,0 @@ -#define VI_VERSION "2.2.0 (2020-08-01)" diff --git a/man/vi.1 b/man/vi.1 index ce7b883f8497..62f28d72569c 100644 --- a/man/vi.1 +++ b/man/vi.1 @@ -1,2772 +1,2798 @@ .\" 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. +Edit a different file. The capitalized command opens a new screen below the +current screen. .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. +Foreground the specified screen. The capitalized command opens a new screen +below the current 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. +Edit the next file from the argument list. The capitalized command opens a +new screen below the current screen. .\" .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. +Edit the previous file from the argument list. The capitalized command opens +a new screen below the current screen. .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. +Edit the file containing the specified tag. The capitalized command opens a +new screen below the current screen. .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 +.Cm Vi Ns +.Op Cm sual Ns .Op Cm !\& .Op Ar +cmd .Op Ar file .Xc .Nm vi -mode only. -Edit a new file. +mode only. Edit a different file by opening a new screen below the current +screen. .Pp .It Xo .Cm viu Ns Op Cm sage .Op Ar command .Xc Display usage for a .Nm vi command. .Pp .It Xo +.Cm vs Ns Op Cm plit +.Op Ar +cmd +.Op Ar file +.Xc +Edit a different file by opening a new screen to the right of the current +screen. +.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 +.Cm !\& Ns Ar shell-command .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. +Write the entire file, or +.Ar range . +.Sq !\& +overwrites a different, preexisting file. +.Sq >> +appends to a file that may preexist. Whitespace followed by +.Sq !\& +pipes the file to +.Ar shell-command . +.Cm wn +moves to the next file if writing succeeds. +.Cm wq +exits the editor if writing succeeds, unless there are more files to edit; +.Sq !\& +exits regardless. .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. diff --git a/vi/extern.h b/vi/extern.h deleted file mode 100644 index 8e145c6318ef..000000000000 --- a/vi/extern.h +++ /dev/null @@ -1,145 +0,0 @@ -int cs_init(SCR *, VCS *); -int cs_next(SCR *, VCS *); -int cs_fspace(SCR *, VCS *); -int cs_fblank(SCR *, VCS *); -int cs_prev(SCR *, VCS *); -int cs_bblank(SCR *, VCS *); -int v_at(SCR *, VICMD *); -int v_chrepeat(SCR *, VICMD *); -int v_chrrepeat(SCR *, VICMD *); -int v_cht(SCR *, VICMD *); -int v_chf(SCR *, VICMD *); -int v_chT(SCR *, VICMD *); -int v_chF(SCR *, VICMD *); -int v_delete(SCR *, VICMD *); -int v_again(SCR *, VICMD *); -int v_exmode(SCR *, VICMD *); -int v_join(SCR *, VICMD *); -int v_shiftl(SCR *, VICMD *); -int v_shiftr(SCR *, VICMD *); -int v_suspend(SCR *, VICMD *); -int v_switch(SCR *, VICMD *); -int v_tagpush(SCR *, VICMD *); -int v_tagpop(SCR *, VICMD *); -int v_filter(SCR *, VICMD *); -int v_ex(SCR *, VICMD *); -int v_ecl_exec(SCR *); -int v_increment(SCR *, VICMD *); -int v_screen_copy(SCR *, SCR *); -int v_screen_end(SCR *); -int v_optchange(SCR *, int, char *, u_long *); -int v_iA(SCR *, VICMD *); -int v_ia(SCR *, VICMD *); -int v_iI(SCR *, VICMD *); -int v_ii(SCR *, VICMD *); -int v_iO(SCR *, VICMD *); -int v_io(SCR *, VICMD *); -int v_change(SCR *, VICMD *); -int v_Replace(SCR *, VICMD *); -int v_subst(SCR *, VICMD *); -int v_left(SCR *, VICMD *); -int v_cfirst(SCR *, VICMD *); -int v_first(SCR *, VICMD *); -int v_ncol(SCR *, VICMD *); -int v_zero(SCR *, VICMD *); -int v_mark(SCR *, VICMD *); -int v_bmark(SCR *, VICMD *); -int v_fmark(SCR *, VICMD *); -int v_emark(SCR *, VICMD *); -int v_match(SCR *, VICMD *); -int v_buildmcs(SCR *, char *); -int v_paragraphf(SCR *, VICMD *); -int v_paragraphb(SCR *, VICMD *); -int v_buildps(SCR *, char *, char *); -int v_Put(SCR *, VICMD *); -int v_put(SCR *, VICMD *); -int v_redraw(SCR *, VICMD *); -int v_replace(SCR *, VICMD *); -int v_right(SCR *, VICMD *); -int v_dollar(SCR *, VICMD *); -int v_screen(SCR *, VICMD *); -int v_lgoto(SCR *, VICMD *); -int v_home(SCR *, VICMD *); -int v_middle(SCR *, VICMD *); -int v_bottom(SCR *, VICMD *); -int v_up(SCR *, VICMD *); -int v_cr(SCR *, VICMD *); -int v_down(SCR *, VICMD *); -int v_hpageup(SCR *, VICMD *); -int v_hpagedown(SCR *, VICMD *); -int v_pagedown(SCR *, VICMD *); -int v_pageup(SCR *, VICMD *); -int v_lineup(SCR *, VICMD *); -int v_linedown(SCR *, VICMD *); -int v_searchb(SCR *, VICMD *); -int v_searchf(SCR *, VICMD *); -int v_searchN(SCR *, VICMD *); -int v_searchn(SCR *, VICMD *); -int v_searchw(SCR *, VICMD *); -int v_correct(SCR *, VICMD *, int); -int v_sectionf(SCR *, VICMD *); -int v_sectionb(SCR *, VICMD *); -int v_sentencef(SCR *, VICMD *); -int v_sentenceb(SCR *, VICMD *); -int v_status(SCR *, VICMD *); -int v_tcmd(SCR *, VICMD *, ARG_CHAR_T, u_int); -int v_txt(SCR *, VICMD *, MARK *, - const CHAR_T *, size_t, ARG_CHAR_T, recno_t, u_long, u_int32_t); -int v_txt_auto(SCR *, recno_t, TEXT *, size_t, TEXT *); -int v_ulcase(SCR *, VICMD *); -int v_mulcase(SCR *, VICMD *); -int v_Undo(SCR *, VICMD *); -int v_undo(SCR *, VICMD *); -void v_eof(SCR *, MARK *); -void v_eol(SCR *, MARK *); -void v_nomove(SCR *); -void v_sof(SCR *, MARK *); -void v_sol(SCR *); -int v_isempty(CHAR_T *, size_t); -void v_emsg(SCR *, char *, vim_t); -int v_wordW(SCR *, VICMD *); -int v_wordw(SCR *, VICMD *); -int v_wordE(SCR *, VICMD *); -int v_worde(SCR *, VICMD *); -int v_wordB(SCR *, VICMD *); -int v_wordb(SCR *, VICMD *); -int v_xchar(SCR *, VICMD *); -int v_Xchar(SCR *, VICMD *); -int v_yank(SCR *, VICMD *); -int v_z(SCR *, VICMD *); -int vs_crel(SCR *, long); -int v_zexit(SCR *, VICMD *); -int vi(SCR **); -int v_curword(SCR *); -int vs_line(SCR *, SMAP *, size_t *, size_t *); -int vs_number(SCR *); -void vs_busy(SCR *, const char *, busy_t); -void vs_home(SCR *); -void vs_update(SCR *, const char *, const CHAR_T *); -void vs_msg(SCR *, mtype_t, char *, size_t); -int vs_ex_resolve(SCR *, int *); -int vs_resolve(SCR *, SCR *, int); -int vs_repaint(SCR *, EVENT *); -int vs_refresh(SCR *, int); -int vs_column(SCR *, size_t *); -size_t vs_screens(SCR *, recno_t, size_t *); -size_t vs_columns(SCR *, CHAR_T *, recno_t, size_t *, size_t *); -size_t vs_rcm(SCR *, recno_t, int); -size_t vs_colpos(SCR *, recno_t, size_t); -int vs_change(SCR *, recno_t, lnop_t); -int vs_sm_fill(SCR *, recno_t, pos_t); -int vs_sm_scroll(SCR *, MARK *, recno_t, scroll_t); -int vs_sm_1up(SCR *); -int vs_sm_1down(SCR *); -int vs_sm_next(SCR *, SMAP *, SMAP *); -int vs_sm_prev(SCR *, SMAP *, SMAP *); -int vs_sm_cursor(SCR *, SMAP **); -int vs_sm_position(SCR *, MARK *, u_long, pos_t); -recno_t vs_sm_nlines(SCR *, SMAP *, recno_t, size_t); -int vs_split(SCR *, SCR *, int); -int vs_vsplit(SCR *, SCR *); -int vs_discard(SCR *, SCR **); -int vs_fg(SCR *, SCR **, CHAR_T *, int); -int vs_bg(SCR *); -int vs_swap(SCR *, SCR **, char *); -int vs_resize(SCR *, long, adj_t); diff --git a/vi/v_increment.c b/vi/v_increment.c index 6c59836cab7e..2583d9704579 100644 --- a/vi/v_increment.c +++ b/vi/v_increment.c @@ -1,260 +1,260 @@ /*- * 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" static CHAR_T * const fmt[] = { #define DEC 0 L("%ld"), #define SDEC 1 L("%+ld"), #define HEXC 2 L("0X%0*lX"), #define HEXL 3 L("0x%0*lx"), #define OCTAL 4 L("%#0*lo"), }; static void inc_err(SCR *, enum nresult); /* * v_increment -- [count]#[#+-] * Increment/decrement a keyword number. * * PUBLIC: int v_increment(SCR *, VICMD *); */ int v_increment(SCR *sp, VICMD *vp) { enum nresult nret; u_long ulval; long change, ltmp, lval; size_t beg, blen, end, len, nlen, wlen; int base, isempty, rval; CHAR_T *ntype, nbuf[100]; CHAR_T *bp, *p, *t; /* Validate the operator. */ if (vp->character == '#') vp->character = '+'; if (vp->character != '+' && vp->character != '-') { v_emsg(sp, vp->kp->usage, VIM_USAGE); return (1); } /* If new value set, save it off, but it has to fit in a long. */ if (F_ISSET(vp, VC_C1SET)) { if (vp->count > LONG_MAX) { inc_err(sp, NUM_OVER); return (1); } change = vp->count; } else change = 1; /* Get the line. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nonum; return (1); } /* * Skip any leading space before the number. Getting a cursor word * implies moving the cursor to its beginning, if we moved, refresh * now. */ for (beg = vp->m_start.cno; beg < len && ISSPACE(p[beg]); ++beg); if (beg >= len) goto nonum; if (beg != vp->m_start.cno) { sp->cno = beg; (void)vs_refresh(sp, 0); } #undef ishex #define ishex(c) (ISXDIGIT(c)) #undef isoctal #define isoctal(c) ((c) >= '0' && (c) <= '7') /* * Look for 0[Xx], or leading + or - signs, guess at the base. * The character after that must be a number. Wlen is set to * the remaining characters in the line that could be part of * the number. */ wlen = len - beg; if (p[beg] == '0' && wlen > 2 && (p[beg + 1] == 'X' || p[beg + 1] == 'x')) { base = 16; end = beg + 2; if (!ishex(p[end])) goto decimal; ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL]; } else if (p[beg] == '0' && wlen > 1) { base = 8; end = beg + 1; if (!isoctal(p[end])) goto decimal; ntype = fmt[OCTAL]; } else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) { base = 10; end = beg + 1; ntype = fmt[SDEC]; if (!isdigit(p[end])) goto nonum; } else { decimal: base = 10; end = beg; ntype = fmt[DEC]; if (!isdigit(p[end])) { nonum: msgq(sp, M_ERR, "181|Cursor not in a number"); return (1); } } /* Find the end of the word, possibly correcting the base. */ while (++end < len) { switch (base) { case 8: if (isoctal(p[end])) continue; if (p[end] == '8' || p[end] == '9') { base = 10; ntype = fmt[DEC]; continue; } break; case 10: if (isdigit(p[end])) continue; break; case 16: if (ishex(p[end])) continue; break; default: abort(); /* NOTREACHED */ } break; } wlen = (end - beg); /* * XXX * If the line was at the end of the buffer, we have to copy it * so we can guarantee that it's NULL-terminated. We make the * buffer big enough to fit the line changes as well, and only * allocate once. */ GET_SPACE_RETW(sp, bp, blen, len + 50); if (end == len) { MEMMOVE(bp, &p[beg], wlen); bp[wlen] = '\0'; t = bp; } else t = &p[beg]; /* * Octal or hex deal in unsigned longs, everything else is done * in signed longs. */ if (base == 10) { if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK) goto err; ltmp = vp->character == '-' ? -change : change; if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) { nret = NUM_OVER; goto err; } if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) { nret = NUM_UNDER; goto err; } lval += ltmp; /* If we cross 0, signed numbers lose their sign. */ if (lval == 0 && ntype == fmt[SDEC]) ntype = fmt[DEC]; - nlen = SPRINTF(nbuf, sizeof(nbuf), ntype, lval); + nlen = SPRINTF(nbuf, SIZE(nbuf), ntype, lval); } else { if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK) goto err; if (vp->character == '+') { if (!NPFITS(ULONG_MAX, ulval, change)) { nret = NUM_OVER; goto err; } ulval += change; } else { if (ulval < change) { nret = NUM_UNDER; goto err; } ulval -= change; } /* Correct for literal "0[Xx]" in format. */ if (base == 16) wlen -= 2; - nlen = SPRINTF(nbuf, sizeof(nbuf), ntype, wlen, ulval); + nlen = SPRINTF(nbuf, SIZE(nbuf), ntype, wlen, ulval); } /* Build the new line. */ MEMMOVE(bp, p, beg); MEMMOVE(bp + beg, nbuf, nlen); MEMMOVE(bp + beg + nlen, p + end, len - beg - (end - beg)); len = beg + nlen + (len - beg - (end - beg)); nret = NUM_OK; rval = db_set(sp, vp->m_start.lno, bp, len); if (0) { err: rval = 1; inc_err(sp, nret); } if (bp != NULL) FREE_SPACEW(sp, bp, blen); return (rval); } static void inc_err(SCR *sp, enum nresult nret) { switch (nret) { case NUM_ERR: break; case NUM_OK: abort(); /* NOREACHED */ case NUM_OVER: msgq(sp, M_ERR, "182|Resulting number too large"); break; case NUM_UNDER: msgq(sp, M_ERR, "183|Resulting number too small"); break; } } diff --git a/vi/vs_line.c b/vi/vs_line.c index e5778e1971e6..45c950badef9 100644 --- a/vi/vs_line.c +++ b/vi/vs_line.c @@ -1,536 +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 (dne) { if (smp->lno == 1) { if (list_dollar) { ch = '$'; goto empty; } } else { ch = '~'; goto empty; } } else { if (list_dollar) { ch = '$'; empty: (void)gp->scr_addstr(sp, KEY_NAME(sp, ch), KEY_LEN(sp, ch)); } } } (void)gp->scr_clrtoeol(sp); (void)gp->scr_move(sp, oldy, oldx); return (0); } /* If we 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 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) { + if (is_partial && XCHAR_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); }