diff --git a/contrib/nvi/CMakeLists.txt b/contrib/nvi/CMakeLists.txt index 66d3ca2aafb0..0c935b3c2a19 100644 --- a/contrib/nvi/CMakeLists.txt +++ b/contrib/nvi/CMakeLists.txt @@ -1,242 +1,246 @@ 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) 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>) if (NOT APPLE) add_compile_options($<$:-Wsystem-headers>) endif() add_compile_options($<$:-Wuninitialized>) add_compile_options($<$:-Wno-dangling-else>) +add_compile_options(-Wno-string-compare) 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) + # The macro _XOPEN_SOURCE_EXTENDED is needed to get the waddnwstr() + # definition on at least FreeBSD and recent macOS. + target_compile_definitions(nvi PRIVATE _XOPEN_SOURCE_EXTENDED) 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}) 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/contrib/nvi/INSTALL.md b/contrib/nvi/INSTALL.md new file mode 100644 index 000000000000..7b2fd35b7c60 --- /dev/null +++ b/contrib/nvi/INSTALL.md @@ -0,0 +1,47 @@ +# Install from source + +For instructions to bring nvi2 as a part of your operating system's base system, see [Porting](https://github.com/lichray/nvi2/wiki/Porting) in the Wiki. This document is an overview of the build process that allows you to give nvi2 a try. + +## Prerequisites + +- CMake >= 3.17; +- Ninja build system; +- libiconv (for `USE_ICONV`); +- libncursesw (for `USE_WIDECHAR`); + +Anything required by a minimal nvi, notably: + +- Berkeley DB1 in libc; +- /var/tmp/vi.recover/ with mode 41777. + +## Building + +Nvi2 uses CMake build system generator. By specifying "Ninja Multi-Config" as the build system to generate, you can compile the project in both Debug and Release modes without re-running CMake. Under the project root directory, run + +```sh +cmake -G "Ninja Multi-Config" -B build +``` + +Now `build` becomes your build directory to hold the artifacts. To build nvi2 in Debug mode, run + +```sh +ninja -C build +``` + +Upon finishing, the nvi2 executable will be available as `build/Debug/nvi`. To launch it in `ex` mode, you can create a symlink + +```sh +ln -s nvi build/Debug/ex +``` + +and run `./build/Debug/ex` rather than `./build/Debug/nvi`. + +To build nvi2 in Release mode, use the following command instead: + +```sh +ninja -C build -f build-Release.ninja +``` + +Upon finishing, you will be able to edit files with `./build/Release/nvi`. + +To change configure-time options, such as disabling wide character support, use `ccmake build`. diff --git a/contrib/nvi/README b/contrib/nvi/README index 8f61a97144be..9e638d952444 100644 --- a/contrib/nvi/README +++ b/contrib/nvi/README @@ -1,64 +1,88 @@ -This is version 2.2.0 (2020-08-01) of nex/nvi, a reimplementation of the ex/vi +This is version 2.2.1 (2023-09-25) of nex/nvi, a reimplementation of the ex/vi text editors originally distributed as part of the Fourth Berkeley Software Distribution (4BSD), by the University of California, Berkeley. The directory layout is as follows: LICENSE ....... Copyright, use and redistribution information. README ........ This file. catalog ....... Message catalogs; see catalog/README. cl ............ Vi interface to the curses(3) library. common ........ Code shared by ex and vi. ex ............ Ex source code. files ......... Template files. man ........... Ex/vi documentation. regex ......... Modified regex library with wide character support. vi ............ Vi source code. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= o Nvi was written by Keith Bostic, and the last version is 1.79. After that, Sven Verdoolaege added the iconv support and the DB3 locking. Jun-ichiro itojun Hagino developed the file encoding detection techniques in his nvi-m17n. +o In nvi2, Zhihao Yuan incorporated the multibyte encoding support onto DB1. + + It was not possible without great support from Alexander Leidinger, + Peter Wemm, and the FreeBSD community. + + Last but not least, money from Google Summer of Code. + +o Since then, + + Todd C. Miller and Craig Leres adopted and refined the NetBSD-style + expandtab option. + + Yamamoto Takashi, Matija Skala, and Jessica Clarke ported the + software to macOS and Linux. + + Anthony J. Bentley made heroic efforts to modernize the code base + and documentation, leveraging experience from OpenBSD to improve the + quality of the project. + + ...and many others, including Michael McConville, Marc Simpson, + Jeffrey H. Johnson, Bosco GarcĂ­a, Anton Konyahin, Walter Alejandro + Iglesias, and those who tried hard to keep anonymous on GitHub :) + Their insights render the software usable, secure, and sustainable. + The following acknowledgments were written by Keith Bostic: =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= o This software is several years old and is the product of many folks' work. This software was originally derived from software contributed to the University of California, Berkeley by Steve Kirkendall, the author of the vi clone elvis. Without his work, this work would have been far more difficult. IEEE POSIX 1003.2 style regular expression support is courtesy of Henry Spencer, for which I am *very* grateful. Elan Amir did the original 4BSD curses work that made it possible to support a full-screen editor using curses. George Neville-Neil added the Tcl interpreter, and the initial interpreter design was his. Sven Verdoolaege added the Perl interpreter. Rob Mayoff provided the original Cscope support. o Many, many people suggested enhancements, and provided bug reports and testing, far too many to individually thank. o From the original vi acknowledgements, by William Joy and Mark Horton: Bruce Englar encouraged the early development of this display editor. Peter Kessler helped bring sanity to version 2's command layout. Bill Joy wrote versions 1 and 2.0 through 2.7, and created the framework that users see in the present editor. Mark Horton added macros and other features and made the editor work on a large number of terminals and Unix systems. o And... The financial support of UUNET Communications Services is gratefully acknowledged. diff --git a/contrib/nvi/common/key.c b/contrib/nvi/common/key.c index e71396893fd4..1ccfb61ee0a1 100644 --- a/contrib/nvi/common/key.c +++ b/contrib/nvi/common/key.c @@ -1,839 +1,928 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" static int v_event_append(SCR *, EVENT *); static int v_event_grow(SCR *, int); static int v_key_cmp(const void *, const void *); static void v_keyval(SCR *, int, scr_keyval_t); static void v_sync(SCR *, int); +static const char *alt_key_notation(int ch); /* * !!! * Historic vi always used: * * ^D: autoindent deletion * ^H: last character deletion * ^W: last word deletion * ^Q: quote the next character (if not used in flow control). * ^V: quote the next character * * regardless of the user's choices for these characters. The user's erase * and kill characters worked in addition to these characters. Nvi wires * down the above characters, but in addition permits the VEOF, VERASE, VKILL * and VWERASE characters described by the user's termios structure. * * Ex was not consistent with this scheme, as it historically ran in tty * cooked mode. This meant that the scroll command and autoindent erase * characters were mapped to the user's EOF character, and the character * and word deletion characters were the user's tty character and word * deletion characters. This implementation makes it all consistent, as * described above for vi. * * !!! * This means that all screens share a special key set. */ KEYLIST keylist[] = { {K_BACKSLASH, '\\'}, /* \ */ {K_CARAT, '^'}, /* ^ */ {K_CNTRLD, '\004'}, /* ^D */ {K_CNTRLR, '\022'}, /* ^R */ {K_CNTRLT, '\024'}, /* ^T */ {K_CNTRLZ, '\032'}, /* ^Z */ {K_COLON, ':'}, /* : */ {K_CR, '\r'}, /* \r */ {K_ESCAPE, '\033'}, /* ^[ */ {K_FORMFEED, '\f'}, /* \f */ {K_HEXCHAR, '\030'}, /* ^X */ {K_NL, '\n'}, /* \n */ {K_RIGHTBRACE, '}'}, /* } */ {K_RIGHTPAREN, ')'}, /* ) */ {K_TAB, '\t'}, /* \t */ {K_VERASE, '\b'}, /* \b */ {K_VKILL, '\025'}, /* ^U */ {K_VLNEXT, '\021'}, /* ^Q */ {K_VLNEXT, '\026'}, /* ^V */ {K_VWERASE, '\027'}, /* ^W */ {K_ZERO, '0'}, /* 0 */ #define ADDITIONAL_CHARACTERS 4 {K_NOTUSED, 0}, /* VEOF, VERASE, VKILL, VWERASE */ {K_NOTUSED, 0}, {K_NOTUSED, 0}, {K_NOTUSED, 0}, }; static int nkeylist = (sizeof(keylist) / sizeof(keylist[0])) - ADDITIONAL_CHARACTERS; /* * v_key_init -- * Initialize the special key lookup table. * * PUBLIC: int v_key_init(SCR *); */ int v_key_init(SCR *sp) { int ch; GS *gp; KEYLIST *kp; int cnt; gp = sp->gp; v_key_ilookup(sp); v_keyval(sp, K_CNTRLD, KEY_VEOF); v_keyval(sp, K_VERASE, KEY_VERASE); v_keyval(sp, K_VKILL, KEY_VKILL); v_keyval(sp, K_VWERASE, KEY_VWERASE); /* Sort the special key list. */ qsort(keylist, nkeylist, sizeof(keylist[0]), v_key_cmp); /* Initialize the fast lookup table. */ for (kp = keylist, cnt = nkeylist; cnt--; ++kp) gp->special_key[kp->ch] = kp->value; /* Find a non-printable character to use as a message separator. */ for (ch = 1; ch <= UCHAR_MAX; ++ch) if (!isprint(ch)) { gp->noprint = ch; break; } if (ch != gp->noprint) { msgq(sp, M_ERR, "079|No non-printable character found"); return (1); } return (0); } /* * v_keyval -- * Set key values. * * We've left some open slots in the keylist table, and if these values exist, * we put them into place. Note, they may reset (or duplicate) values already * in the table, so we check for that first. */ static void v_keyval(SCR *sp, int val, scr_keyval_t name) { KEYLIST *kp; CHAR_T ch; int dne; /* Get the key's value from the screen. */ if (sp->gp->scr_keyval(sp, name, &ch, &dne)) return; if (dne) return; /* Check for duplication. */ for (kp = keylist; kp->value != K_NOTUSED; ++kp) if (kp->ch == ch) { kp->value = val; return; } /* Add a new entry. */ if (kp->value == K_NOTUSED) { keylist[nkeylist].ch = ch; keylist[nkeylist].value = val; ++nkeylist; } } /* * v_key_ilookup -- * Build the fast-lookup key display array. * * PUBLIC: void v_key_ilookup(SCR *); */ void v_key_ilookup(SCR *sp) { UCHAR_T ch; char *p, *t; GS *gp; size_t len; for (gp = sp->gp, ch = 0;; ++ch) { for (p = gp->cname[ch].name, t = v_key_name(sp, ch), len = gp->cname[ch].len = sp->clen; len--;) *p++ = *t++; if (ch == MAX_FAST_KEY) break; } } /* * v_key_len -- * Return the length of the string that will display the key. * This routine is the backup for the KEY_LEN() macro. * * PUBLIC: size_t v_key_len(SCR *, ARG_CHAR_T); */ size_t v_key_len(SCR *sp, ARG_CHAR_T ch) { (void)v_key_name(sp, ch); return (sp->clen); } /* * v_key_name -- * Return the string that will display the key. This routine * is the backup for the KEY_NAME() macro. * * PUBLIC: char *v_key_name(SCR *, ARG_CHAR_T); */ char * v_key_name(SCR *sp, ARG_CHAR_T ach) { static const char hexdigit[] = "0123456789abcdef"; static const char octdigit[] = "01234567"; int ch; size_t len; char *chp; /* * Cache the last checked character. It won't be a problem * since nvi will rescan the mapping when settings changed. */ if (ach && sp->lastc == ach) return (sp->cname); sp->lastc = ach; #ifdef USE_WIDECHAR len = wctomb(sp->cname, ach); if (len > MB_CUR_MAX) #endif sp->cname[(len = 1)-1] = (u_char)ach; ch = (u_char)sp->cname[0]; sp->cname[len] = '\0'; /* See if the character was explicitly declared printable or not. */ if ((chp = O_STR(sp, O_PRINT)) != NULL) if (strstr(chp, sp->cname) != NULL) goto done; if ((chp = O_STR(sp, O_NOPRINT)) != NULL) if (strstr(chp, sp->cname) != NULL) goto nopr; /* * Historical (ARPA standard) mappings. Printable characters are left * alone. Control characters less than 0x20 are represented as '^' * followed by the character offset from the '@' character in the ASCII * character set. Del (0x7f) is represented as '^' followed by '?'. * + * If set O_ALTNOTATION, control characters less than 0x20 are + * represented in notations. Carriage feed, escape, and + * delete are marked as , , and , respectively. + * * XXX * The following code depends on the current locale being identical to * the ASCII map from 0x40 to 0x5f (since 0x1f + 0x40 == 0x5f). I'm * told that this is a reasonable assumption... * * XXX * The code prints non-printable wide characters in 4 or 5 digits * Unicode escape sequences, so only supports plane 0 to 15. */ if (CAN_PRINT(sp, ach)) goto done; nopr: if (iscntrl(ch) && (ch < 0x20 || ch == 0x7f)) { - sp->cname[0] = '^'; - sp->cname[1] = ch == 0x7f ? '?' : '@' + ch; - len = 2; + if (O_ISSET(sp, O_ALTNOTATION)) { + const char *notation = alt_key_notation(ch); + len = strlcpy(sp->cname, notation, sizeof(sp->cname)); + } else { + sp->cname[0] = '^'; + sp->cname[1] = ch == 0x7f ? '?' : '@' + ch; + len = 2; + } goto done; } #ifdef USE_WIDECHAR if (INTISWIDE(ach)) { int uc = -1; if (!strcmp(codeset(), "UTF-8")) uc = decode_utf8(sp->cname); #ifdef USE_ICONV else { char buf[sizeof(sp->cname)] = ""; size_t left = sizeof(sp->cname); char *in = sp->cname; char *out = buf; iconv(sp->conv.id[IC_IE_TO_UTF16], (iconv_src_t)&in, &len, &out, &left); iconv(sp->conv.id[IC_IE_TO_UTF16], NULL, NULL, NULL, NULL); uc = decode_utf16(buf, 1); } #endif if (uc >= 0) { len = snprintf(sp->cname, sizeof(sp->cname), uc < 0x10000 ? "\\u%04x" : "\\U%05X", uc); goto done; } } #endif if (O_ISSET(sp, O_OCTAL)) { sp->cname[0] = '\\'; sp->cname[1] = octdigit[(ch & 0300) >> 6]; sp->cname[2] = octdigit[(ch & 070) >> 3]; sp->cname[3] = octdigit[ ch & 07 ]; } else { sp->cname[0] = '\\'; sp->cname[1] = 'x'; sp->cname[2] = hexdigit[(ch & 0xf0) >> 4]; sp->cname[3] = hexdigit[ ch & 0x0f ]; } len = 4; done: sp->cname[sp->clen = len] = '\0'; return (sp->cname); } /* * v_key_val -- * Fill in the value for a key. This routine is the backup * for the KEY_VAL() macro. * * PUBLIC: e_key_t v_key_val(SCR *, ARG_CHAR_T); */ e_key_t v_key_val(SCR *sp, ARG_CHAR_T ch) { KEYLIST k, *kp; k.ch = ch; kp = bsearch(&k, keylist, nkeylist, sizeof(keylist[0]), v_key_cmp); return (kp == NULL ? K_NOTUSED : kp->value); } /* * v_event_push -- * Push events/keys onto the front of the buffer. * * There is a single input buffer in ex/vi. Characters are put onto the * end of the buffer by the terminal input routines, and pushed onto the * front of the buffer by various other functions in ex/vi. Each key has * an associated flag value, which indicates if it has already been quoted, * and if it is the result of a mapping or an abbreviation. * * PUBLIC: int v_event_push(SCR *, EVENT *, CHAR_T *, size_t, u_int); */ int v_event_push(SCR *sp, EVENT *p_evp, /* Push event. */ CHAR_T *p_s, /* Push characters. */ size_t nitems, /* Number of items to push. */ u_int flags) /* CH_* flags. */ { EVENT *evp; GS *gp; size_t total; /* If we have room, stuff the items into the buffer. */ gp = sp->gp; if (nitems <= gp->i_next || (gp->i_event != NULL && gp->i_cnt == 0 && nitems <= gp->i_nelem)) { if (gp->i_cnt != 0) gp->i_next -= nitems; goto copy; } /* * If there are currently items in the queue, shift them up, * leaving some extra room. Get enough space plus a little * extra. */ #define TERM_PUSH_SHIFT 30 total = gp->i_cnt + gp->i_next + nitems + TERM_PUSH_SHIFT; if (total >= gp->i_nelem && v_event_grow(sp, MAX(total, 64))) return (1); if (gp->i_cnt) memmove(gp->i_event + TERM_PUSH_SHIFT + nitems, gp->i_event + gp->i_next, gp->i_cnt * sizeof(EVENT)); gp->i_next = TERM_PUSH_SHIFT; /* Put the new items into the queue. */ copy: gp->i_cnt += nitems; for (evp = gp->i_event + gp->i_next; nitems--; ++evp) { if (p_evp != NULL) *evp = *p_evp++; else { evp->e_event = E_CHARACTER; evp->e_c = *p_s++; evp->e_value = KEY_VAL(sp, evp->e_c); F_INIT(&evp->e_ch, flags); } } return (0); } /* * v_event_append -- * Append events onto the tail of the buffer. */ static int v_event_append(SCR *sp, EVENT *argp) { CHAR_T *s; /* Characters. */ EVENT *evp; GS *gp; size_t nevents; /* Number of events. */ /* Grow the buffer as necessary. */ nevents = argp->e_event == E_STRING ? argp->e_len : 1; gp = sp->gp; if (gp->i_event == NULL || nevents > gp->i_nelem - (gp->i_next + gp->i_cnt)) v_event_grow(sp, MAX(nevents, 64)); evp = gp->i_event + gp->i_next + gp->i_cnt; gp->i_cnt += nevents; /* Transform strings of characters into single events. */ if (argp->e_event == E_STRING) for (s = argp->e_csp; nevents--; ++evp) { evp->e_event = E_CHARACTER; evp->e_c = *s++; evp->e_value = KEY_VAL(sp, evp->e_c); evp->e_flags = 0; } else *evp = *argp; return (0); } /* Remove events from the queue. */ #define QREM(len) do { \ if ((gp->i_cnt -= len) == 0) \ gp->i_next = 0; \ else \ gp->i_next += len; \ } while (0) /* * v_event_get -- * Return the next event. * * !!! * The flag EC_NODIGIT probably needs some explanation. First, the idea of * mapping keys is that one or more keystrokes act like a function key. * What's going on is that vi is reading a number, and the character following * the number may or may not be mapped (EC_MAPCOMMAND). For example, if the * user is entering the z command, a valid command is "z40+", and we don't want * to map the '+', i.e. if '+' is mapped to "xxx", we don't want to change it * into "z40xxx". However, if the user enters "35x", we want to put all of the * characters through the mapping code. * * Historical practice is a bit muddled here. (Surprise!) It always permitted * mapping digits as long as they weren't the first character of the map, e.g. * ":map ^A1 xxx" was okay. It also permitted the mapping of the digits 1-9 * (the digit 0 was a special case as it doesn't indicate the start of a count) * as the first character of the map, but then ignored those mappings. While * it's probably stupid to map digits, vi isn't your mother. * * The way this works is that the EC_MAPNODIGIT causes term_key to return the * end-of-digit without "looking" at the next character, i.e. leaving it as the * user entered it. Presumably, the next term_key call will tell us how the * user wants it handled. * * There is one more complication. Users might map keys to digits, and, as * it's described above, the commands: * * :map g 1G * d2g * * would return the keys "d21G", when the user probably wanted * "d21G". So, if a map starts off with a digit we continue as * before, otherwise, we pretend we haven't mapped the character, and return * . * * Now that that's out of the way, let's talk about Energizer Bunny macros. * It's easy to create macros that expand to a loop, e.g. map x 3x. It's * fairly easy to detect this example, because it's all internal to term_key. * If we're expanding a macro and it gets big enough, at some point we can * assume it's looping and kill it. The examples that are tough are the ones * where the parser is involved, e.g. map x "ayyx"byy. We do an expansion * on 'x', and get "ayyx"byy. We then return the first 4 characters, and then * find the looping macro again. There is no way that we can detect this * without doing a full parse of the command, because the character that might * cause the loop (in this case 'x') may be a literal character, e.g. the map * map x "ayy"xyy"byy is perfectly legal and won't cause a loop. * * Historic vi tried to detect looping macros by disallowing obvious cases in * the map command, maps that that ended with the same letter as they started * (which wrongly disallowed "map x 'x"), and detecting macros that expanded * too many times before keys were returned to the command parser. It didn't * get many (most?) of the tricky cases right, however, and it was certainly * possible to create macros that ran forever. And, even if it did figure out * what was going on, the user was usually tossed into ex mode. Finally, any * changes made before vi realized that the macro was recursing were left in * place. We recover gracefully, but the only recourse the user has in an * infinite macro loop is to interrupt. * * !!! * It is historic practice that mapping characters to themselves as the first * part of the mapped string was legal, and did not cause infinite loops, i.e. * ":map! { {^M^T" and ":map n nz." were known to work. The initial, matching * characters were returned instead of being remapped. * * !!! * It is also historic practice that the macro "map ] ]]^" caused a single ] * keypress to behave as the command ]] (the ^ got the map past the vi check * for "tail recursion"). Conversely, the mapping "map n nn^" went recursive. * What happened was that, in the historic vi, maps were expanded as the keys * were retrieved, but not all at once and not centrally. So, the keypress ] * pushed ]]^ on the stack, and then the first ] from the stack was passed to * the ]] command code. The ]] command then retrieved a key without entering * the mapping code. This could bite us anytime a user has a map that depends * on secondary keys NOT being mapped. I can't see any possible way to make * this work in here without the complete abandonment of Rationality Itself. * * XXX * The final issue is recovery. It would be possible to undo all of the work * that was done by the macro if we entered a record into the log so that we * knew when the macro started, and, in fact, this might be worth doing at some * point. Given that this might make the log grow unacceptably (consider that * cursor keys are done with maps), for now we leave any changes made in place. * * PUBLIC: int v_event_get(SCR *, EVENT *, int, u_int32_t); */ int v_event_get(SCR *sp, EVENT *argp, int timeout, u_int32_t flags) { EVENT *evp, ev; GS *gp; SEQ *qp; int init_nomap, ispartial, istimeout, remap_cnt; gp = sp->gp; /* If simply checking for interrupts, argp may be NULL. */ if (argp == NULL) argp = &ev; retry: istimeout = remap_cnt = 0; /* * If the queue isn't empty and we're timing out for characters, * return immediately. */ if (gp->i_cnt != 0 && LF_ISSET(EC_TIMEOUT)) return (0); /* * If the queue is empty, we're checking for interrupts, or we're * timing out for characters, get more events. */ if (gp->i_cnt == 0 || LF_ISSET(EC_INTERRUPT | EC_TIMEOUT)) { /* * If we're reading new characters, check any scripting * windows for input. */ if (F_ISSET(gp, G_SCRWIN) && sscr_input(sp)) return (1); loop: if (gp->scr_event(sp, argp, LF_ISSET(EC_INTERRUPT | EC_QUOTED | EC_RAW), timeout)) return (1); switch (argp->e_event) { case E_ERR: case E_SIGHUP: case E_SIGTERM: /* * Fatal conditions cause the file to be synced to * disk immediately. */ v_sync(sp, RCV_ENDSESSION | RCV_PRESERVE | (argp->e_event == E_SIGTERM ? 0: RCV_EMAIL)); return (1); case E_TIMEOUT: istimeout = 1; break; case E_INTERRUPT: /* Set the global interrupt flag. */ F_SET(sp->gp, G_INTERRUPTED); /* * If the caller was interested in interrupts, return * immediately. */ if (LF_ISSET(EC_INTERRUPT)) return (0); goto append; default: append: if (v_event_append(sp, argp)) return (1); break; } } /* * If the caller was only interested in interrupts or timeouts, return * immediately. (We may have gotten characters, and that's okay, they * were queued up for later use.) */ if (LF_ISSET(EC_INTERRUPT | EC_TIMEOUT)) return (0); newmap: evp = &gp->i_event[gp->i_next]; /* * If the next event in the queue isn't a character event, return * it, we're done. */ if (evp->e_event != E_CHARACTER) { *argp = *evp; QREM(1); return (0); } /* * If the key isn't mappable because: * * + ... the timeout has expired * + ... it's not a mappable key * + ... neither the command or input map flags are set * + ... there are no maps that can apply to it * * return it forthwith. */ if (istimeout || F_ISSET(&evp->e_ch, CH_NOMAP) || !LF_ISSET(EC_MAPCOMMAND | EC_MAPINPUT) || ((evp->e_c & ~MAX_BIT_SEQ) == 0 && !bit_test(gp->seqb, evp->e_c))) goto nomap; /* Search the map. */ qp = seq_find(sp, NULL, evp, NULL, gp->i_cnt, LF_ISSET(EC_MAPCOMMAND) ? SEQ_COMMAND : SEQ_INPUT, &ispartial); /* * If get a partial match, get more characters and retry the map. * If time out without further characters, return the characters * unmapped. * * !!! * characters are a problem. Cursor keys start with * characters, so there's almost always a map in place that begins with * an character. If we timeout keys in the same way * that we timeout other keys, the user will get a noticeable pause as * they enter to terminate input mode. If key timeout is set * for a slow link, users will get an even longer pause. Nvi used to * simply timeout characters at 1/10th of a second, but this * loses over PPP links where the latency is greater than 100Ms. */ if (ispartial) { if (O_ISSET(sp, O_TIMEOUT)) timeout = (evp->e_value == K_ESCAPE ? O_VAL(sp, O_ESCAPETIME) : O_VAL(sp, O_KEYTIME)) * 100; else timeout = 0; goto loop; } /* If no map, return the character. */ if (qp == NULL) { nomap: if (!ISDIGIT(evp->e_c) && LF_ISSET(EC_MAPNODIGIT)) goto not_digit; *argp = *evp; QREM(1); return (0); } /* * If looking for the end of a digit string, and the first character * of the map is it, pretend we haven't seen the character. */ if (LF_ISSET(EC_MAPNODIGIT) && qp->output != NULL && !ISDIGIT(qp->output[0])) { not_digit: argp->e_c = CH_NOT_DIGIT; argp->e_value = K_NOTUSED; argp->e_event = E_CHARACTER; F_INIT(&argp->e_ch, 0); return (0); } /* Find out if the initial segments are identical. */ init_nomap = !e_memcmp(qp->output, &gp->i_event[gp->i_next], qp->ilen); /* Delete the mapped characters from the queue. */ QREM(qp->ilen); /* If keys mapped to nothing, go get more. */ if (qp->output == NULL) goto retry; /* If remapping characters... */ if (O_ISSET(sp, O_REMAP)) { /* * Periodically check for interrupts. Always check the first * time through, because it's possible to set up a map that * will return a character every time, but will expand to more, * e.g. "map! a aaaa" will always return a 'a', but we'll never * get anywhere useful. */ if ((++remap_cnt == 1 || remap_cnt % 10 == 0) && (gp->scr_event(sp, &ev, EC_INTERRUPT, 0) || ev.e_event == E_INTERRUPT)) { F_SET(sp->gp, G_INTERRUPTED); argp->e_event = E_INTERRUPT; return (0); } /* * If an initial part of the characters mapped, they are not * further remapped -- return the first one. Push the rest * of the characters, or all of the characters if no initial * part mapped, back on the queue. */ if (init_nomap) { if (v_event_push(sp, NULL, qp->output + qp->ilen, qp->olen - qp->ilen, CH_MAPPED)) return (1); if (v_event_push(sp, NULL, qp->output, qp->ilen, CH_NOMAP | CH_MAPPED)) return (1); evp = &gp->i_event[gp->i_next]; goto nomap; } if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED)) return (1); goto newmap; } /* Else, push the characters on the queue and return one. */ if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED | CH_NOMAP)) return (1); goto nomap; } /* * v_sync -- * Walk the screen lists, sync'ing files to their backup copies. */ static void v_sync(SCR *sp, int flags) { GS *gp; gp = sp->gp; TAILQ_FOREACH(sp, gp->dq, q) rcv_sync(sp, flags); TAILQ_FOREACH(sp, gp->hq, q) rcv_sync(sp, flags); } +/* + * alt_key_notation -- + * Lookup for alternative notations of control characters. + */ +static const char* +alt_key_notation(int ch) +{ + switch (ch) { + case 0x00: + return ""; + case 0x01: + return ""; + case 0x02: + return ""; + case 0x03: + return ""; + case 0x04: + return ""; + case 0x05: + return ""; + case 0x06: + return ""; + case 0x07: + return ""; + case 0x08: + return ""; + case 0x09: + return ""; + case 0x0A: + return ""; + case 0x0B: + return ""; + case 0x0C: + return ""; + case 0x0D: + return ""; + case 0x0E: + return ""; + case 0x0F: + return ""; + case 0x10: + return ""; + case 0x11: + return ""; + case 0x12: + return ""; + case 0x13: + return ""; + case 0x14: + return ""; + case 0x15: + return ""; + case 0x16: + return ""; + case 0x17: + return ""; + case 0x18: + return ""; + case 0x19: + return ""; + case 0x1A: + return ""; + case 0x1B: + return ""; + case 0x1C: + return ""; + case 0x1D: + return ""; + case 0x1E: + return ""; + case 0x1F: + return ""; + case 0x7f: + return ""; + default: + __builtin_unreachable(); + } +} + /* * v_event_err -- * Unexpected event. * * PUBLIC: void v_event_err(SCR *, EVENT *); */ void v_event_err(SCR *sp, EVENT *evp) { switch (evp->e_event) { case E_CHARACTER: msgq(sp, M_ERR, "276|Unexpected character event"); break; case E_EOF: msgq(sp, M_ERR, "277|Unexpected end-of-file event"); break; case E_INTERRUPT: msgq(sp, M_ERR, "279|Unexpected interrupt event"); break; case E_REPAINT: msgq(sp, M_ERR, "281|Unexpected repaint event"); break; case E_STRING: msgq(sp, M_ERR, "285|Unexpected string event"); break; case E_TIMEOUT: msgq(sp, M_ERR, "286|Unexpected timeout event"); break; case E_WRESIZE: msgq(sp, M_ERR, "316|Unexpected resize event"); break; /* * Theoretically, none of these can occur, as they're handled at the * top editor level. */ case E_ERR: case E_SIGHUP: case E_SIGTERM: default: abort(); } /* Free any allocated memory. */ free(evp->e_asp); } /* * v_event_flush -- * Flush any flagged keys, returning if any keys were flushed. * * PUBLIC: int v_event_flush(SCR *, u_int); */ int v_event_flush(SCR *sp, u_int flags) { GS *gp; int rval; for (rval = 0, gp = sp->gp; gp->i_cnt != 0 && F_ISSET(&gp->i_event[gp->i_next].e_ch, flags); rval = 1) QREM(1); return (rval); } /* * v_event_grow -- * Grow the terminal queue. */ static int v_event_grow(SCR *sp, int add) { GS *gp; size_t new_nelem, olen; gp = sp->gp; new_nelem = gp->i_nelem + add; olen = gp->i_nelem * sizeof(gp->i_event[0]); BINC_RET(sp, EVENT, gp->i_event, olen, new_nelem * sizeof(gp->i_event[0])); gp->i_nelem = olen / sizeof(gp->i_event[0]); return (0); } /* * v_key_cmp -- * Compare two keys for sorting. */ static int v_key_cmp(const void *ap, const void *bp) { return (((KEYLIST *)ap)->ch - ((KEYLIST *)bp)->ch); } diff --git a/contrib/nvi/common/main.c b/contrib/nvi/common/main.c index a7e60f1af806..807dbde3895c 100644 --- a/contrib/nvi/common/main.c +++ b/contrib/nvi/common/main.c @@ -1,576 +1,575 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" #include "pathnames.h" static void attach(GS *); static int v_obsolete(char *[]); /* * editor -- * Main editor routine. * * PUBLIC: int editor(GS *, int, char *[]); */ int editor(GS *gp, int argc, char *argv[]) { extern int optind; extern char *optarg; const char *p; EVENT ev; FREF *frp; SCR *sp; size_t len; u_int flags; int ch, flagchk, lflag, secure, startup, readonly, rval, silent; char *tag_f, *wsizearg; CHAR_T *w, path[256]; size_t wlen; /* Initialize the busy routine, if not defined by the screen. */ if (gp->scr_busy == NULL) gp->scr_busy = vs_busy; /* Initialize the message routine, if not defined by the screen. */ if (gp->scr_msg == NULL) gp->scr_msg = vs_msg; gp->catd = (nl_catd)-1; /* Common global structure initialization. */ TAILQ_INIT(gp->dq); TAILQ_INIT(gp->hq); SLIST_INIT(gp->ecq); SLIST_INSERT_HEAD(gp->ecq, &gp->excmd, q); gp->noprint = DEFAULT_NOPRINT; /* Structures shared by screens so stored in the GS structure. */ TAILQ_INIT(gp->frefq); TAILQ_INIT(gp->dcb_store.textq); SLIST_INIT(gp->cutq); SLIST_INIT(gp->seqq); /* Set initial screen type and mode based on the program name. */ readonly = 0; if (!strcmp(getprogname(), "ex") || !strcmp(getprogname(), "nex")) LF_INIT(SC_EX); else { /* Nview, view are readonly. */ if (!strcmp(getprogname(), "nview") || !strcmp(getprogname(), "view")) readonly = 1; /* Vi is the default. */ LF_INIT(SC_VI); } /* Convert old-style arguments into new-style ones. */ if (v_obsolete(argv)) return (1); /* Parse the arguments. */ flagchk = '\0'; tag_f = wsizearg = NULL; lflag = secure = silent = 0; startup = 1; /* Set the file snapshot flag. */ F_SET(gp, G_SNAPSHOT); #ifdef DEBUG while ((ch = getopt(argc, argv, "c:D:eFlRrSsT:t:vw:")) != EOF) #else while ((ch = getopt(argc, argv, "c:eFlRrSst:vw:")) != EOF) #endif switch (ch) { case 'c': /* Run the command. */ /* * XXX * We should support multiple -c options. */ if (gp->c_option != NULL) { warnx("only one -c command may be specified."); return (1); } gp->c_option = optarg; break; #ifdef DEBUG case 'D': switch (optarg[0]) { case 's': startup = 0; break; case 'w': attach(gp); break; default: warnx("usage: -D requires s or w argument."); return (1); } break; #endif case 'e': /* Ex mode. */ LF_CLR(SC_VI); LF_SET(SC_EX); break; case 'F': /* No snapshot. */ F_CLR(gp, G_SNAPSHOT); break; case 'l': /* Set lisp, showmatch options. */ lflag = 1; break; case 'R': /* Readonly. */ readonly = 1; break; case 'r': /* Recover. */ if (flagchk == 't') { warnx("only one of -r and -t may be specified."); return (1); } flagchk = 'r'; break; case 'S': secure = 1; break; case 's': silent = 1; break; #ifdef DEBUG case 'T': /* Trace. */ if ((gp->tracefp = fopen(optarg, "w")) == NULL) { warn("%s", optarg); goto err; } (void)fprintf(gp->tracefp, "\n===\ntrace: open %s\n", optarg); break; #endif case 't': /* Tag. */ if (flagchk == 'r') { warnx("only one of -r and -t may be specified."); return (1); } if (flagchk == 't') { warnx("only one tag file may be specified."); return (1); } flagchk = 't'; tag_f = optarg; break; case 'v': /* Vi mode. */ LF_CLR(SC_EX); LF_SET(SC_VI); break; case 'w': wsizearg = optarg; break; case '?': default: (void)gp->scr_usage(); return (1); } argc -= optind; argv += optind; /* * -s option is only meaningful to ex. * * If not reading from a terminal, it's like -s was specified. */ if (silent && !LF_ISSET(SC_EX)) { warnx("-s option is only applicable to ex."); goto err; } if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED)) silent = 1; /* * Build and initialize the first/current screen. This is a bit * tricky. If an error is returned, we may or may not have a * screen structure. If we have a screen structure, put it on a * display queue so that the error messages get displayed. * * !!! * Everything we do until we go interactive is done in ex mode. */ if (screen_init(gp, NULL, &sp)) { if (sp != NULL) TAILQ_INSERT_HEAD(gp->dq, sp, q); goto err; } F_SET(sp, SC_EX); TAILQ_INSERT_HEAD(gp->dq, sp, q); if (v_key_init(sp)) /* Special key initialization. */ goto err; { int oargs[5], *oargp = oargs; if (lflag) { /* Command-line options. */ *oargp++ = O_LISP; *oargp++ = O_SHOWMATCH; } if (readonly) *oargp++ = O_READONLY; if (secure) *oargp++ = O_SECURE; *oargp = -1; /* Options initialization. */ if (opts_init(sp, oargs)) goto err; } if (wsizearg != NULL) { ARGS *av[2], a, b; (void)SPRINTF(path, SIZE(path), L("window=%s"), wsizearg); a.bp = (CHAR_T *)path; a.len = SIZE(path); b.bp = NULL; b.len = 0; av[0] = &a; av[1] = &b; (void)opts_set(sp, av, NULL); } if (silent) { /* Ex batch mode option values. */ O_CLR(sp, O_AUTOPRINT); O_CLR(sp, O_PROMPT); O_CLR(sp, O_VERBOSE); O_CLR(sp, O_WARN); F_SET(sp, SC_EX_SILENT); } sp->rows = O_VAL(sp, O_LINES); /* Make ex formatting work. */ sp->cols = O_VAL(sp, O_COLUMNS); if (!silent && startup) { /* Read EXINIT, exrc files. */ if (ex_exrc(sp)) goto err; if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } /* * List recovery files if -r specified without file arguments. * Note, options must be initialized and startup information * read before doing this. */ if (flagchk == 'r' && argv[0] == NULL) { if (rcv_list(sp)) goto err; if (screen_end(sp)) goto err; goto done; } /* * !!! * Initialize the default ^D, ^U scrolling value here, after the * user has had every opportunity to set the window option. * * It's historic practice that changing the value of the window * option did not alter the default scrolling value, only giving * a count to ^D/^U did that. */ sp->defscroll = (O_VAL(sp, O_WINDOW) + 1) / 2; /* * If we don't have a command-line option, switch into the right * editor now, so that we position default files correctly, and * so that any tags file file-already-locked messages are in the * vi screen, not the ex screen. * * XXX * If we have a command-line option, the error message can end * up in the wrong place, but I think that the combination is * unlikely. */ if (gp->c_option == NULL) { F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI)); } /* Open a tag file if specified. */ if (tag_f != NULL) { CHAR2INT(sp, tag_f, strlen(tag_f) + 1, w, wlen); if (ex_tag_first(sp, w)) goto err; } /* * Append any remaining arguments as file names. Files are recovery * files if -r specified. If the tag option or ex startup commands * loaded a file, then any file arguments are going to come after it. */ if (*argv != NULL) { if (sp->frp != NULL) { /* Cheat -- we know we have an extra argv slot. */ *--argv = strdup(sp->frp->name); if (*argv == NULL) { warn(NULL); goto err; } } sp->argv = sp->cargv = argv; F_SET(sp, SC_ARGNOFREE); if (flagchk == 'r') F_SET(sp, SC_ARGRECOVER); } /* * If the ex startup commands and or/the tag option haven't already * created a file, create one. If no command-line files were given, * use a temporary file. */ if (sp->frp == NULL) { if (sp->argv == NULL) { if ((frp = file_add(sp, NULL)) == NULL) goto err; } else { if ((frp = file_add(sp, sp->argv[0])) == NULL) goto err; if (F_ISSET(sp, SC_ARGRECOVER)) F_SET(frp, FR_RECOVER); } if (file_init(sp, frp, NULL, 0)) goto err; if (EXCMD_RUNNING(gp)) { (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } } /* * Check to see if we need to wait for ex. If SC_SCR_EX is set, ex * was forced to initialize the screen during startup. We'd like to * wait for a single character from the user, but we can't because * we're not in raw mode. We can't switch to raw mode because the * vi initialization will switch to xterm's alternate screen, causing * us to lose the messages we're pausing to make sure the user read. * So, wait for a complete line. */ if (F_ISSET(sp, SC_SCR_EX)) { p = msg_cmsg(sp, CMSG_CONT_R, &len); (void)write(STDOUT_FILENO, p, len); for (;;) { if (v_event_get(sp, &ev, 0, 0)) goto err; if (ev.e_event == E_INTERRUPT || (ev.e_event == E_CHARACTER && (ev.e_value == K_CR || ev.e_value == K_NL))) break; (void)gp->scr_bell(sp); } } /* Switch into the right editor, regardless. */ F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI) | SC_STATUS_CNT); /* * Main edit loop. Vi handles split screens itself, we only return * here when switching editor modes or restarting the screen. */ while (sp != NULL) if (F_ISSET(sp, SC_EX) ? ex(&sp) : vi(&sp)) goto err; done: rval = 0; if (0) err: rval = 1; /* Clean out the global structure. */ v_end(gp); return (rval); } /* * v_end -- * End the program, discarding screens and most of the global area. * * PUBLIC: void v_end(GS *); */ void -v_end(gp) - GS *gp; +v_end(GS *gp) { MSGS *mp; SCR *sp; /* If there are any remaining screens, kill them off. */ if (gp->ccl_sp != NULL) { (void)file_end(gp->ccl_sp, NULL, 1); (void)screen_end(gp->ccl_sp); } while ((sp = TAILQ_FIRST(gp->dq)) != NULL) (void)screen_end(sp); while ((sp = TAILQ_FIRST(gp->hq)) != NULL) (void)screen_end(sp); #if defined(DEBUG) || defined(PURIFY) { FREF *frp; /* Free FREF's. */ while ((frp = TAILQ_FIRST(gp->frefq)) != NULL) { TAILQ_REMOVE(gp->frefq, frp, q); free(frp->name); free(frp->tname); free(frp); } } /* Free key input queue. */ free(gp->i_event); /* Free cut buffers. */ cut_close(gp); /* Free map sequences. */ seq_close(gp); /* Free default buffer storage. */ (void)text_lfree(gp->dcb_store.textq); /* Close message catalogs. */ msg_close(gp); #endif /* Ring the bell if scheduled. */ if (F_ISSET(gp, G_BELLSCHED)) (void)fprintf(stderr, "\07"); /* \a */ /* * Flush any remaining messages. If a message is here, it's almost * certainly the message about the event that killed us (although * it's possible that the user is sourcing a file that exits from the * editor). */ while ((mp = SLIST_FIRST(gp->msgq)) != NULL) { (void)fprintf(stderr, "%s%.*s", mp->mtype == M_ERR ? "ex/vi: " : "", (int)mp->len, mp->buf); SLIST_REMOVE_HEAD(gp->msgq, q); #if defined(DEBUG) || defined(PURIFY) free(mp->buf); free(mp); #endif } #if defined(DEBUG) || defined(PURIFY) /* Free any temporary space. */ free(gp->tmp_bp); #if defined(DEBUG) /* Close debugging file descriptor. */ if (gp->tracefp != NULL) (void)fclose(gp->tracefp); #endif #endif } /* * v_obsolete -- * Convert historic arguments into something getopt(3) will like. */ static int v_obsolete(char *argv[]) { size_t len; char *p; /* * Translate old style arguments into something getopt will like. * Make sure it's not text space memory, because ex modifies the * strings. * Change "+" into "-c$". * Change "+" into "-c". * Change "-" into "-s" * The c, T, t and w options take arguments so they can't be * special arguments. * * Stop if we find "--" as an argument, the user may want to edit * a file named "+foo". */ while (*++argv && strcmp(argv[0], "--")) if (argv[0][0] == '+') { if (argv[0][1] == '\0') { argv[0] = strdup("-c$"); if (argv[0] == NULL) goto nomem; } else { p = argv[0]; len = strlen(argv[0]); argv[0] = malloc(len + 2); if (argv[0] == NULL) goto nomem; argv[0][0] = '-'; argv[0][1] = 'c'; (void)strlcpy(argv[0] + 2, p + 1, len); } } else if (argv[0][0] == '-') { if (argv[0][1] == '\0') { argv[0] = strdup("-s"); if (argv[0] == NULL) { nomem: warn(NULL); return (1); } } else if ((argv[0][1] == 'c' || argv[0][1] == 'T' || argv[0][1] == 't' || argv[0][1] == 'w') && argv[0][2] == '\0') ++argv; } return (0); } #ifdef DEBUG static void attach(GS *gp) { int fd; char ch; if ((fd = open(_PATH_TTY, O_RDONLY, 0)) < 0) { warn("%s", _PATH_TTY); return; } (void)printf("process %lu waiting, enter to continue: ", (u_long)getpid()); (void)fflush(stdout); do { if (read(fd, &ch, 1) != 1) { (void)close(fd); return; } } while (ch != '\n' && ch != '\r'); (void)close(fd); } #endif diff --git a/contrib/nvi/common/options.c b/contrib/nvi/common/options.c index d5c039f97228..c3d1f7343f9a 100644 --- a/contrib/nvi/common/options.c +++ b/contrib/nvi/common/options.c @@ -1,1166 +1,1168 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" #include "pathnames.h" static int opts_abbcmp(const void *, const void *); static int opts_cmp(const void *, const void *); static int opts_print(SCR *, OPTLIST const *); #ifdef USE_WIDECHAR #define OPT_WC 0 #else #define OPT_WC (OPT_NOSAVE | OPT_NDISP) #endif /* * O'Reilly noted options and abbreviations are from "Learning the VI Editor", * Fifth Edition, May 1992. There's no way of knowing what systems they are * actually from. * * HPUX noted options and abbreviations are from "The Ultimate Guide to the * VI and EX Text Editors", 1990. */ OPTLIST const optlist[] = { +/* O_ALTNOTATION */ + {L("altnotation"), f_print, OPT_0BOOL, 0}, /* O_ALTWERASE 4.4BSD */ {L("altwerase"), f_altwerase, OPT_0BOOL, 0}, /* O_AUTOINDENT 4BSD */ {L("autoindent"), NULL, OPT_0BOOL, 0}, /* O_AUTOPRINT 4BSD */ {L("autoprint"), NULL, OPT_1BOOL, 0}, /* O_AUTOWRITE 4BSD */ {L("autowrite"), NULL, OPT_0BOOL, 0}, /* O_BACKUP 4.4BSD */ {L("backup"), NULL, OPT_STR, 0}, /* O_BEAUTIFY 4BSD */ {L("beautify"), NULL, OPT_0BOOL, 0}, /* O_CDPATH 4.4BSD */ {L("cdpath"), NULL, OPT_STR, 0}, /* O_CEDIT 4.4BSD */ {L("cedit"), NULL, OPT_STR, 0}, /* O_COLUMNS 4.4BSD */ {L("columns"), f_columns, OPT_NUM, OPT_NOSAVE}, /* O_COMBINED */ {L("combined"), NULL, OPT_0BOOL, OPT_NOSET|OPT_WC}, /* O_COMMENT 4.4BSD */ {L("comment"), NULL, OPT_0BOOL, 0}, /* O_TMPDIR 4BSD */ {L("directory"), NULL, OPT_STR, 0}, /* O_EDCOMPATIBLE 4BSD */ {L("edcompatible"),NULL, OPT_0BOOL, 0}, /* O_ERRORBELLS 4BSD */ {L("errorbells"), NULL, OPT_0BOOL, 0}, /* O_ESCAPETIME 4.4BSD */ {L("escapetime"), NULL, OPT_NUM, 0}, /* O_EXPANDTAB NetBSD 5.0 */ {L("expandtab"), NULL, OPT_0BOOL, 0}, /* O_EXRC System V (undocumented) */ {L("exrc"), NULL, OPT_0BOOL, 0}, /* O_EXTENDED 4.4BSD */ {L("extended"), f_recompile, OPT_0BOOL, 0}, /* O_FILEC 4.4BSD */ {L("filec"), NULL, OPT_STR, 0}, /* O_FILEENCODING */ {L("fileencoding"),f_encoding, OPT_STR, OPT_WC}, /* O_FLASH HPUX */ {L("flash"), NULL, OPT_1BOOL, 0}, /* O_HARDTABS 4BSD */ {L("hardtabs"), NULL, OPT_NUM, 0}, /* O_ICLOWER 4.4BSD */ {L("iclower"), f_recompile, OPT_0BOOL, 0}, /* O_IGNORECASE 4BSD */ {L("ignorecase"), f_recompile, OPT_0BOOL, 0}, /* O_INPUTENCODING */ {L("inputencoding"),f_encoding, OPT_STR, OPT_WC}, /* O_KEYTIME 4.4BSD */ {L("keytime"), NULL, OPT_NUM, 0}, /* O_LEFTRIGHT 4.4BSD */ {L("leftright"), f_reformat, OPT_0BOOL, 0}, /* O_LINES 4.4BSD */ {L("lines"), f_lines, OPT_NUM, OPT_NOSAVE}, /* O_LISP 4BSD * XXX * When the lisp option is implemented, delete the OPT_NOSAVE flag, * so that :mkexrc dumps it. */ {L("lisp"), f_lisp, OPT_0BOOL, OPT_NOSAVE}, /* O_LIST 4BSD */ {L("list"), f_reformat, OPT_0BOOL, 0}, /* O_LOCKFILES 4.4BSD * XXX * Locking isn't reliable enough over NFS to require it, in addition, * it's a serious startup performance problem over some remote links. */ {L("lock"), NULL, OPT_1BOOL, 0}, /* O_MAGIC 4BSD */ {L("magic"), NULL, OPT_1BOOL, 0}, /* O_MATCHCHARS NetBSD 2.0 */ {L("matchchars"), NULL, OPT_STR, OPT_PAIRS}, /* O_MATCHTIME 4.4BSD */ {L("matchtime"), NULL, OPT_NUM, 0}, /* O_MESG 4BSD */ {L("mesg"), NULL, OPT_1BOOL, 0}, /* O_MODELINE 4BSD * !!! * This has been documented in historical systems as both "modeline" * and as "modelines". Regardless of the name, this option represents * a security problem of mammoth proportions, not to mention a stunning * example of what your intro CS professor referred to as the perils of * mixing code and data. Don't add it, or I will kill you. */ {L("modeline"), NULL, OPT_0BOOL, OPT_NOSET}, /* O_MSGCAT 4.4BSD */ {L("msgcat"), f_msgcat, OPT_STR, 0}, /* O_NOPRINT 4.4BSD */ {L("noprint"), f_print, OPT_STR, 0}, /* O_NUMBER 4BSD */ {L("number"), f_reformat, OPT_0BOOL, 0}, /* O_OCTAL 4.4BSD */ {L("octal"), f_print, OPT_0BOOL, 0}, /* O_OPEN 4BSD */ {L("open"), NULL, OPT_1BOOL, 0}, /* O_OPTIMIZE 4BSD */ {L("optimize"), NULL, OPT_1BOOL, 0}, /* O_PARAGRAPHS 4BSD */ {L("paragraphs"), NULL, OPT_STR, OPT_PAIRS}, /* O_PATH 4.4BSD */ {L("path"), NULL, OPT_STR, 0}, /* O_PRINT 4.4BSD */ {L("print"), f_print, OPT_STR, 0}, /* O_PROMPT 4BSD */ {L("prompt"), NULL, OPT_1BOOL, 0}, /* O_READONLY 4BSD (undocumented) */ {L("readonly"), f_readonly, OPT_0BOOL, OPT_ALWAYS}, /* O_RECDIR 4.4BSD */ {L("recdir"), NULL, OPT_STR, 0}, /* O_REDRAW 4BSD */ {L("redraw"), NULL, OPT_0BOOL, 0}, /* O_REMAP 4BSD */ {L("remap"), NULL, OPT_1BOOL, 0}, /* O_REPORT 4BSD */ {L("report"), NULL, OPT_NUM, 0}, /* O_RULER 4.4BSD */ {L("ruler"), NULL, OPT_0BOOL, 0}, /* O_SCROLL 4BSD */ {L("scroll"), NULL, OPT_NUM, 0}, /* O_SEARCHINCR 4.4BSD */ {L("searchincr"), NULL, OPT_0BOOL, 0}, /* O_SECTIONS 4BSD */ {L("sections"), NULL, OPT_STR, OPT_PAIRS}, /* O_SECURE 4.4BSD */ {L("secure"), NULL, OPT_0BOOL, OPT_NOUNSET}, /* O_SHELL 4BSD */ {L("shell"), NULL, OPT_STR, 0}, /* O_SHELLMETA 4.4BSD */ {L("shellmeta"), NULL, OPT_STR, 0}, /* O_SHIFTWIDTH 4BSD */ {L("shiftwidth"), NULL, OPT_NUM, OPT_NOZERO}, /* O_SHOWMATCH 4BSD */ {L("showmatch"), NULL, OPT_0BOOL, 0}, /* O_SHOWMODE 4.4BSD */ {L("showmode"), NULL, OPT_0BOOL, 0}, /* O_SIDESCROLL 4.4BSD */ {L("sidescroll"), NULL, OPT_NUM, OPT_NOZERO}, /* O_SLOWOPEN 4BSD */ {L("slowopen"), NULL, OPT_0BOOL, 0}, /* O_SOURCEANY 4BSD (undocumented) * !!! * Historic vi, on startup, source'd $HOME/.exrc and ./.exrc, if they * were owned by the user. The sourceany option was an undocumented * feature of historic vi which permitted the startup source'ing of * .exrc files the user didn't own. This is an obvious security problem, * and we ignore the option. */ {L("sourceany"), NULL, OPT_0BOOL, OPT_NOSET}, /* O_TABSTOP 4BSD */ {L("tabstop"), f_reformat, OPT_NUM, OPT_NOZERO}, /* O_TAGLENGTH 4BSD */ {L("taglength"), NULL, OPT_NUM, 0}, /* O_TAGS 4BSD */ {L("tags"), NULL, OPT_STR, 0}, /* O_TERM 4BSD * !!! * By default, the historic vi always displayed information about two * options, redraw and term. Term seems sufficient. */ {L("term"), NULL, OPT_STR, OPT_ADISP|OPT_NOSAVE}, /* O_TERSE 4BSD */ {L("terse"), NULL, OPT_0BOOL, 0}, /* O_TILDEOP 4.4BSD */ {L("tildeop"), NULL, OPT_0BOOL, 0}, /* O_TIMEOUT 4BSD (undocumented) */ {L("timeout"), NULL, OPT_1BOOL, 0}, /* O_TTYWERASE 4.4BSD */ {L("ttywerase"), f_ttywerase, OPT_0BOOL, 0}, /* O_VERBOSE 4.4BSD */ {L("verbose"), NULL, OPT_0BOOL, 0}, /* O_W1200 4BSD */ {L("w1200"), f_w1200, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_W300 4BSD */ {L("w300"), f_w300, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_W9600 4BSD */ {L("w9600"), f_w9600, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_WARN 4BSD */ {L("warn"), NULL, OPT_1BOOL, 0}, /* O_WINDOW 4BSD */ {L("window"), f_window, OPT_NUM, 0}, /* O_WINDOWNAME 4BSD */ {L("windowname"), NULL, OPT_0BOOL, 0}, /* O_WRAPLEN 4.4BSD */ {L("wraplen"), NULL, OPT_NUM, 0}, /* O_WRAPMARGIN 4BSD */ {L("wrapmargin"), NULL, OPT_NUM, 0}, /* O_WRAPSCAN 4BSD */ {L("wrapscan"), NULL, OPT_1BOOL, 0}, /* O_WRITEANY 4BSD */ {L("writeany"), NULL, OPT_0BOOL, 0}, {NULL}, }; typedef struct abbrev { CHAR_T *name; int offset; } OABBREV; static OABBREV const abbrev[] = { {L("ai"), O_AUTOINDENT}, /* 4BSD */ {L("ap"), O_AUTOPRINT}, /* 4BSD */ {L("aw"), O_AUTOWRITE}, /* 4BSD */ {L("bf"), O_BEAUTIFY}, /* 4BSD */ {L("co"), O_COLUMNS}, /* 4.4BSD */ {L("dir"), O_TMPDIR}, /* 4BSD */ {L("eb"), O_ERRORBELLS}, /* 4BSD */ {L("ed"), O_EDCOMPATIBLE}, /* 4BSD */ {L("et"), O_EXPANDTAB}, /* NetBSD 5.0 */ {L("ex"), O_EXRC}, /* System V (undocumented) */ {L("fe"), O_FILEENCODING}, {L("ht"), O_HARDTABS}, /* 4BSD */ {L("ic"), O_IGNORECASE}, /* 4BSD */ {L("ie"), O_INPUTENCODING}, {L("li"), O_LINES}, /* 4.4BSD */ {L("modelines"), O_MODELINE}, /* HPUX */ {L("nu"), O_NUMBER}, /* 4BSD */ {L("opt"), O_OPTIMIZE}, /* 4BSD */ {L("para"), O_PARAGRAPHS}, /* 4BSD */ {L("re"), O_REDRAW}, /* O'Reilly */ {L("ro"), O_READONLY}, /* 4BSD (undocumented) */ {L("scr"), O_SCROLL}, /* 4BSD (undocumented) */ {L("sect"), O_SECTIONS}, /* O'Reilly */ {L("sh"), O_SHELL}, /* 4BSD */ {L("slow"), O_SLOWOPEN}, /* 4BSD */ {L("sm"), O_SHOWMATCH}, /* 4BSD */ {L("smd"), O_SHOWMODE}, /* 4BSD */ {L("sw"), O_SHIFTWIDTH}, /* 4BSD */ {L("tag"), O_TAGS}, /* 4BSD (undocumented) */ {L("tl"), O_TAGLENGTH}, /* 4BSD */ {L("to"), O_TIMEOUT}, /* 4BSD (undocumented) */ {L("ts"), O_TABSTOP}, /* 4BSD */ {L("tty"), O_TERM}, /* 4BSD (undocumented) */ {L("ttytype"), O_TERM}, /* 4BSD (undocumented) */ {L("w"), O_WINDOW}, /* O'Reilly */ {L("wa"), O_WRITEANY}, /* 4BSD */ {L("wi"), O_WINDOW}, /* 4BSD (undocumented) */ {L("wl"), O_WRAPLEN}, /* 4.4BSD */ {L("wm"), O_WRAPMARGIN}, /* 4BSD */ {L("ws"), O_WRAPSCAN}, /* 4BSD */ {NULL}, }; /* * opts_init -- * Initialize some of the options. * * PUBLIC: int opts_init(SCR *, int *); */ int opts_init(SCR *sp, int *oargs) { ARGS *argv[2], a, b; OPTLIST const *op; u_long v; int cnt, optindx = 0; char *s; CHAR_T b2[1024]; a.bp = b2; b.bp = NULL; a.len = b.len = 0; argv[0] = &a; argv[1] = &b; /* Set numeric and string default values. */ #define OI(indx, str) do { \ a.len = STRLEN(str); \ if ((CHAR_T*)str != b2) /* GCC puts strings in text-space. */ \ (void)MEMCPY(b2, str, a.len+1); \ if (opts_set(sp, argv, NULL)) { \ optindx = indx; \ goto err; \ } \ } while (0) /* * Indirect global options to global space. Specifically, set up * terminal, lines, columns first, they're used by other options. * Note, don't set the flags until we've set up the indirection. */ if (o_set(sp, O_TERM, 0, NULL, GO_TERM)) goto err; F_SET(&sp->opts[O_TERM], OPT_GLOBAL); if (o_set(sp, O_LINES, 0, NULL, GO_LINES)) goto err; F_SET(&sp->opts[O_LINES], OPT_GLOBAL); if (o_set(sp, O_COLUMNS, 0, NULL, GO_COLUMNS)) goto err; F_SET(&sp->opts[O_COLUMNS], OPT_GLOBAL); if (o_set(sp, O_SECURE, 0, NULL, GO_SECURE)) goto err; F_SET(&sp->opts[O_SECURE], OPT_GLOBAL); /* Initialize string values. */ (void)SPRINTF(b2, SIZE(b2), L("cdpath=%s"), (s = getenv("CDPATH")) == NULL ? ":" : s); OI(O_CDPATH, b2); OI(O_CEDIT, L("cedit=\033")); /* * !!! * Vi historically stored temporary files in /var/tmp. We store them * in /tmp by default, hoping it's a memory based file system. There * are two ways to change this -- the user can set either the directory * option or the TMPDIR environmental variable. */ (void)SPRINTF(b2, SIZE(b2), L("directory=%s"), (s = getenv("TMPDIR")) == NULL ? _PATH_TMP : s); OI(O_TMPDIR, b2); OI(O_ESCAPETIME, L("escapetime=6")); OI(O_FILEC, L("filec=\t")); OI(O_KEYTIME, L("keytime=6")); OI(O_MATCHCHARS, L("matchchars=()[]{}")); OI(O_MATCHTIME, L("matchtime=7")); (void)SPRINTF(b2, SIZE(b2), L("msgcat=%s"), _PATH_MSGCAT); OI(O_MSGCAT, b2); OI(O_REPORT, L("report=5")); OI(O_PARAGRAPHS, L("paragraphs=IPLPPPQPP LIpplpipbp")); (void)SPRINTF(b2, SIZE(b2), L("path=%s"), ""); OI(O_PATH, b2); (void)SPRINTF(b2, SIZE(b2), L("recdir=%s"), NVI_PATH_PRESERVE); OI(O_RECDIR, b2); OI(O_SECTIONS, L("sections=NHSHH HUnhsh")); (void)SPRINTF(b2, SIZE(b2), L("shell=%s"), (s = getenv("SHELL")) == NULL ? _PATH_BSHELL : s); OI(O_SHELL, b2); OI(O_SHELLMETA, L("shellmeta=~{[*?$`'\"\\")); OI(O_SHIFTWIDTH, L("shiftwidth=8")); OI(O_SIDESCROLL, L("sidescroll=16")); OI(O_TABSTOP, L("tabstop=8")); (void)SPRINTF(b2, SIZE(b2), L("tags=%s"), _PATH_TAGS); OI(O_TAGS, b2); /* * XXX * Initialize O_SCROLL here, after term; initializing term should * have created a LINES/COLUMNS value. */ if ((v = (O_VAL(sp, O_LINES) - 1) / 2) == 0) v = 1; (void)SPRINTF(b2, SIZE(b2), L("scroll=%ld"), v); OI(O_SCROLL, b2); /* * The default window option values are: * 8 if baud rate <= 600 * 16 if baud rate <= 1200 * LINES - 1 if baud rate > 1200 * * Note, the windows option code will correct any too-large value * or when the O_LINES value is 1. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v <= 600) v = 8; else if (v <= 1200) v = 16; else if ((v = O_VAL(sp, O_LINES) - 1) == 0) v = 1; (void)SPRINTF(b2, SIZE(b2), L("window=%lu"), v); OI(O_WINDOW, b2); /* * Set boolean default values, and copy all settings into the default * information. OS_NOFREE is set, we're copying, not replacing. */ for (op = optlist, cnt = 0; op->name != NULL; ++op, ++cnt) { if (F_ISSET(op, OPT_GLOBAL)) continue; switch (op->type) { case OPT_0BOOL: break; case OPT_1BOOL: O_SET(sp, cnt); O_D_SET(sp, cnt); break; case OPT_NUM: o_set(sp, cnt, OS_DEF, NULL, O_VAL(sp, cnt)); break; case OPT_STR: if (O_STR(sp, cnt) != NULL && o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STRDUP, O_STR(sp, cnt), 0)) goto err; break; default: abort(); } } /* * !!! * Some options can be initialized by the command name or the * command-line arguments. They don't set the default values, * it's historic practice. */ for (; *oargs != -1; ++oargs) OI(*oargs, optlist[*oargs].name); #undef OI return (0); err: msgq_wstr(sp, M_ERR, optlist[optindx].name, "031|Unable to set default %s option"); return (1); } /* * opts_set -- * Change the values of one or more options. * * PUBLIC: int opts_set(SCR *, ARGS *[], char *); */ int opts_set(SCR *sp, ARGS *argv[], char *usage) { enum optdisp disp; enum nresult nret; OPTLIST const *op; OPTION *spo; u_long isset, turnoff, value; int ch, equals, nf, nf2, offset, qmark, rval; CHAR_T *endp, *name, *p, *sep; char *p2, *t2; char *np; size_t nlen; disp = NO_DISPLAY; for (rval = 0; argv[0]->len != 0; ++argv) { /* * The historic vi dumped the options for each occurrence of * "all" in the set list. Puhleeze. */ if (!STRCMP(argv[0]->bp, L("all"))) { disp = ALL_DISPLAY; continue; } /* Find equals sign or question mark. */ for (sep = NULL, equals = qmark = 0, p = name = argv[0]->bp; (ch = *p) != '\0'; ++p) if (ch == '=' || ch == '?') { if (p == name) { if (usage != NULL) msgq(sp, M_ERR, "032|Usage: %s", usage); return (1); } sep = p; if (ch == '=') equals = 1; else qmark = 1; break; } turnoff = 0; op = NULL; if (sep != NULL) *sep++ = '\0'; /* Search for the name, then name without any leading "no". */ if ((op = opts_search(name)) == NULL && name[0] == 'n' && name[1] == 'o') { turnoff = 1; name += 2; op = opts_search(name); } if (op == NULL) { opts_nomatch(sp, name); rval = 1; continue; } /* Find current option values. */ offset = op - optlist; spo = sp->opts + offset; /* * !!! * Historically, the question mark could be a separate * argument. */ if (!equals && !qmark && argv[1]->len == 1 && argv[1]->bp[0] == '?') { ++argv; qmark = 1; } /* Set name, value. */ switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: /* Some options may not be reset. */ if (F_ISSET(op, OPT_NOUNSET) && turnoff) { msgq_wstr(sp, M_ERR, name, "291|set: the %s option may not be turned off"); rval = 1; break; } /* Some options may not be set. */ if (F_ISSET(op, OPT_NOSET) && !turnoff) { msgq_wstr(sp, M_ERR, name, "313|set: the %s option may never be turned on"); rval = 1; break; } if (equals) { msgq_wstr(sp, M_ERR, name, "034|set: [no]%s option doesn't take a value"); rval = 1; break; } if (qmark) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ isset = !turnoff; if (!F_ISSET(op, OPT_ALWAYS)) { if (isset) { if (O_ISSET(sp, offset)) break; } else if (!O_ISSET(sp, offset)) break; } /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, NULL, &isset)) || ex_optchange(sp, offset, NULL, &isset) || v_optchange(sp, offset, NULL, &isset) || sp->gp->scr_optchange(sp, offset, NULL, &isset)) { rval = 1; break; } /* Set the value. */ if (isset) O_SET(sp, offset); else O_CLR(sp, offset); break; case OPT_NUM: if (turnoff) { msgq_wstr(sp, M_ERR, name, "035|set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } if (!ISDIGIT(sep[0])) goto badnum; if ((nret = nget_uslong(&value, sep, &endp, 10)) != NUM_OK) { INT2CHAR(sp, name, STRLEN(name) + 1, np, nlen); p2 = msg_print(sp, np, &nf); INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); t2 = msg_print(sp, np, &nf2); switch (nret) { case NUM_ERR: msgq(sp, M_SYSERR, "036|set: %s option: %s", p2, t2); break; case NUM_OVER: msgq(sp, M_ERR, "037|set: %s option: %s: value overflow", p2, t2); break; case NUM_OK: case NUM_UNDER: abort(); } if (nf) FREE_SPACE(sp, p2, 0); if (nf2) FREE_SPACE(sp, t2, 0); rval = 1; break; } if (*endp && !cmdskip(*endp)) { badnum: INT2CHAR(sp, name, STRLEN(name) + 1, np, nlen); p2 = msg_print(sp, np, &nf); INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); t2 = msg_print(sp, np, &nf2); msgq(sp, M_ERR, "038|set: %s option: %s is an illegal number", p2, t2); if (nf) FREE_SPACE(sp, p2, 0); if (nf2) FREE_SPACE(sp, t2, 0); rval = 1; break; } /* Some options may never be set to zero. */ if (F_ISSET(op, OPT_NOZERO) && value == 0) { msgq_wstr(sp, M_ERR, name, "314|set: the %s option may never be set to 0"); rval = 1; break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ if (!F_ISSET(op, OPT_ALWAYS) && O_VAL(sp, offset) == value) break; /* Report to subsystems. */ INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); if ((op->func != NULL && op->func(sp, spo, np, &value)) || ex_optchange(sp, offset, np, &value) || v_optchange(sp, offset, np, &value) || sp->gp->scr_optchange(sp, offset, np, &value)) { rval = 1; break; } /* Set the value. */ if (o_set(sp, offset, 0, NULL, value)) rval = 1; break; case OPT_STR: if (turnoff) { msgq_wstr(sp, M_ERR, name, "039|set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* Check for strings that must have even length. */ if (F_ISSET(op, OPT_PAIRS) && STRLEN(sep) & 1) { msgq_wstr(sp, M_ERR, name, "047|The %s option must be in two character groups"); rval = 1; break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ INT2CHAR(sp, sep, STRLEN(sep) + 1, np, nlen); if (!F_ISSET(op, OPT_ALWAYS) && O_STR(sp, offset) != NULL && !strcmp(O_STR(sp, offset), np)) break; /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, np, NULL)) || ex_optchange(sp, offset, np, NULL) || v_optchange(sp, offset, np, NULL) || sp->gp->scr_optchange(sp, offset, np, NULL)) { rval = 1; break; } /* Set the value. */ if (o_set(sp, offset, OS_STRDUP, np, 0)) rval = 1; break; default: abort(); } } if (disp != NO_DISPLAY) opts_dump(sp, disp); return (rval); } /* * o_set -- * Set an option's value. * * PUBLIC: int o_set(SCR *, int, u_int, char *, u_long); */ int o_set(SCR *sp, int opt, u_int flags, char *str, u_long val) { OPTION *op; /* Set a pointer to the options area. */ op = F_ISSET(&sp->opts[opt], OPT_GLOBAL) ? &sp->gp->opts[sp->opts[opt].o_cur.val] : &sp->opts[opt]; /* Copy the string, if requested. */ if (LF_ISSET(OS_STRDUP) && (str = strdup(str)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* Free the previous string, if requested, and set the value. */ if LF_ISSET(OS_DEF) if (LF_ISSET(OS_STR | OS_STRDUP)) { if (!LF_ISSET(OS_NOFREE)) free(op->o_def.str); op->o_def.str = str; } else op->o_def.val = val; else if (LF_ISSET(OS_STR | OS_STRDUP)) { if (!LF_ISSET(OS_NOFREE)) free(op->o_cur.str); op->o_cur.str = str; } else op->o_cur.val = val; return (0); } /* * opts_empty -- * Return 1 if the string option is invalid, 0 if it's OK. * * PUBLIC: int opts_empty(SCR *, int, int); */ int opts_empty(SCR *sp, int off, int silent) { char *p; if ((p = O_STR(sp, off)) == NULL || p[0] == '\0') { if (!silent) msgq_wstr(sp, M_ERR, optlist[off].name, "305|No %s edit option specified"); return (1); } return (0); } /* * opts_dump -- * List the current values of selected options. * * PUBLIC: void opts_dump(SCR *, enum optdisp); */ void opts_dump(SCR *sp, enum optdisp type) { OPTLIST const *op; int base, b_num, cnt, col, colwidth, curlen, s_num; int numcols, numrows, row; int b_op[O_OPTIONCOUNT], s_op[O_OPTIONCOUNT]; char nbuf[20]; /* * Options are output in two groups -- those that fit in a column and * those that don't. Output is done on 6 character "tab" boundaries * for no particular reason. (Since we don't output tab characters, * we can ignore the terminal's tab settings.) Ignore the user's tab * setting because we have no idea how reasonable it is. * * Find a column width we can live with, testing from 10 columns to 1. */ for (numcols = 10; numcols > 1; --numcols) { colwidth = sp->cols / numcols & ~(STANDARD_TAB - 1); if (colwidth >= 10) { colwidth = (colwidth + STANDARD_TAB) & ~(STANDARD_TAB - 1); numcols = sp->cols / colwidth; break; } colwidth = 0; } /* * Get the set of options to list, entering them into * the column list or the overflow list. */ for (b_num = s_num = 0, op = optlist; op->name != NULL; ++op) { cnt = op - optlist; /* If OPT_NDISP set, it's never displayed. */ if (F_ISSET(op, OPT_NDISP)) continue; switch (type) { case ALL_DISPLAY: /* Display all. */ break; case CHANGED_DISPLAY: /* Display changed. */ /* If OPT_ADISP set, it's always "changed". */ if (F_ISSET(op, OPT_ADISP)) break; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: case OPT_NUM: if (O_VAL(sp, cnt) == O_D_VAL(sp, cnt)) continue; break; case OPT_STR: if (O_STR(sp, cnt) == O_D_STR(sp, cnt) || (O_D_STR(sp, cnt) != NULL && !strcmp(O_STR(sp, cnt), O_D_STR(sp, cnt)))) continue; break; } break; case SELECT_DISPLAY: /* Display selected. */ if (!F_ISSET(&sp->opts[cnt], OPT_SELECTED)) continue; break; default: case NO_DISPLAY: abort(); } F_CLR(&sp->opts[cnt], OPT_SELECTED); curlen = STRLEN(op->name); switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: if (!O_ISSET(sp, cnt)) curlen += 2; break; case OPT_NUM: (void)snprintf(nbuf, sizeof(nbuf), "%ld", O_VAL(sp, cnt)); curlen += strlen(nbuf); break; case OPT_STR: if (O_STR(sp, cnt) != NULL) curlen += strlen(O_STR(sp, cnt)); curlen += 3; break; } /* Offset by 2 so there's a gap. */ if (curlen <= colwidth - 2) s_op[s_num++] = cnt; else b_op[b_num++] = cnt; } if (s_num > 0) { /* Figure out the number of rows. */ if (s_num > numcols) { numrows = s_num / numcols; if (s_num % numcols) ++numrows; } else numrows = 1; /* Display the options in sorted order. */ for (row = 0; row < numrows;) { for (base = row, col = 0; col < numcols; ++col) { cnt = opts_print(sp, &optlist[s_op[base]]); if ((base += numrows) >= s_num) break; (void)ex_printf(sp, "%*s", (int)(colwidth - cnt), ""); } if (++row < numrows || b_num) (void)ex_puts(sp, "\n"); } } for (row = 0; row < b_num;) { (void)opts_print(sp, &optlist[b_op[row]]); if (++row < b_num) (void)ex_puts(sp, "\n"); } (void)ex_puts(sp, "\n"); } /* * opts_print -- * Print out an option. */ static int opts_print(SCR *sp, OPTLIST const *op) { int curlen, offset; const char *p; curlen = 0; offset = op - optlist; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: curlen += ex_printf(sp, "%s"WS, O_ISSET(sp, offset) ? "" : "no", op->name); break; case OPT_NUM: curlen += ex_printf(sp, WS"=%ld", op->name, O_VAL(sp, offset)); break; case OPT_STR: curlen += ex_printf(sp, WS"=\"", op->name); p = O_STR(sp, offset); /* Keep correct count for unprintable character sequences */ if (p != NULL) for (; *p != '\0'; ++p) curlen += ex_puts(sp, unctrl(*p)); curlen += ex_puts(sp, "\""); break; } return (curlen); } /* * opts_save -- * Write the current configuration to a file. * * PUBLIC: int opts_save(SCR *, FILE *); */ int opts_save(SCR *sp, FILE *fp) { OPTLIST const *op; CHAR_T ch, *p; char nch, *np; int cnt; for (op = optlist; op->name != NULL; ++op) { if (F_ISSET(op, OPT_NOSAVE)) continue; cnt = op - optlist; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: if (O_ISSET(sp, cnt)) (void)fprintf(fp, "set "WS"\n", op->name); else (void)fprintf(fp, "set no"WS"\n", op->name); break; case OPT_NUM: (void)fprintf(fp, "set "WS"=%-3ld\n", op->name, O_VAL(sp, cnt)); break; case OPT_STR: if (O_STR(sp, cnt) == NULL) break; (void)fprintf(fp, "set "); for (p = op->name; (ch = *p) != '\0'; ++p) { if (cmdskip(ch) || ch == '\\') (void)putc('\\', fp); fprintf(fp, WC, ch); } (void)putc('=', fp); for (np = O_STR(sp, cnt); (nch = *np) != '\0'; ++np) { if (cmdskip(nch) || nch == '\\') (void)putc('\\', fp); (void)putc(nch, fp); } (void)putc('\n', fp); break; } if (ferror(fp)) { msgq(sp, M_SYSERR, NULL); return (1); } } return (0); } /* * opts_search -- * Search for an option. * * PUBLIC: OPTLIST const *opts_search(CHAR_T *); */ OPTLIST const * opts_search(CHAR_T *name) { OPTLIST const *op, *found; OABBREV atmp, *ap; OPTLIST otmp; size_t len; /* Check list of abbreviations. */ atmp.name = name; if ((ap = bsearch(&atmp, abbrev, sizeof(abbrev) / sizeof(OABBREV) - 1, sizeof(OABBREV), opts_abbcmp)) != NULL) return (optlist + ap->offset); /* Check list of options. */ otmp.name = name; if ((op = bsearch(&otmp, optlist, sizeof(optlist) / sizeof(OPTLIST) - 1, sizeof(OPTLIST), opts_cmp)) != NULL) return (op); /* * Check to see if the name is the prefix of one (and only one) * option. If so, return the option. */ len = STRLEN(name); for (found = NULL, op = optlist; op->name != NULL; ++op) { if (op->name[0] < name[0]) continue; if (op->name[0] > name[0]) break; if (!MEMCMP(op->name, name, len)) { if (found != NULL) return (NULL); found = op; } } return (found); } /* * opts_nomatch -- * Standard nomatch error message for options. * * PUBLIC: void opts_nomatch(SCR *, CHAR_T *); */ void opts_nomatch(SCR *sp, CHAR_T *name) { msgq_wstr(sp, M_ERR, name, "033|set: no %s option: 'set all' gives all option values"); } static int opts_abbcmp(const void *a, const void *b) { return(STRCMP(((OABBREV *)a)->name, ((OABBREV *)b)->name)); } static int opts_cmp(const void *a, const void *b) { return(STRCMP(((OPTLIST *)a)->name, ((OPTLIST *)b)->name)); } /* * opts_copy -- * Copy a screen's OPTION array. * * PUBLIC: int opts_copy(SCR *, SCR *); */ int opts_copy(SCR *orig, SCR *sp) { int cnt, rval; /* Copy most everything without change. */ memcpy(sp->opts, orig->opts, sizeof(orig->opts)); /* Copy the string edit options. */ for (cnt = rval = 0; cnt < O_OPTIONCOUNT; ++cnt) { if (optlist[cnt].type != OPT_STR || F_ISSET(&sp->opts[cnt], OPT_GLOBAL)) continue; /* * If never set, or already failed, NULL out the entries -- * have to continue after failure, otherwise would have two * screens referencing the same memory. */ if (rval || O_STR(sp, cnt) == NULL) { o_set(sp, cnt, OS_NOFREE | OS_STR, NULL, 0); o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0); continue; } /* Copy the current string. */ if (o_set(sp, cnt, OS_NOFREE | OS_STRDUP, O_STR(sp, cnt), 0)) { o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0); goto nomem; } /* Copy the default string. */ if (O_D_STR(sp, cnt) != NULL && o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STRDUP, O_D_STR(sp, cnt), 0)) { nomem: msgq(orig, M_SYSERR, NULL); rval = 1; } } return (rval); } /* * opts_free -- * Free all option strings * * PUBLIC: void opts_free(SCR *); */ void opts_free(SCR *sp) { int cnt; for (cnt = 0; cnt < O_OPTIONCOUNT; ++cnt) { if (optlist[cnt].type != OPT_STR || F_ISSET(&sp->opts[cnt], OPT_GLOBAL)) continue; free(O_STR(sp, cnt)); free(O_D_STR(sp, cnt)); } } diff --git a/contrib/nvi/common/options_f.c b/contrib/nvi/common/options_f.c index 45ab913c55ab..fe07e4989f05 100644 --- a/contrib/nvi/common/options_f.c +++ b/contrib/nvi/common/options_f.c @@ -1,293 +1,293 @@ /*- * 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 "common.h" /* * PUBLIC: int f_altwerase(SCR *, OPTION *, char *, u_long *); */ int f_altwerase(SCR *sp, OPTION *op, char *str, u_long *valp) { if (*valp) O_CLR(sp, O_TTYWERASE); return (0); } /* * PUBLIC: int f_columns(SCR *, OPTION *, char *, u_long *); */ int f_columns(SCR *sp, OPTION *op, char *str, u_long *valp) { /* Validate the number. */ if (*valp < MINIMUM_SCREEN_COLS) { msgq(sp, M_ERR, "040|Screen columns too small, less than %d", MINIMUM_SCREEN_COLS); return (1); } /* * !!! * It's not uncommon for allocation of huge chunks of memory to cause * core dumps on various systems. So, we prune out numbers that are * "obviously" wrong. Vi will not work correctly if it has the wrong * number of lines/columns for the screen, but at least we don't drop * core. */ #define MAXIMUM_SCREEN_COLS 500 if (*valp > MAXIMUM_SCREEN_COLS) { msgq(sp, M_ERR, "041|Screen columns too large, greater than %d", MAXIMUM_SCREEN_COLS); return (1); } return (0); } /* * PUBLIC: int f_lines(SCR *, OPTION *, char *, u_long *); */ int f_lines(SCR *sp, OPTION *op, char *str, u_long *valp) { /* Validate the number. */ if (*valp < MINIMUM_SCREEN_ROWS) { msgq(sp, M_ERR, "042|Screen lines too small, less than %d", MINIMUM_SCREEN_ROWS); return (1); } /* * !!! * It's not uncommon for allocation of huge chunks of memory to cause * core dumps on various systems. So, we prune out numbers that are * "obviously" wrong. Vi will not work correctly if it has the wrong * number of lines/columns for the screen, but at least we don't drop * core. */ #define MAXIMUM_SCREEN_ROWS 500 if (*valp > MAXIMUM_SCREEN_ROWS) { msgq(sp, M_ERR, "043|Screen lines too large, greater than %d", MAXIMUM_SCREEN_ROWS); return (1); } /* * Set the value, and the related scroll value. If no window * value set, set a new default window. */ o_set(sp, O_LINES, 0, NULL, *valp); if (*valp == 1) { sp->defscroll = 1; if (O_VAL(sp, O_WINDOW) == O_D_VAL(sp, O_WINDOW) || O_VAL(sp, O_WINDOW) > *valp) { o_set(sp, O_WINDOW, 0, NULL, 1); o_set(sp, O_WINDOW, OS_DEF, NULL, 1); } } else { sp->defscroll = (*valp - 1) / 2; if (O_VAL(sp, O_WINDOW) == O_D_VAL(sp, O_WINDOW) || O_VAL(sp, O_WINDOW) > *valp) { o_set(sp, O_WINDOW, 0, NULL, *valp - 1); o_set(sp, O_WINDOW, OS_DEF, NULL, *valp - 1); } } return (0); } /* * PUBLIC: int f_lisp(SCR *, OPTION *, char *, u_long *); */ int f_lisp(SCR *sp, OPTION *op, char *str, u_long *valp) { msgq(sp, M_ERR, "044|The lisp option is not implemented"); return (0); } /* * PUBLIC: int f_msgcat(SCR *, OPTION *, char *, u_long *); */ int f_msgcat(SCR *sp, OPTION *op, char *str, u_long *valp) { (void)msg_open(sp, str); return (0); } /* * PUBLIC: int f_print(SCR *, OPTION *, char *, u_long *); */ int f_print(SCR *sp, OPTION *op, char *str, u_long *valp) { int offset = op - sp->opts; /* Preset the value, needed for reinitialization of lookup table. */ - if (offset == O_OCTAL) { + if (offset == O_OCTAL || offset == O_ALTNOTATION) { if (*valp) O_SET(sp, offset); else O_CLR(sp, offset); } else if (o_set(sp, offset, OS_STRDUP, str, 0)) return(1); /* Reinitialize the key fast lookup table. */ v_key_ilookup(sp); /* Reformat the screen. */ F_SET(sp, SC_SCR_REFORMAT); return (0); } /* * PUBLIC: int f_readonly(SCR *, OPTION *, char *, u_long *); */ int f_readonly(SCR *sp, OPTION *op, char *str, u_long *valp) { /* * !!! * See the comment in exf.c. */ if (*valp) F_SET(sp, SC_READONLY); else F_CLR(sp, SC_READONLY); return (0); } /* * PUBLIC: int f_recompile(SCR *, OPTION *, char *, u_long *); */ int f_recompile(SCR *sp, OPTION *op, char *str, u_long *valp) { if (F_ISSET(sp, SC_RE_SEARCH)) { regfree(&sp->re_c); F_CLR(sp, SC_RE_SEARCH); } if (F_ISSET(sp, SC_RE_SUBST)) { regfree(&sp->subre_c); F_CLR(sp, SC_RE_SUBST); } return (0); } /* * PUBLIC: int f_reformat(SCR *, OPTION *, char *, u_long *); */ int f_reformat(SCR *sp, OPTION *op, char *str, u_long *valp) { F_SET(sp, SC_SCR_REFORMAT); return (0); } /* * PUBLIC: int f_ttywerase(SCR *, OPTION *, char *, u_long *); */ int f_ttywerase(SCR *sp, OPTION *op, char *str, u_long *valp) { if (*valp) O_CLR(sp, O_ALTWERASE); return (0); } /* * PUBLIC: int f_w300(SCR *, OPTION *, char *, u_long *); */ int f_w300(SCR *sp, OPTION *op, char *str, u_long *valp) { u_long v; /* Historical behavior for w300 was < 1200. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v >= 1200) return (0); return (f_window(sp, op, str, valp)); } /* * PUBLIC: int f_w1200(SCR *, OPTION *, char *, u_long *); */ int f_w1200(SCR *sp, OPTION *op, char *str, u_long *valp) { u_long v; /* Historical behavior for w1200 was == 1200. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v < 1200 || v > 4800) return (0); return (f_window(sp, op, str, valp)); } /* * PUBLIC: int f_w9600(SCR *, OPTION *, char *, u_long *); */ int f_w9600(SCR *sp, OPTION *op, char *str, u_long *valp) { u_long v; /* Historical behavior for w9600 was > 1200. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v <= 4800) return (0); return (f_window(sp, op, str, valp)); } /* * PUBLIC: int f_window(SCR *, OPTION *, char *, u_long *); */ int f_window(SCR *sp, OPTION *op, char *str, u_long *valp) { if (*valp >= O_VAL(sp, O_LINES) - 1 && (*valp = O_VAL(sp, O_LINES) - 1) == 0) *valp = 1; return (0); } /* * PUBLIC: int f_encoding(SCR *, OPTION *, char *, u_long *); */ int f_encoding(SCR *sp, OPTION *op, char *str, u_long *valp) { int offset = op - sp->opts; return conv_enc(sp, offset, str); } diff --git a/contrib/nvi/common/search.c b/contrib/nvi/common/search.c index e8dcac431f51..c3f7291437ff 100644 --- a/contrib/nvi/common/search.c +++ b/contrib/nvi/common/search.c @@ -1,474 +1,479 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "common.h" typedef enum { S_EMPTY, S_EOF, S_NOPREV, S_NOTFOUND, S_SOF, S_WRAP } smsg_t; static void search_msg(SCR *, smsg_t); static int search_init(SCR *, dir_t, CHAR_T *, size_t, CHAR_T **, u_int); /* * search_init -- * Set up a search. */ static int search_init(SCR *sp, dir_t dir, CHAR_T *ptrn, size_t plen, CHAR_T **epp, u_int flags) { recno_t lno; int delim; CHAR_T *p, *t; /* If the file is empty, it's a fast search. */ if (sp->lno <= 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EMPTY); return (1); } } if (LF_ISSET(SEARCH_PARSE)) { /* Parse the string. */ /* * Use the saved pattern if no pattern specified, or if only * one or two delimiter characters specified. * * !!! * Historically, only the pattern itself was saved, vi didn't * preserve addressing or delta information. */ if (ptrn == NULL) goto prev; if (plen == 1) { if (epp != NULL) *epp = ptrn + 1; goto prev; } if (ptrn[0] == ptrn[1]) { if (epp != NULL) *epp = ptrn + 2; /* Complain if we don't have a previous pattern. */ prev: if (sp->re == NULL) { search_msg(sp, S_NOPREV); return (1); } /* Re-compile the search pattern if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH | (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT))) return (1); /* Set the search direction. */ if (LF_ISSET(SEARCH_SET)) sp->searchdir = dir; return (0); } /* * Set the delimiter, and move forward to the terminating * delimiter, handling escaped delimiters. * * QUOTING NOTE: * Only discard an escape character if it escapes a delimiter. */ for (delim = *ptrn, p = t = ++ptrn;; *t++ = *p++) { if (--plen == 0 || p[0] == delim) { if (plen != 0) ++p; break; } - if (plen > 1 && p[0] == '\\' && p[1] == delim) { - ++p; - --plen; + if (plen > 1 && p[0] == '\\') { + if (p[1] == delim) { + ++p; + --plen; + } else if ( p[1] == '\\') { + *t++ = *p++; + --plen; + } } } if (epp != NULL) *epp = p; plen = t - ptrn; } /* Compile the RE. */ if (re_compile(sp, ptrn, plen, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH | (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT) | (LF_ISSET(SEARCH_TAG) ? RE_C_TAG : 0) | (LF_ISSET(SEARCH_CSCOPE) ? RE_C_CSCOPE : 0))) return (1); /* Set the search direction. */ if (LF_ISSET(SEARCH_SET)) sp->searchdir = dir; return (0); } /* * f_search -- * Do a forward search. * * PUBLIC: int f_search(SCR *, * PUBLIC: MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int); */ int f_search(SCR *sp, MARK *fm, MARK *rm, CHAR_T *ptrn, size_t plen, CHAR_T **eptrn, u_int flags) { busy_t btype; recno_t lno; regmatch_t match[1]; size_t coff, len; int cnt, eval, rval, wrapped = 0; CHAR_T *l; if (search_init(sp, FORWARD, ptrn, plen, eptrn, flags)) return (1); if (LF_ISSET(SEARCH_FILE)) { lno = 1; coff = 0; } else { if (db_get(sp, fm->lno, DBG_FATAL, &l, &len)) return (1); lno = fm->lno; /* * If doing incremental search, start searching at the previous * column, so that we search a minimal distance and still match * special patterns, e.g., \< for beginning of a word. * * Otherwise, start searching immediately after the cursor. If * at the end of the line, start searching on the next line. * This is incompatible (read bug fix) with the historic vi -- * searches for the '$' pattern never moved forward, and the * "-t foo" didn't work if the 'f' was the first character in * the file. */ if (LF_ISSET(SEARCH_INCR)) { if ((coff = fm->cno) != 0) --coff; } else if (fm->cno + 1 >= len) { coff = 0; lno = fm->lno + 1; if (db_get(sp, lno, 0, &l, &len)) { if (!O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EOF); return (1); } lno = 1; wrapped = 1; } } else coff = fm->cno + 1; } btype = BUSY_ON; for (cnt = INTERRUPT_CHECK, rval = 1;; ++lno, coff = 0) { if (cnt-- == 0) { if (INTERRUPTED(sp)) break; if (LF_ISSET(SEARCH_MSG)) { search_busy(sp, btype); btype = BUSY_UPDATE; } cnt = INTERRUPT_CHECK; } if ((wrapped && lno > fm->lno) || db_get(sp, lno, 0, &l, &len)) { if (wrapped) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_NOTFOUND); break; } if (!O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EOF); break; } lno = 0; wrapped = 1; continue; } /* If already at EOL, just keep going. */ if (len != 0 && coff == len) continue; /* Set the termination. */ match[0].rm_so = coff; match[0].rm_eo = len; #if defined(DEBUG) && 0 TRACE(sp, "F search: %lu from %u to %u\n", lno, coff, len != 0 ? len - 1 : len); #endif /* Search the line. */ eval = regexec(&sp->re_c, l, 1, match, (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND); if (eval == REG_NOMATCH) continue; if (eval != 0) { if (LF_ISSET(SEARCH_MSG)) re_error(sp, eval, &sp->re_c); else (void)sp->gp->scr_bell(sp); break; } /* Warn if the search wrapped. */ if (wrapped && LF_ISSET(SEARCH_WMSG)) search_msg(sp, S_WRAP); #if defined(DEBUG) && 0 TRACE(sp, "F search: %qu to %qu\n", match[0].rm_so, match[0].rm_eo); #endif rm->lno = lno; rm->cno = match[0].rm_so; /* * If a change command, it's possible to move beyond the end * of a line. Historic vi generally got this wrong (e.g. try * "c?$"). Not all that sure this gets it right, there * are lots of strange cases. */ if (!LF_ISSET(SEARCH_EOL) && rm->cno >= len) rm->cno = len != 0 ? len - 1 : 0; rval = 0; break; } if (LF_ISSET(SEARCH_MSG)) search_busy(sp, BUSY_OFF); return (rval); } /* * b_search -- * Do a backward search. * * PUBLIC: int b_search(SCR *, * PUBLIC: MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int); */ int b_search(SCR *sp, MARK *fm, MARK *rm, CHAR_T *ptrn, size_t plen, CHAR_T **eptrn, u_int flags) { busy_t btype; recno_t lno; regmatch_t match[1]; size_t coff, last, len; int cnt, eval, rval, wrapped; CHAR_T *l; if (search_init(sp, BACKWARD, ptrn, plen, eptrn, flags)) return (1); /* * If doing incremental search, set the "starting" position past the * current column, so that we search a minimal distance and still * match special patterns, e.g., \> for the end of a word. This is * safe when the cursor is at the end of a line because we only use * it for comparison with the location of the match. * * Otherwise, start searching immediately before the cursor. If in * the first column, start search on the previous line. */ if (LF_ISSET(SEARCH_INCR)) { lno = fm->lno; coff = fm->cno + 1; } else { if (fm->cno == 0) { if (fm->lno == 1 && !O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_SOF); return (1); } lno = fm->lno - 1; } else lno = fm->lno; coff = fm->cno; } btype = BUSY_ON; for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; --lno, coff = 0) { if (cnt-- == 0) { if (INTERRUPTED(sp)) break; if (LF_ISSET(SEARCH_MSG)) { search_busy(sp, btype); btype = BUSY_UPDATE; } cnt = INTERRUPT_CHECK; } if ((wrapped && lno < fm->lno) || lno == 0) { if (wrapped) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_NOTFOUND); break; } if (!O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_SOF); break; } if (db_last(sp, &lno)) break; if (lno == 0) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EMPTY); break; } ++lno; wrapped = 1; continue; } if (db_get(sp, lno, 0, &l, &len)) break; /* Set the termination. */ match[0].rm_so = 0; match[0].rm_eo = len; #if defined(DEBUG) && 0 TRACE(sp, "B search: %lu from 0 to %qu\n", lno, match[0].rm_eo); #endif /* Search the line. */ eval = regexec(&sp->re_c, l, 1, match, (match[0].rm_eo == len ? 0 : REG_NOTEOL) | REG_STARTEND); if (eval == REG_NOMATCH) continue; if (eval != 0) { if (LF_ISSET(SEARCH_MSG)) re_error(sp, eval, &sp->re_c); else (void)sp->gp->scr_bell(sp); break; } /* Check for a match starting past the cursor. */ if (coff != 0 && match[0].rm_so >= coff) continue; /* Warn if the search wrapped. */ if (wrapped && LF_ISSET(SEARCH_WMSG)) search_msg(sp, S_WRAP); #if defined(DEBUG) && 0 TRACE(sp, "B found: %qu to %qu\n", match[0].rm_so, match[0].rm_eo); #endif /* * We now have the first match on the line. Step through the * line character by character until find the last acceptable * match. This is painful, we need a better interface to regex * to make this work. */ for (;;) { last = match[0].rm_so++; if (match[0].rm_so >= len) break; match[0].rm_eo = len; eval = regexec(&sp->re_c, l, 1, match, (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND); if (eval == REG_NOMATCH) break; if (eval != 0) { if (LF_ISSET(SEARCH_MSG)) re_error(sp, eval, &sp->re_c); else (void)sp->gp->scr_bell(sp); goto err; } if (coff && match[0].rm_so >= coff) break; } rm->lno = lno; /* See comment in f_search(). */ if (!LF_ISSET(SEARCH_EOL) && last >= len) rm->cno = len != 0 ? len - 1 : 0; else rm->cno = last; rval = 0; break; } err: if (LF_ISSET(SEARCH_MSG)) search_busy(sp, BUSY_OFF); return (rval); } /* * search_msg -- * Display one of the search messages. */ static void search_msg(SCR *sp, smsg_t msg) { switch (msg) { case S_EMPTY: msgq(sp, M_ERR, "072|File empty; nothing to search"); break; case S_EOF: msgq(sp, M_ERR, "073|Reached end-of-file without finding the pattern"); break; case S_NOPREV: msgq(sp, M_ERR, "074|No previous search pattern"); break; case S_NOTFOUND: msgq(sp, M_ERR, "075|Pattern not found"); break; case S_SOF: msgq(sp, M_ERR, "076|Reached top-of-file without finding the pattern"); break; case S_WRAP: msgq(sp, M_ERR, "077|Search wrapped"); break; default: abort(); } } /* * search_busy -- * Put up the busy searching message. * * PUBLIC: void search_busy(SCR *, busy_t); */ void search_busy(SCR *sp, busy_t btype) { sp->gp->scr_busy(sp, "078|Searching...", btype); } diff --git a/contrib/nvi/ex/ex.c b/contrib/nvi/ex/ex.c index fd920a8df9a1..900678e942eb 100644 --- a/contrib/nvi/ex/ex.c +++ b/contrib/nvi/ex/ex.c @@ -1,2368 +1,2377 @@ /*- * 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); + if (*spp) { + F_CLR(*spp, SC_SCR_VI); + F_SET(*spp, SC_SCR_EX); + } 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); + O_ISSET(sp, O_AUTOPRINT) && F_ISSET(ecp, E_AUTOPRINT)) { + /* Honor the number option if autoprint is set. */ + if (F_ISSET(ecp, E_OPTNUM)) + LF_INIT(E_C_HASH); + else + 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 (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/contrib/nvi/ex/ex_argv.c b/contrib/nvi/ex/ex_argv.c index 8b1fd7858fb1..dd37fe1f69e2 100644 --- a/contrib/nvi/ex/ex_argv.c +++ b/contrib/nvi/ex/ex_argv.c @@ -1,918 +1,918 @@ /*- * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" static int argv_alloc(SCR *, size_t); static int argv_comp(const void *, const void *); static int argv_fexp(SCR *, EXCMD *, CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int); static int argv_sexp(SCR *, CHAR_T **, size_t *, size_t *); static int argv_flt_user(SCR *, EXCMD *, CHAR_T *, size_t); /* * argv_init -- * Build a prototype arguments list. * * PUBLIC: int argv_init(SCR *, EXCMD *); */ int argv_init(SCR *sp, EXCMD *excp) { EX_PRIVATE *exp; exp = EXP(sp); exp->argsoff = 0; argv_alloc(sp, 1); excp->argv = exp->args; excp->argc = exp->argsoff; return (0); } /* * argv_exp0 -- * Append a string to the argument list. * * PUBLIC: int argv_exp0(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_exp0(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; exp = EXP(sp); argv_alloc(sp, cmdlen); MEMCPY(exp->args[exp->argsoff]->bp, cmd, cmdlen); exp->args[exp->argsoff]->bp[cmdlen] = '\0'; exp->args[exp->argsoff]->len = cmdlen; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; return (0); } /* * argv_exp1 -- * Do file name expansion on a string, and append it to the * argument list. * * PUBLIC: int argv_exp1(SCR *, EXCMD *, CHAR_T *, size_t, int); */ int argv_exp1(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, int is_bang) { EX_PRIVATE *exp; size_t blen, len; CHAR_T *p, *t, *bp; GET_SPACE_RETW(sp, bp, blen, 512); len = 0; exp = EXP(sp); if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) { FREE_SPACEW(sp, bp, blen); return (1); } /* If it's empty, we're done. */ if (len != 0) { for (p = bp, t = bp + len; p < t; ++p) if (!cmdskip(*p)) break; if (p == t) goto ret; } else goto ret; (void)argv_exp0(sp, excp, bp, len); ret: FREE_SPACEW(sp, bp, blen); return (0); } /* * argv_exp2 -- * Do file name and shell expansion on a string, and append it to * the argument list. * * PUBLIC: int argv_exp2(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_exp2(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { size_t blen, len, n; int rval; CHAR_T *bp, *p; GET_SPACE_RETW(sp, bp, blen, 512); #define SHELLECHO L("echo ") #define SHELLOFFSET (SIZE(SHELLECHO) - 1) MEMCPY(bp, SHELLECHO, SHELLOFFSET); p = bp + SHELLOFFSET; len = SHELLOFFSET; #if defined(DEBUG) && 0 TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd); #endif if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) { rval = 1; goto err; } #if defined(DEBUG) && 0 TRACE(sp, "before shell: %d: {%s}\n", len, bp); #endif /* * Do shell word expansion -- it's very, very hard to figure out what * magic characters the user's shell expects. Historically, it was a * union of v7 shell and csh meta characters. We match that practice * by default, so ":read \%" tries to read a file named '%'. It would * make more sense to pass any special characters through the shell, * but then, if your shell was csh, the above example will behave * differently in nvi than in vi. If you want to get other characters * passed through to your shell, change the "meta" option. */ if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1)) n = 0; else { p = bp + SHELLOFFSET; n = len - SHELLOFFSET; for (; n > 0; --n, ++p) if (IS_SHELLMETA(sp, *p)) break; } /* * If we found a meta character in the string, fork a shell to expand * it. Unfortunately, this is comparatively slow. Historically, it * didn't matter much, since users don't enter meta characters as part * of pathnames that frequently. The addition of filename completion * broke that assumption because it's easy to use. To increase the * completion performance, nvi used to have an internal routine to * handle "filename*". However, the shell special characters does not * limit to "shellmeta", so such a hack breaks historic practice. * After it all, we split the completion logic out from here. */ switch (n) { case 0: p = bp + SHELLOFFSET; len -= SHELLOFFSET; rval = argv_exp3(sp, excp, p, len); break; default: if (argv_sexp(sp, &bp, &blen, &len)) { rval = 1; goto err; } p = bp; rval = argv_exp3(sp, excp, p, len); break; } err: FREE_SPACEW(sp, bp, blen); return (rval); } /* * argv_exp3 -- * Take a string and break it up into an argv, which is appended * to the argument list. * * PUBLIC: int argv_exp3(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_exp3(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; size_t len; int ch, off; CHAR_T *ap, *p; for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) { /* Skip any leading whitespace. */ for (; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (!cmdskip(ch)) break; } if (cmdlen == 0) break; /* * Determine the length of this whitespace delimited * argument. * * QUOTING NOTE: * * Skip any character preceded by the user's quoting * character. */ for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) { ch = *cmd; if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) { ++cmd; --cmdlen; } else if (cmdskip(ch)) break; } /* * Copy the argument into place. * * QUOTING NOTE: * * Lose quote chars. */ argv_alloc(sp, len); off = exp->argsoff; exp->args[off]->len = len; for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++) if (IS_ESCAPE(sp, excp, *ap)) ++ap; *p = '\0'; } excp->argv = exp->args; excp->argc = exp->argsoff; #if defined(DEBUG) && 0 for (cnt = 0; cnt < exp->argsoff; ++cnt) TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]); #endif return (0); } /* * argv_flt_ex -- * Filter the ex commands with a prefix, and append the results to * the argument list. * * PUBLIC: int argv_flt_ex(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_flt_ex(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; EXCMDLIST const *cp; int off; size_t len; exp = EXP(sp); for (off = exp->argsoff, cp = cmds; cp->name != NULL; ++cp) { len = STRLEN(cp->name); if (cmdlen > 0 && (cmdlen > len || MEMCMP(cmd, cp->name, cmdlen))) continue; /* Copy the matched ex command name. */ argv_alloc(sp, len + 1); MEMCPY(exp->args[exp->argsoff]->bp, cp->name, len + 1); exp->args[exp->argsoff]->len = len; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } return (0); } /* * argv_flt_user -- * Filter the ~user list on the system with a prefix, and append * the results to the argument list. */ static int argv_flt_user(SCR *sp, EXCMD *excp, CHAR_T *uname, size_t ulen) { EX_PRIVATE *exp; struct passwd *pw; int off; char *np; size_t len, nlen; exp = EXP(sp); off = exp->argsoff; /* The input must come with a leading '~'. */ INT2CHAR(sp, uname + 1, ulen - 1, np, nlen); if ((np = v_strdup(sp, np, nlen)) == NULL) return (1); setpwent(); while ((pw = getpwent()) != NULL) { len = strlen(pw->pw_name); if (nlen > 0 && (nlen > len || memcmp(np, pw->pw_name, nlen))) continue; /* Copy '~' + the matched user name. */ CHAR2INT(sp, pw->pw_name, len + 1, uname, ulen); argv_alloc(sp, ulen + 1); exp->args[exp->argsoff]->bp[0] = '~'; MEMCPY(exp->args[exp->argsoff]->bp + 1, uname, ulen); exp->args[exp->argsoff]->len = ulen; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } endpwent(); free(np); qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); return (0); } /* * argv_fexp -- * Do file name and bang command expansion. */ static int argv_fexp(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, CHAR_T *p, size_t *lenp, CHAR_T **bpp, size_t *blenp, int is_bang) { EX_PRIVATE *exp; char *t; size_t blen, len, off, tlen; CHAR_T *bp; CHAR_T *wp; size_t wlen; /* Replace file name characters. */ for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd) switch (*cmd) { case '!': if (!is_bang) goto ins_ch; exp = EXP(sp); if (exp->lastbcomm == NULL) { msgq(sp, M_ERR, "115|No previous command to replace \"!\""); return (1); } len += tlen = STRLEN(exp->lastbcomm); off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; MEMCPY(p, exp->lastbcomm, tlen); p += tlen; F_SET(excp, E_MODIFY); break; case '%': if ((t = sp->frp->name) == NULL) { msgq(sp, M_ERR, "116|No filename to substitute for %%"); return (1); } tlen = strlen(t); len += tlen; off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; CHAR2INT(sp, t, tlen, wp, wlen); MEMCPY(p, wp, wlen); p += wlen; F_SET(excp, E_MODIFY); break; case '#': if ((t = sp->alt_name) == NULL) { msgq(sp, M_ERR, "117|No filename to substitute for #"); return (1); } len += tlen = strlen(t); off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; CHAR2INT(sp, t, tlen, wp, wlen); MEMCPY(p, wp, wlen); p += wlen; F_SET(excp, E_MODIFY); break; case '\\': /* * QUOTING NOTE: * * Strip any backslashes that protected the file * expansion characters. */ if (cmdlen > 1 && (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) { ++cmd; --cmdlen; } /* FALLTHROUGH */ default: ins_ch: ++len; off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; *p++ = *cmd; } /* Nul termination. */ ++len; off = p - bp; ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; *p = '\0'; /* Return the new string length, buffer, buffer length. */ *lenp = len - 1; *bpp = bp; *blenp = blen; return (0); } /* * argv_alloc -- * Make more space for arguments. */ static int argv_alloc(SCR *sp, size_t len) { ARGS *ap; EX_PRIVATE *exp; int cnt, off; /* * Allocate room for another argument, always leaving * enough room for an ARGS structure with a length of 0. */ #define INCREMENT 20 exp = EXP(sp); off = exp->argsoff; if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) { cnt = exp->argscnt + INCREMENT; REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *)); if (exp->args == NULL) { (void)argv_free(sp); goto mem; } memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *)); exp->argscnt = cnt; } /* First argument. */ if (exp->args[off] == NULL) { CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); if (exp->args[off] == NULL) goto mem; } /* First argument buffer. */ ap = exp->args[off]; ap->len = 0; if (ap->blen < len + 1) { ap->blen = len + 1; REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T)); if (ap->bp == NULL) { ap->bp = NULL; ap->blen = 0; F_CLR(ap, A_ALLOCATED); mem: msgq(sp, M_SYSERR, NULL); return (1); } F_SET(ap, A_ALLOCATED); } /* Second argument. */ if (exp->args[++off] == NULL) { CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); if (exp->args[off] == NULL) goto mem; } /* 0 length serves as end-of-argument marker. */ exp->args[off]->len = 0; return (0); } /* * argv_free -- * Free up argument structures. * * PUBLIC: int argv_free(SCR *); */ int argv_free(SCR *sp) { EX_PRIVATE *exp; int off; exp = EXP(sp); if (exp->args != NULL) { for (off = 0; off < exp->argscnt; ++off) { if (exp->args[off] == NULL) continue; if (F_ISSET(exp->args[off], A_ALLOCATED)) free(exp->args[off]->bp); free(exp->args[off]); } free(exp->args); } exp->args = NULL; exp->argscnt = 0; exp->argsoff = 0; return (0); } /* * argv_flt_path -- * Find all file names matching the prefix and append them to the * argument list. * * PUBLIC: int argv_flt_path(SCR *, EXCMD *, CHAR_T *, size_t); */ int argv_flt_path(SCR *sp, EXCMD *excp, CHAR_T *path, size_t plen) { struct dirent *dp; DIR *dirp; EX_PRIVATE *exp; int off; size_t dlen, len, nlen; CHAR_T *dname; CHAR_T *p, *np, *n; char *name, *tp, *epd = NULL; CHAR_T *wp; size_t wlen; exp = EXP(sp); /* Set up the name and length for comparison. */ if ((path = v_wstrdup(sp, path, plen)) == NULL) return (1); if ((p = STRRCHR(path, '/')) == NULL) { if (*path == '~') { int rc; /* Filter ~user list instead. */ rc = argv_flt_user(sp, excp, path, plen); free(path); return (rc); } dname = L("."); dlen = 0; np = path; } else { if (p == path) { dname = L("/"); dlen = 1; } else { *p = '\0'; dname = path; dlen = p - path; } np = p + 1; } INT2CHAR(sp, dname, dlen + 1, tp, nlen); if ((epd = expanduser(tp)) != NULL) tp = epd; if ((dirp = opendir(tp)) == NULL) { free(epd); free(path); return (1); } free(epd); INT2CHAR(sp, np, STRLEN(np), tp, nlen); if ((name = v_strdup(sp, tp, nlen)) == NULL) { free(path); return (1); } for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) { if (nlen == 0) { if (dp->d_name[0] == '.') continue; #ifdef HAVE_DIRENT_D_NAMLEN len = dp->d_namlen; #else len = strlen(dp->d_name); #endif } else { #ifdef HAVE_DIRENT_D_NAMLEN len = dp->d_namlen; #else len = strlen(dp->d_name); #endif if (len < nlen || memcmp(dp->d_name, name, nlen)) continue; } /* Directory + name + slash + null. */ CHAR2INT(sp, dp->d_name, len + 1, wp, wlen); argv_alloc(sp, dlen + wlen + 1); n = exp->args[exp->argsoff]->bp; if (dlen != 0) { MEMCPY(n, dname, dlen); n += dlen; if (dlen > 1 || dname[0] != '/') *n++ = '/'; exp->args[exp->argsoff]->len = dlen + 1; } MEMCPY(n, wp, wlen); exp->args[exp->argsoff]->len += wlen - 1; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } closedir(dirp); free(name); free(path); qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); return (0); } /* * argv_comp -- * Alphabetic comparison. */ static int argv_comp(const void *a, const void *b) { return (STRCMP((*(ARGS **)a)->bp, (*(ARGS **)b)->bp)); } /* * argv_sexp -- * Fork a shell, pipe a command through it, and read the output into * a buffer. */ static int argv_sexp(SCR *sp, CHAR_T **bpp, size_t *blenp, size_t *lenp) { enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval; FILE *ifp; pid_t pid; size_t blen, len; int ch, std_output[2]; CHAR_T *bp, *p; char *sh, *sh_path; char *np; size_t nlen; /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { msgq(sp, M_ERR, "289|Shell expansions not supported when the secure edit option is set"); return (1); } sh_path = O_STR(sp, O_SHELL); if ((sh = strrchr(sh_path, '/')) == NULL) sh = sh_path; else ++sh; /* Local copies of the buffer variables. */ bp = *bpp; blen = *blenp; /* * There are two different processes running through this code, named * the utility (the shell) and the parent. The utility reads standard * input and writes standard output and standard error output. The * parent writes to the utility, reads its standard output and ignores * its standard error output. Historically, the standard error output * was discarded by vi, as it produces a lot of noise when file patterns * don't match. * * The parent reads std_output[0], and the utility writes std_output[1]. */ ifp = NULL; std_output[0] = std_output[1] = -1; if (pipe(std_output) < 0) { msgq(sp, M_SYSERR, "pipe"); return (1); } if ((ifp = fdopen(std_output[0], "r")) == NULL) { msgq(sp, M_SYSERR, "fdopen"); goto err; } /* * Do the minimal amount of work possible, the shell is going to run * briefly and then exit. We sincerely hope. */ switch (pid = vfork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "vfork"); err: if (ifp != NULL) (void)fclose(ifp); else if (std_output[0] != -1) close(std_output[0]); if (std_output[1] != -1) close(std_output[0]); return (1); case 0: /* Utility. */ /* Redirect stdout to the write end of the pipe. */ (void)dup2(std_output[1], STDOUT_FILENO); /* Close the utility's file descriptors. */ (void)close(std_output[0]); (void)close(std_output[1]); (void)close(STDERR_FILENO); /* * XXX * Assume that all shells have -c. */ INT2CHAR(sp, bp, STRLEN(bp)+1, np, nlen); execl(sh_path, sh, "-c", np, (char *)NULL); msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s"); _exit(127); default: /* Parent. */ /* Close the pipe ends the parent won't use. */ (void)close(std_output[1]); break; } /* * Copy process standard output into a buffer. * * !!! * Historic vi apparently discarded leading \n and \r's from * the shell output stream. We don't on the grounds that any * shell that does that is broken. */ for (p = bp, len = 0, ch = EOF; - (ch = GETC(ifp)) != EOF; *p++ = ch, blen-=sizeof(CHAR_T), ++len) + (ch = GETC(ifp)) != EOF; *p++ = ch, blen -= sizeof(CHAR_T), ++len) if (blen < 5) { - ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2); + ADD_SPACE_GOTO(sp, CHAR_T, bp, *blenp, *blenp * 2); p = bp + len; - blen = *blenp - len; + blen = *blenp - len * sizeof(CHAR_T); } /* Delete the final newline, nul terminate the string. */ if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) { --p; --len; } *p = '\0'; *lenp = len; *bpp = bp; /* *blenp is already updated. */ if (ferror(ifp)) goto ioerr; if (fclose(ifp)) { ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s"); alloc_err: rval = SEXP_ERR; } else rval = SEXP_OK; /* * Wait for the process. If the shell process fails (e.g., "echo $q" * where q wasn't a defined variable) or if the returned string has * no characters or only blank characters, (e.g., "echo $5"), complain * that the shell expansion failed. We can't know for certain that's * the error, but it's a good guess, and it matches historic practice. * This won't catch "echo foo_$5", but that's not a common error and * historic vi didn't catch it either. */ if (proc_wait(sp, (long)pid, sh, 1, 0)) rval = SEXP_EXPANSION_ERR; for (p = bp; len; ++p, --len) if (!cmdskip(*p)) break; if (len == 0) rval = SEXP_EXPANSION_ERR; if (rval == SEXP_EXPANSION_ERR) msgq(sp, M_ERR, "304|Shell expansion failed"); return (rval == SEXP_OK ? 0 : 1); } /* * argv_esc -- * Escape a string into an ex and shell argument. * * PUBLIC: CHAR_T *argv_esc(SCR *, EXCMD *, CHAR_T *, size_t); */ CHAR_T * argv_esc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len) { size_t blen, off; CHAR_T *bp, *p; int ch; GET_SPACE_GOTOW(sp, bp, blen, len + 1); /* * Leaving the first '~' unescaped causes the user to need a * "./" prefix to edit a file which really starts with a '~'. * However, the file completion happens to not work for these * files without the prefix. * * All ex expansion characters, "!%#", are double escaped. */ for (p = bp; len > 0; ++str, --len) { ch = *str; off = p - bp; if (blen / sizeof(CHAR_T) - off < 3) { ADD_SPACE_GOTOW(sp, bp, blen, off + 3); p = bp + off; } if (cmdskip(ch) || ch == '\n' || IS_ESCAPE(sp, excp, ch)) /* Ex. */ *p++ = CH_LITERAL; else switch (ch) { case '~': /* ~user. */ if (p != bp) *p++ = '\\'; break; case '+': /* Ex +cmd. */ if (p == bp) *p++ = '\\'; break; case '!': case '%': case '#': /* Ex exp. */ *p++ = '\\'; *p++ = '\\'; break; case ',': case '-': case '.': case '/': /* Safe. */ case ':': case '=': case '@': case '_': break; default: /* Unsafe. */ if (isascii(ch) && !isalnum(ch)) *p++ = '\\'; } *p++ = ch; } *p = '\0'; return bp; alloc_err: return NULL; } /* * argv_uesc -- * Unescape an escaped ex and shell argument. * * PUBLIC: CHAR_T *argv_uesc(SCR *, EXCMD *, CHAR_T *, size_t); */ CHAR_T * argv_uesc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len) { size_t blen; CHAR_T *bp, *p; GET_SPACE_GOTOW(sp, bp, blen, len + 1); for (p = bp; len > 0; ++str, --len) { if (IS_ESCAPE(sp, excp, *str)) { if (--len < 1) break; ++str; } else if (*str == '\\') { if (--len < 1) break; ++str; /* Check for double escaping. */ if (*str == '\\' && len > 1) switch (str[1]) { case '!': case '%': case '#': ++str; --len; } } *p++ = *str; } *p = '\0'; return bp; alloc_err: return NULL; } diff --git a/contrib/nvi/ex/ex_subst.c b/contrib/nvi/ex/ex_subst.c index bf03e4417e91..3113ac5ca360 100644 --- a/contrib/nvi/ex/ex_subst.c +++ b/contrib/nvi/ex/ex_subst.c @@ -1,1434 +1,1437 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" #define SUB_FIRST 0x01 /* The 'r' flag isn't reasonable. */ #define SUB_MUSTSETR 0x02 /* The 'r' flag is required. */ static int re_conv(SCR *, CHAR_T **, size_t *, int *); static int re_cscope_conv(SCR *, CHAR_T **, size_t *, int *); static int re_sub(SCR *, CHAR_T *, CHAR_T **, size_t *, size_t *, regmatch_t [10]); static int re_tag_conv(SCR *, CHAR_T **, size_t *, int *); static int s(SCR *, EXCMD *, CHAR_T *, regex_t *, u_int); /* * ex_s -- * [line [,line]] s[ubstitute] [[/;]pat[/;]/repl[/;] [cgr] [count] [#lp]] * * Substitute on lines matching a pattern. * * PUBLIC: int ex_s(SCR *, EXCMD *); */ int ex_s(SCR *sp, EXCMD *cmdp) { regex_t *re; size_t blen, len; u_int flags; int delim; CHAR_T *bp, *p, *ptrn, *rep, *t; /* * Skip leading white space. * * !!! * Historic vi allowed any non-alphanumeric to serve as the * substitution command delimiter. * * !!! * If the arguments are empty, it's the same as &, i.e. we * repeat the last substitution. */ if (cmdp->argc == 0) goto subagain; for (p = cmdp->argv[0]->bp, len = cmdp->argv[0]->len; len > 0; --len, ++p) { if (!cmdskip(*p)) break; } if (len == 0) subagain: return (ex_subagain(sp, cmdp)); delim = *p++; if (is09azAZ(delim) || delim == '\\') return (s(sp, cmdp, p, &sp->subre_c, SUB_MUSTSETR)); /* * !!! * The full-blown substitute command reset the remembered * state of the 'c' and 'g' suffices. */ sp->c_suffix = sp->g_suffix = 0; /* * Get the pattern string, toss escaping characters. * * !!! * Historic vi accepted any of the following forms: * * :s/abc/def/ change "abc" to "def" * :s/abc/def change "abc" to "def" * :s/abc/ delete "abc" * :s/abc delete "abc" * * QUOTING NOTE: * * Only toss an escaping character if it escapes a delimiter. * This means that "s/A/\\\\f" replaces "A" with "\\f". It * would be nice to be more regular, i.e. for each layer of * escaping a single escaping character is removed, but that's * not how the historic vi worked. */ for (ptrn = t = p;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; /* * !!! * Nul terminate the pattern string -- it's passed * to regcomp which doesn't understand anything else. */ *t = '\0'; break; } if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') *t++ = *p++; } *t++ = *p++; } /* * If the pattern string is empty, use the last RE (not just the * last substitution RE). */ if (*ptrn == '\0') { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } /* Re-compile the RE if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); flags = 0; } else { /* * !!! * Compile the RE. Historic practice is that substitutes set * the search direction as well as both substitute and search * RE's. We compile the RE twice, as we don't want to bother * ref counting the pattern string and (opaque) structure. */ if (re_compile(sp, ptrn, t - ptrn, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) return (1); if (re_compile(sp, ptrn, t - ptrn, &sp->subre, &sp->subre_len, &sp->subre_c, RE_C_SUBST)) return (1); flags = SUB_FIRST; sp->searchdir = FORWARD; } re = &sp->re_c; /* * Get the replacement string. * * The special character & (\& if O_MAGIC not set) matches the * entire RE. No handling of & is required here, it's done by * re_sub(). * * The special character ~ (\~ if O_MAGIC not set) inserts the * previous replacement string into this replacement string. * Count ~'s to figure out how much space we need. We could * special case nonexistent last patterns or whether or not * O_MAGIC is set, but it's probably not worth the effort. * * QUOTING NOTE: * * Only toss an escaping character if it escapes a delimiter or * if O_MAGIC is set and it escapes a tilde. * * !!! * If the entire replacement pattern is "%", then use the last * replacement pattern. This semantic was added to vi in System * V and then percolated elsewhere, presumably around the time * that it was added to their version of ed(1). */ if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; free(sp->repl); sp->repl = NULL; sp->repl_len = 0; } else if (p[0] == '%' && (p[1] == '\0' || p[1] == delim)) p += p[1] == delim ? 2 : 1; else { for (rep = p, len = 0; p[0] != '\0' && p[0] != delim; ++p, ++len) if (p[0] == '~') len += sp->repl_len; GET_SPACE_RETW(sp, bp, blen, len); for (t = bp, len = 0, p = rep;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; break; } if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') { *t++ = *p++; ++len; } else if (p[1] == '~') { ++p; if (!O_ISSET(sp, O_MAGIC)) goto tilde; } } else if (p[0] == '~' && O_ISSET(sp, O_MAGIC)) { tilde: ++p; MEMCPY(t, sp->repl, sp->repl_len); t += sp->repl_len; len += sp->repl_len; continue; } *t++ = *p++; ++len; } if ((sp->repl_len = len) != 0) { free(sp->repl); MALLOC(sp, sp->repl, len * sizeof(CHAR_T)); if (sp->repl == NULL) { FREE_SPACEW(sp, bp, blen); return (1); } MEMCPY(sp->repl, bp, len); } FREE_SPACEW(sp, bp, blen); } return (s(sp, cmdp, p, re, flags)); } /* * ex_subagain -- * [line [,line]] & [cgr] [count] [#lp]] * * Substitute using the last substitute RE and replacement pattern. * * PUBLIC: int ex_subagain(SCR *, EXCMD *); */ int ex_subagain(SCR *sp, EXCMD *cmdp) { if (sp->subre == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } if (!F_ISSET(sp, SC_RE_SUBST) && re_compile(sp, sp->subre, sp->subre_len, NULL, NULL, &sp->subre_c, RE_C_SUBST)) return (1); return (s(sp, cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->subre_c, 0)); } /* * ex_subtilde -- * [line [,line]] ~ [cgr] [count] [#lp]] * * Substitute using the last RE and last substitute replacement pattern. * * PUBLIC: int ex_subtilde(SCR *, EXCMD *); */ int ex_subtilde(SCR *sp, EXCMD *cmdp) { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); return (s(sp, cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->re_c, 0)); } /* * s -- * Do the substitution. This stuff is *really* tricky. There are lots of * special cases, and general nastiness. Don't mess with it unless you're * pretty confident. * * The nasty part of the substitution is what happens when the replacement * string contains newlines. It's a bit tricky -- consider the information * that has to be retained for "s/f\(o\)o/^M\1^M\1/". The solution here is * to build a set of newline offsets which we use to break the line up later, * when the replacement is done. Don't change it unless you're *damned* * confident. */ #define NEEDNEWLINE(sp) do { \ if (sp->newl_len == sp->newl_cnt) { \ sp->newl_len += 25; \ REALLOC(sp, sp->newl, size_t *, \ sp->newl_len * sizeof(size_t)); \ if (sp->newl == NULL) { \ sp->newl_len = 0; \ return (1); \ } \ } \ } while (0) #define BUILD(sp, l, len) do { \ if (lbclen + (len) > lblen) { \ lblen = p2roundup(MAX(lbclen + (len), 256)); \ REALLOC(sp, lb, CHAR_T *, lblen * sizeof(CHAR_T)); \ if (lb == NULL) { \ lbclen = 0; \ return (1); \ } \ } \ MEMCPY(lb + lbclen, l, len); \ lbclen += len; \ } while (0) #define NEEDSP(sp, len, pnt) do { \ if (lbclen + (len) > lblen) { \ lblen = p2roundup(MAX(lbclen + (len), 256)); \ REALLOC(sp, lb, CHAR_T *, lblen * sizeof(CHAR_T)); \ if (lb == NULL) { \ lbclen = 0; \ return (1); \ } \ pnt = lb + lbclen; \ } \ } while (0) static int s(SCR *sp, EXCMD *cmdp, CHAR_T *s, regex_t *re, u_int flags) { EVENT ev; MARK from, to; TEXTH tiq[] = {{ 0 }}; recno_t elno, lno, slno; u_long ul; regmatch_t match[10]; size_t blen, cnt, last, lbclen, lblen, len, llen; size_t offset, saved_offset, scno; int cflag, lflag, nflag, pflag, rflag; int didsub, do_eol_match, eflags, empty_ok, eval; int linechanged, matched, quit, rval; CHAR_T *bp, *lb; enum nresult nret; NEEDFILE(sp, cmdp); slno = sp->lno; scno = sp->cno; /* * !!! * Historically, the 'g' and 'c' suffices were always toggled as flags, * so ":s/A/B/" was the same as ":s/A/B/ccgg". If O_EDCOMPATIBLE was * not set, they were initialized to 0 for all substitute commands. If * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user * specified substitute/replacement patterns (see ex_s()). */ if (!O_ISSET(sp, O_EDCOMPATIBLE)) sp->c_suffix = sp->g_suffix = 0; /* * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but * it only displayed the last change. I'd disallow them, but they are * useful in combination with the [v]global commands. In the current * model the problem is combining them with the 'c' flag -- the screen * would have to flip back and forth between the confirm screen and the * ex print screen, which would be pretty awful. We do display all * changes, though, for what that's worth. * * !!! * Historic vi was fairly strict about the order of "options", the * count, and "flags". I'm somewhat fuzzy on the difference between * options and flags, anyway, so this is a simpler approach, and we * just take it them in whatever order the user gives them. (The ex * usage statement doesn't reflect this.) */ cflag = lflag = nflag = pflag = rflag = 0; if (s == NULL) goto noargs; for (lno = OOBLNO; *s != '\0'; ++s) switch (*s) { case ' ': case '\t': continue; case '+': ++cmdp->flagoff; break; case '-': --cmdp->flagoff; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (lno != OOBLNO) goto usage; errno = 0; nret = nget_uslong(&ul, s, &s, 10); lno = ul; if (*s == '\0') /* Loop increment correction. */ --s; if (nret != NUM_OK) { if (nret == NUM_OVER) msgq(sp, M_ERR, "153|Count overflow"); else if (nret == NUM_UNDER) msgq(sp, M_ERR, "154|Count underflow"); else msgq(sp, M_SYSERR, NULL); return (1); } /* * In historic vi, the count was inclusive from the * second address. */ cmdp->addr1.lno = cmdp->addr2.lno; cmdp->addr2.lno += lno - 1; if (!db_exist(sp, cmdp->addr2.lno) && db_last(sp, &cmdp->addr2.lno)) return (1); break; case '#': nflag = 1; break; case 'c': sp->c_suffix = !sp->c_suffix; /* Ex text structure initialization. */ if (F_ISSET(sp, SC_EX)) TAILQ_INIT(tiq); break; case 'g': sp->g_suffix = !sp->g_suffix; break; case 'l': lflag = 1; break; case 'p': pflag = 1; break; case 'r': if (LF_ISSET(SUB_FIRST)) { msgq(sp, M_ERR, "155|Regular expression specified; r flag meaningless"); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH)) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } rflag = 1; re = &sp->re_c; break; default: goto usage; } if (*s != '\0' || (!rflag && LF_ISSET(SUB_MUSTSETR))) { usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } noargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) { msgq(sp, M_ERR, "156|The #, l and p flags may not be combined with the c flag in vi mode"); return (1); } /* * bp: if interactive, line cache * blen: if interactive, line cache length * lb: build buffer pointer. * lbclen: current length of built buffer. * lblen; length of build buffer. */ bp = lb = NULL; blen = lbclen = lblen = 0; /* For each line... */ lno = cmdp->addr1.lno == 0 ? 1 : cmdp->addr1.lno; for (matched = quit = 0, elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) { /* Someone's unhappy, time to stop. */ if (INTERRUPTED(sp)) break; /* Get the line. */ if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; /* * Make a local copy if doing confirmation -- when calling * the confirm routine we're likely to lose the cached copy. */ if (sp->c_suffix) { if (bp == NULL) { GET_SPACE_RETW(sp, bp, blen, llen); } else ADD_SPACE_RETW(sp, bp, blen, llen); MEMCPY(bp, s, llen); s = bp; } /* Start searching from the beginning. */ offset = 0; len = llen; /* Reset the build buffer offset. */ lbclen = 0; /* Reset empty match flag. */ empty_ok = 1; /* * We don't want to have to do a setline if the line didn't * change -- keep track of whether or not this line changed. * If doing confirmations, don't want to keep setting the * line if change is refused -- keep track of substitutions. */ didsub = linechanged = 0; /* New line, do an EOL match. */ do_eol_match = 1; /* It's not nul terminated, but we pretend it is. */ eflags = REG_STARTEND; /* * The search area is from s + offset to the EOL. * * Generally, match[0].rm_so is the offset of the start * of the match from the start of the search, and offset * is the offset of the start of the last search. */ nextmatch: match[0].rm_so = 0; match[0].rm_eo = len; /* Get the next match. */ eval = regexec(re, s + offset, 10, match, eflags); /* * There wasn't a match or if there was an error, deal with * it. If there was a previous match in this line, resolve * the changes into the database. Otherwise, just move on. */ if (eval == REG_NOMATCH) goto endmatch; if (eval != 0) { re_error(sp, eval, re); goto err; } matched = 1; /* Only the first search can match an anchored expression. */ eflags |= REG_NOTBOL; /* * !!! * It's possible to match 0-length strings -- for example, the * command s;a*;X;, when matched against the string "aabb" will * result in "XbXbX", i.e. the matches are "aa", the space * between the b's and the space between the b's and the end of * the string. There is a similar space between the beginning * of the string and the a's. The rule that we use (because vi * historically used it) is that any 0-length match, occurring * immediately after a match, is ignored. Otherwise, the above * example would have resulted in "XXbXbX". Another example is * incorrectly using " *" to replace groups of spaces with one * space. * * The way we do this is that if we just had a successful match, * the starting offset does not skip characters, and the match * is empty, ignore the match and move forward. If there's no * more characters in the string, we were attempting to match * after the last character, so quit. */ if (!empty_ok && match[0].rm_so == 0 && match[0].rm_eo == 0) { empty_ok = 1; if (len == 0) goto endmatch; BUILD(sp, s + offset, 1); ++offset; --len; goto nextmatch; } /* Confirm change. */ if (sp->c_suffix) { /* * Set the cursor position for confirmation. Note, * if we matched on a '$', the cursor may be past * the end of line. */ from.lno = to.lno = lno; from.cno = match[0].rm_so + offset; to.cno = match[0].rm_eo + offset; /* * Both ex and vi have to correct for a change before * the first character in the line. */ if (llen == 0) from.cno = to.cno = 0; if (F_ISSET(sp, SC_VI)) { /* * Only vi has to correct for a change after * the last character in the line. * * XXX * It would be nice to change the vi code so * that we could display a cursor past EOL. */ if (to.cno >= llen) to.cno = llen - 1; if (from.cno >= llen) from.cno = llen - 1; sp->lno = from.lno; sp->cno = from.cno; if (vs_refresh(sp, 1)) goto err; vs_update(sp, msg_cat(sp, "169|Confirm change? [n]", NULL), NULL); if (v_event_get(sp, &ev, 0, 0)) goto err; switch (ev.e_event) { case E_CHARACTER: break; case E_EOF: case E_ERR: case E_INTERRUPT: goto lquit; default: v_event_err(sp, &ev); goto lquit; } } else { - if (ex_print(sp, cmdp, &from, &to, 0) || + const int flags = + O_ISSET(sp, O_NUMBER) ? E_C_HASH : 0; + if (ex_print(sp, cmdp, &from, &to, flags) || ex_scprint(sp, &from, &to)) goto lquit; if (ex_txt(sp, tiq, 0, TXT_CR)) goto err; ev.e_c = TAILQ_FIRST(tiq)->lb[0]; } switch (ev.e_c) { case CH_YES: break; default: case CH_NO: didsub = 0; BUILD(sp, s +offset, match[0].rm_eo); goto skip; case CH_QUIT: /* Set the quit/interrupted flags. */ lquit: quit = 1; F_SET(sp->gp, G_INTERRUPTED); /* * Resolve any changes, then return to (and * exit from) the main loop. */ goto endmatch; } } /* * Set the cursor to the last position changed, converting * from 1-based to 0-based. */ sp->lno = lno; sp->cno = match[0].rm_so; /* Copy the bytes before the match into the build buffer. */ BUILD(sp, s + offset, match[0].rm_so); /* Substitute the matching bytes. */ didsub = 1; if (re_sub(sp, s + offset, &lb, &lbclen, &lblen, match)) goto err; /* Set the change flag so we know this line was modified. */ linechanged = 1; /* Move past the matched bytes. */ skip: offset += match[0].rm_eo; len -= match[0].rm_eo; /* A match cannot be followed by an empty pattern. */ empty_ok = 0; /* * If doing a global change with confirmation, we have to * update the screen. The basic idea is to store the line * so the screen update routines can find it, and restart. */ if (didsub && sp->c_suffix && sp->g_suffix) { /* * The new search offset will be the end of the * modified line. */ saved_offset = lbclen; /* Copy the rest of the line. */ if (len) BUILD(sp, s + offset, len); /* Set the new offset. */ offset = saved_offset; /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; offset -= last; sp->newl_cnt = 0; } /* Store and retrieve the line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; ADD_SPACE_RETW(sp, bp, blen, llen); MEMCPY(bp, s, llen); s = bp; len = llen - offset; /* Restart the build. */ lbclen = 0; BUILD(sp, s, offset); /* * If we haven't already done the after-the-string * match, do one. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (!do_eol_match) goto endmatch; if (offset == len) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } /* * If it's a global: * * If at the end of the string, do a test for the after * the string match. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (sp->g_suffix && do_eol_match) { if (len == 0) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } endmatch: if (!linechanged) continue; /* Copy any remaining bytes into the build buffer. */ if (len) BUILD(sp, s + offset, len); /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; sp->newl_cnt = 0; } /* Store the changed line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; /* Update changed line counter. */ if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* * !!! * Display as necessary. Historic practice is to only * display the last line of a line split into multiple * lines. */ if (lflag || nflag || pflag) { from.lno = to.lno = lno; from.cno = to.cno = 0; if (lflag) (void)ex_print(sp, cmdp, &from, &to, E_C_LIST); if (nflag) (void)ex_print(sp, cmdp, &from, &to, E_C_HASH); if (pflag) (void)ex_print(sp, cmdp, &from, &to, E_C_PRINT); } } /* * !!! * Historically, vi attempted to leave the cursor at the same place if * the substitution was done at the current cursor position. Otherwise * it moved it to the first non-blank of the last line changed. There * were some problems: for example, :s/$/foo/ with the cursor on the * last character of the line left the cursor on the last character, or * the & command with multiple occurrences of the matching string in the * line usually left the cursor in a fairly random position. * * We try to do the same thing, with the exception that if the user is * doing substitution with confirmation, we move to the last line about * which the user was consulted, as opposed to the last line that they * actually changed. This prevents a screen flash if the user doesn't * change many of the possible lines. */ if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * If not in a global command, and nothing matched, say so. * Else, if none of the lines displayed, put something up. */ rval = 0; if (!matched) { if (!F_ISSET(sp, SC_EX_GLOBAL)) { msgq(sp, M_ERR, "157|No match found"); goto err; } } else if (!lflag && !nflag && !pflag) F_SET(cmdp, E_AUTOPRINT); if (0) { err: rval = 1; } if (bp != NULL) FREE_SPACEW(sp, bp, blen); free(lb); return (rval); } /* * re_compile -- * Compile the RE. * * PUBLIC: int re_compile(SCR *, * PUBLIC: CHAR_T *, size_t, CHAR_T **, size_t *, regex_t *, u_int); */ int re_compile(SCR *sp, CHAR_T *ptrn, size_t plen, CHAR_T **ptrnp, size_t *lenp, regex_t *rep, u_int flags) { size_t len; int reflags, replaced, rval; CHAR_T *p; /* Set RE flags. */ reflags = 0; if (!LF_ISSET(RE_C_CSCOPE | RE_C_TAG)) { if (O_ISSET(sp, O_EXTENDED)) reflags |= REG_EXTENDED; if (O_ISSET(sp, O_IGNORECASE)) reflags |= REG_ICASE; if (O_ISSET(sp, O_ICLOWER)) { for (p = ptrn, len = plen; len > 0; ++p, --len) if (ISUPPER(*p)) break; if (len == 0) reflags |= REG_ICASE; } } /* If we're replacing a saved value, clear the old one. */ if (LF_ISSET(RE_C_SEARCH) && F_ISSET(sp, SC_RE_SEARCH)) { regfree(&sp->re_c); F_CLR(sp, SC_RE_SEARCH); } if (LF_ISSET(RE_C_SUBST) && F_ISSET(sp, SC_RE_SUBST)) { regfree(&sp->subre_c); F_CLR(sp, SC_RE_SUBST); } /* * If we're saving the string, it's a pattern we haven't seen before, * so convert the vi-style RE's to POSIX 1003.2 RE's. Save a copy for * later recompilation. Free any previously saved value. */ if (ptrnp != NULL) { replaced = 0; if (LF_ISSET(RE_C_CSCOPE)) { if (re_cscope_conv(sp, &ptrn, &plen, &replaced)) return (1); /* * XXX * Currently, the match-any- expression used in * re_cscope_conv() requires extended RE's. This may * not be right or safe. */ reflags |= REG_EXTENDED; } else if (LF_ISSET(RE_C_TAG)) { if (re_tag_conv(sp, &ptrn, &plen, &replaced)) return (1); } else if (re_conv(sp, &ptrn, &plen, &replaced)) return (1); /* Discard previous pattern. */ free(*ptrnp); *ptrnp = NULL; if (lenp != NULL) *lenp = plen; /* * Copy the string into allocated memory. * * XXX * Regcomp isn't 8-bit clean, so the pattern is nul-terminated * for now. There's just no other solution. */ MALLOC(sp, *ptrnp, (plen + 1) * sizeof(CHAR_T)); if (*ptrnp != NULL) { MEMCPY(*ptrnp, ptrn, plen); (*ptrnp)[plen] = '\0'; } /* Free up conversion-routine-allocated memory. */ if (replaced) FREE_SPACEW(sp, ptrn, 0); if (*ptrnp == NULL) return (1); ptrn = *ptrnp; } /* * XXX * Regcomp isn't 8-bit clean, so we just lost if the pattern * contained a nul. Bummer! */ if ((rval = regcomp(rep, ptrn, /* plen, */ reflags)) != 0) { if (!LF_ISSET(RE_C_SILENT)) re_error(sp, rval, rep); return (1); } if (LF_ISSET(RE_C_SEARCH)) F_SET(sp, SC_RE_SEARCH); if (LF_ISSET(RE_C_SUBST)) F_SET(sp, SC_RE_SUBST); return (0); } /* * re_conv -- * Convert vi's regular expressions into something that the * the POSIX 1003.2 RE functions can handle. * * There are three conversions we make to make vi's RE's (specifically * the global, search, and substitute patterns) work with POSIX RE's. * * 1: If O_MAGIC is not set, strip backslashes from the magic character * set (.[*~) that have them, and add them to the ones that don't. * 2: If O_MAGIC is not set, the string "\~" is replaced with the text * from the last substitute command's replacement string. If O_MAGIC * is set, it's the string "~". * 3: The pattern \ does "word" searches, convert it to use the * new RE escapes. * * !!!/XXX * This doesn't exactly match the historic behavior of vi because we do * the ~ substitution before calling the RE engine, so magic characters * in the replacement string will be expanded by the RE engine, and they * weren't historically. It's a bug. */ static int re_conv(SCR *sp, CHAR_T **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len, needlen; int magic; CHAR_T *bp, *p, *t; /* * First pass through, we figure out how much space we'll need. * We do it in two passes, on the grounds that most of the time * the user is doing a search and won't have magic characters. * That way we can skip most of the memory allocation and copies. */ magic = 0; for (p = *ptrnp, len = *plenp, needlen = 0; len > 0; ++p, --len) switch (*p) { case '\\': if (len > 1) { --len; switch (*++p) { case '<': magic = 1; needlen += RE_WSTART_LEN + 1; break; case '>': magic = 1; needlen += RE_WSTOP_LEN + 1; break; case '~': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += sp->repl_len; } break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += 1; } break; default: needlen += 2; } } else needlen += 1; break; case '~': if (O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += sp->repl_len; } break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += 2; } break; default: needlen += 1; break; } if (!magic) { *replacedp = 0; return (0); } /* Get enough memory to hold the final pattern. */ *replacedp = 1; GET_SPACE_RETW(sp, bp, blen, needlen); for (p = *ptrnp, len = *plenp, t = bp; len > 0; ++p, --len) switch (*p) { case '\\': if (len > 1) { --len; switch (*++p) { case '<': MEMCPY(t, RE_WSTART, RE_WSTART_LEN); t += RE_WSTART_LEN; break; case '>': MEMCPY(t, RE_WSTOP, RE_WSTOP_LEN); t += RE_WSTOP_LEN; break; case '~': if (O_ISSET(sp, O_MAGIC)) *t++ = '~'; else { MEMCPY(t, sp->repl, sp->repl_len); t += sp->repl_len; } break; case '.': case '[': case '*': if (O_ISSET(sp, O_MAGIC)) *t++ = '\\'; *t++ = *p; break; default: *t++ = '\\'; *t++ = *p; } } else *t++ = '\\'; break; case '~': if (O_ISSET(sp, O_MAGIC)) { MEMCPY(t, sp->repl, sp->repl_len); t += sp->repl_len; } else *t++ = '~'; break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) *t++ = '\\'; *t++ = *p; break; default: *t++ = *p; break; } *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_tag_conv -- * Convert a tags search path into something that the POSIX * 1003.2 RE functions can handle. */ static int re_tag_conv(SCR *sp, CHAR_T **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len; int lastdollar; CHAR_T *bp, *p, *t; len = *plenp; /* Max memory usage is 2 times the length of the string. */ *replacedp = 1; GET_SPACE_RETW(sp, bp, blen, len * 2); p = *ptrnp; t = bp; /* If the last character is a '/' or '?', we just strip it. */ if (len > 0 && (p[len - 1] == '/' || p[len - 1] == '?')) --len; /* If the next-to-last or last character is a '$', it's magic. */ if (len > 0 && p[len - 1] == '$') { --len; lastdollar = 1; } else lastdollar = 0; /* If the first character is a '/' or '?', we just strip it. */ if (len > 0 && (p[0] == '/' || p[0] == '?')) { ++p; --len; } /* If the first or second character is a '^', it's magic. */ if (p[0] == '^') { *t++ = *p++; --len; } /* * Escape every other magic character we can find, meanwhile stripping * the backslashes ctags inserts when escaping the search delimiter * characters. */ for (; len > 0; --len) { if (p[0] == '\\' && (p[1] == '/' || p[1] == '?')) { ++p; - --len; + if (len > 1) + --len; } else if (STRCHR(L("^.[]$*"), p[0])) *t++ = '\\'; *t++ = *p++; } if (lastdollar) *t++ = '$'; *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_cscope_conv -- * Convert a cscope search path into something that the POSIX * 1003.2 RE functions can handle. */ static int re_cscope_conv(SCR *sp, CHAR_T **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len, nspaces; CHAR_T *bp, *t; CHAR_T *p; CHAR_T *wp; size_t wlen; /* * Each space in the source line printed by cscope represents an * arbitrary sequence of spaces, tabs, and comments. */ #define CSCOPE_RE_SPACE "([ \t]|/\\*([^*]|\\*/)*\\*/)*" #define CSCOPE_LEN sizeof(CSCOPE_RE_SPACE) - 1 CHAR2INT(sp, CSCOPE_RE_SPACE, CSCOPE_LEN, wp, wlen); for (nspaces = 0, p = *ptrnp, len = *plenp; len > 0; ++p, --len) if (*p == ' ') ++nspaces; /* * Allocate plenty of space: * the string, plus potential escaping characters; * nspaces + 2 copies of CSCOPE_RE_SPACE; * ^, $, nul terminator characters. */ *replacedp = 1; len = (p - *ptrnp) * 2 + (nspaces + 2) * sizeof(CSCOPE_RE_SPACE) + 3; GET_SPACE_RETW(sp, bp, blen, len); p = *ptrnp; t = bp; *t++ = '^'; MEMCPY(t, wp, wlen); t += wlen; for (len = *plenp; len > 0; ++p, --len) if (*p == ' ') { MEMCPY(t, wp, wlen); t += wlen; } else { if (STRCHR(L("\\^.[]$*+?()|{}"), *p)) *t++ = '\\'; *t++ = *p; } MEMCPY(t, wp, wlen); t += wlen; *t++ = '$'; *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_error -- * Report a regular expression error. * * PUBLIC: void re_error(SCR *, int, regex_t *); */ void re_error(SCR *sp, int errcode, regex_t *preg) { size_t s; char *oe; s = regerror(errcode, preg, "", 0); MALLOC(sp, oe, s); if (oe != NULL) { (void)regerror(errcode, preg, oe, s); msgq(sp, M_ERR, "RE error: %s", oe); free(oe); } } /* * re_sub -- * Do the substitution for a regular expression. */ static int re_sub( SCR *sp, CHAR_T *ip, /* Input line. */ CHAR_T **lbp, size_t *lbclenp, size_t *lblenp, regmatch_t match[10]) { enum { C_NOTSET, C_LOWER, C_ONELOWER, C_ONEUPPER, C_UPPER } conv; size_t lbclen, lblen; /* Local copies. */ size_t mlen; /* Match length. */ size_t rpl; /* Remaining replacement length. */ CHAR_T *rp; /* Replacement pointer. */ int ch; int no; /* Match replacement offset. */ CHAR_T *p, *t; /* Buffer pointers. */ CHAR_T *lb; /* Local copies. */ lb = *lbp; /* Get local copies. */ lbclen = *lbclenp; lblen = *lblenp; /* * QUOTING NOTE: * * There are some special sequences that vi provides in the * replacement patterns. * & string the RE matched (\& if nomagic set) * \# n-th regular subexpression * \E end \U, \L conversion * \e end \U, \L conversion * \l convert the next character to lower-case * \L convert to lower-case, until \E, \e, or end of replacement * \u convert the next character to upper-case * \U convert to upper-case, until \E, \e, or end of replacement * * Otherwise, since this is the lowest level of replacement, discard * all escaping characters. This (hopefully) matches historic practice. */ #define OUTCH(ch, nltrans) do { \ ARG_CHAR_T __ch = (ch); \ e_key_t __value = KEY_VAL(sp, __ch); \ if (nltrans && (__value == K_CR || __value == K_NL)) { \ NEEDNEWLINE(sp); \ sp->newl[sp->newl_cnt++] = lbclen; \ } else if (conv != C_NOTSET) { \ switch (conv) { \ case C_ONELOWER: \ conv = C_NOTSET; \ /* FALLTHROUGH */ \ case C_LOWER: \ if (ISUPPER(__ch)) \ __ch = TOLOWER(__ch); \ break; \ case C_ONEUPPER: \ conv = C_NOTSET; \ /* FALLTHROUGH */ \ case C_UPPER: \ if (ISLOWER(__ch)) \ __ch = TOUPPER(__ch); \ break; \ default: \ abort(); \ } \ } \ NEEDSP(sp, 1, p); \ *p++ = __ch; \ ++lbclen; \ } while (0) conv = C_NOTSET; for (rp = sp->repl, rpl = sp->repl_len, p = lb + lbclen; rpl--;) { switch (ch = *rp++) { case '&': if (O_ISSET(sp, O_MAGIC)) { no = 0; goto subzero; } break; case '\\': if (rpl == 0) break; --rpl; switch (ch = *rp) { case '&': ++rp; if (!O_ISSET(sp, O_MAGIC)) { no = 0; goto subzero; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': no = *rp++ - '0'; subzero: if (match[no].rm_so == -1 || match[no].rm_eo == -1) break; mlen = match[no].rm_eo - match[no].rm_so; for (t = ip + match[no].rm_so; mlen--; ++t) OUTCH(*t, 0); continue; case 'e': case 'E': ++rp; conv = C_NOTSET; continue; case 'l': ++rp; conv = C_ONELOWER; continue; case 'L': ++rp; conv = C_LOWER; continue; case 'u': ++rp; conv = C_ONEUPPER; continue; case 'U': ++rp; conv = C_UPPER; continue; case '\r': OUTCH(ch, 0); continue; default: ++rp; break; } } OUTCH(ch, 1); } *lbp = lb; /* Update caller's information. */ *lbclenp = lbclen; *lblenp = lblen; return (0); } diff --git a/contrib/nvi/man/vi.1 b/contrib/nvi/man/vi.1 index b6eaf6964bbd..f1893040ceb7 100644 --- a/contrib/nvi/man/vi.1 +++ b/contrib/nvi/man/vi.1 @@ -1,2798 +1,2802 @@ .\" 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 September 25, 2020 .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. 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. 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. 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. 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. 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 .Cm Vi Ns .Op Cm sual Ns .Op Cm !\& .Op Ar +cmd .Op Ar file .Xc .Nm vi 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 .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 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 altnotation Bq off +Display control characters less than 0x20 in notations. +Carriage feed, escape, and delete are marked as , , and , +respectively. .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/contrib/nvi/vi/v_paragraph.c b/contrib/nvi/vi/v_paragraph.c index abe8d9cf50e0..2d7f07569b79 100644 --- a/contrib/nvi/vi/v_paragraph.c +++ b/contrib/nvi/vi/v_paragraph.c @@ -1,337 +1,342 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" #define INTEXT_CHECK do { \ if (len == 0 || v_isempty(p, len)) { \ if (!--cnt) \ goto found; \ pstate = P_INBLANK; \ } \ /* \ * !!! \ * Historic documentation (USD:15-11, 4.2) said that formfeed \ * characters (^L) in the first column delimited paragraphs. \ * The historic vi code mentions formfeed characters, but never \ * implements them. It seems reasonable, do it. \ */ \ if (p[0] == '\014') { \ if (!--cnt) \ goto found; \ + if (pstate == P_INTEXT && !--cnt) \ + goto found; \ continue; \ } \ if (p[0] != '.' || len < 2) \ continue; \ for (lp = VIP(sp)->ps; *lp != '\0'; lp += 2) \ if (lp[0] == p[1] && \ - (lp[1] == ' ' && len == 2 || lp[1] == p[2]) && \ - !--cnt) \ - goto found; \ + (lp[1] == ' ' && len == 2 || lp[1] == p[2])) { \ + if (!--cnt) \ + goto found; \ + if (pstate == P_INTEXT && !--cnt) \ + goto found; \ + } \ } while (0) /* * v_paragraphf -- [count]} * Move forward count paragraphs. * * Paragraphs are empty lines after text, formfeed characters, or values * from the paragraph or section options. * * PUBLIC: int v_paragraphf(SCR *, VICMD *); */ int v_paragraphf(SCR *sp, VICMD *vp) { enum { P_INTEXT, P_INBLANK } pstate; size_t lastlen, len; recno_t cnt, lastlno, lno; int isempty; CHAR_T *p; char *lp; /* * !!! * If the starting cursor position is at or before any non-blank * characters in the line, i.e. the movement is cutting all of the * line's text, the buffer is in line mode. It's a lot easier to * check here, because we know that the end is going to be the start * or end of a line. * * This was historical practice in vi, with a single exception. If * the paragraph movement was from the start of the last line to EOF, * then all the characters were deleted from the last line, but the * line itself remained. If somebody complains, don't pause, don't * hesitate, just hit them. */ if (ISMOTION(vp)) { if (vp->m_start.cno == 0) F_SET(vp, VM_LMODE); else { vp->m_stop = vp->m_start; vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); if (vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); } } /* Figure out what state we're currently in. */ lno = vp->m_start.lno; if (db_get(sp, lno, 0, &p, &len)) goto eof; /* * If we start in text, we want to switch states * (2 * N - 1) times, in non-text, (2 * N) times. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt *= 2; if (len == 0 || v_isempty(p, len)) pstate = P_INBLANK; else { --cnt; pstate = P_INTEXT; } for (;;) { lastlno = lno; lastlen = len; if (db_get(sp, ++lno, 0, &p, &len)) goto eof; switch (pstate) { case P_INTEXT: INTEXT_CHECK; break; case P_INBLANK: if (len == 0 || v_isempty(p, len)) break; if (--cnt) { pstate = P_INTEXT; break; } /* * !!! * Non-motion commands move to the end of the range, * delete and yank stay at the start. Ignore others. * Adjust the end of the range for motion commands; * historically, a motion component was to the end of * the previous line, whereas the movement command was * to the start of the new "paragraph". */ found: if (ISMOTION(vp)) { vp->m_stop.lno = lastlno; vp->m_stop.cno = lastlen ? lastlen - 1 : 0; vp->m_final = vp->m_start; } else { vp->m_stop.lno = lno; vp->m_stop.cno = 0; vp->m_final = vp->m_stop; } return (0); default: abort(); } } /* * !!! * Adjust end of the range for motion commands; EOF is a movement * sink. The } command historically moved to the end of the last * line, not the beginning, from any position before the end of the * last line. It also historically worked on empty files, so we * have to make it okay. */ eof: if (vp->m_start.lno == lno || vp->m_start.lno == lno - 1) { if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); vp->m_start.cno = 0; return (0); } if (vp->m_start.cno == (len ? len - 1 : 0)) { v_eof(sp, NULL); return (1); } } /* * !!! * Non-motion commands move to the end of the range, delete * and yank stay at the start. Ignore others. * * If deleting the line (which happens if deleting to EOF), then * cursor movement is to the first nonblank. */ if (ISMOTION(vp) && ISCMD(vp->rkp, 'd')) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } vp->m_stop.lno = lno - 1; vp->m_stop.cno = len ? len - 1 : 0; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_paragraphb -- [count]{ * Move backward count paragraphs. * * PUBLIC: int v_paragraphb(SCR *, VICMD *); */ int v_paragraphb(SCR *sp, VICMD *vp) { enum { P_INTEXT, P_INBLANK } pstate; size_t len; recno_t cnt, lno; CHAR_T *p; char *lp; /* * !!! * Check for SOF. The historic vi didn't complain if users hit SOF * repeatedly, unless it was part of a motion command. There is no * question but that Emerson's editor of choice was vi. * * The { command historically moved to the beginning of the first * line if invoked on the first line. * * !!! * If the starting cursor position is in the first column (backward * paragraph movements did NOT historically pay attention to non-blank * characters) i.e. the movement is cutting the entire line, the buffer * is in line mode. Cuts from the beginning of the line also did not * cut the current line, but started at the previous EOL. * * Correct for a left motion component while we're thinking about it. */ lno = vp->m_start.lno; if (ISMOTION(vp)) { if (vp->m_start.cno == 0) { if (vp->m_start.lno == 1) { v_sof(sp, &vp->m_start); return (1); } else --vp->m_start.lno; F_SET(vp, VM_LMODE); } else --vp->m_start.cno; } if (vp->m_start.lno <= 1) goto sof; /* Figure out what state we're currently in. */ if (db_get(sp, lno, 0, &p, &len)) goto sof; /* * If we start in text, we want to switch states * (2 * N - 1) times, in non-text, (2 * N) times. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt *= 2; if (len == 0 || v_isempty(p, len)) pstate = P_INBLANK; else { --cnt; pstate = P_INTEXT; /* * !!! * If the starting cursor is past the first column, * the current line is checked for a paragraph. */ if (vp->m_start.cno > 0) ++lno; } for (;;) { if (db_get(sp, --lno, 0, &p, &len)) goto sof; switch (pstate) { case P_INTEXT: INTEXT_CHECK; break; case P_INBLANK: if (len != 0 && !v_isempty(p, len)) { if (!--cnt) goto found; pstate = P_INTEXT; } break; default: abort(); } } /* SOF is a movement sink. */ sof: lno = 1; found: vp->m_stop.lno = lno; vp->m_stop.cno = 0; /* * All commands move to the end of the range. (We already * adjusted the start of the range for motion commands). */ vp->m_final = vp->m_stop; return (0); } /* * v_buildps -- * Build the paragraph command search pattern. * * PUBLIC: int v_buildps(SCR *, char *, char *); */ int v_buildps(SCR *sp, char *p_p, char *s_p) { VI_PRIVATE *vip; size_t p_len, s_len; char *p; /* * The vi paragraph command searches for either a paragraph or * section option macro. */ p_len = p_p == NULL ? 0 : strlen(p_p); s_len = s_p == NULL ? 0 : strlen(s_p); if (p_len == 0 && s_len == 0) return (0); MALLOC_RET(sp, p, p_len + s_len + 1); vip = VIP(sp); free(vip->ps); if (p_p != NULL) memmove(p, p_p, p_len + 1); if (s_p != NULL) memmove(p + p_len, s_p, s_len + 1); vip->ps = p; return (0); } diff --git a/contrib/nvi/vi/v_redraw.c b/contrib/nvi/vi/v_redraw.c index de6ed0079701..046fcf15c1b4 100644 --- a/contrib/nvi/vi/v_redraw.c +++ b/contrib/nvi/vi/v_redraw.c @@ -1,33 +1,34 @@ /*- * 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 "../common/common.h" #include "vi.h" /* * v_redraw -- ^L, ^R * Redraw the screen. * * PUBLIC: int v_redraw(SCR *, VICMD *); */ int v_redraw(SCR *sp, VICMD *vp) { + F_SET(sp, SC_SCR_REFORMAT); return (sp->gp->scr_refresh(sp, 1)); } diff --git a/usr.bin/vi/ex/version.h b/usr.bin/vi/ex/version.h index 657da969e721..1c18911cc593 100644 --- a/usr.bin/vi/ex/version.h +++ b/usr.bin/vi/ex/version.h @@ -1,2 +1 @@ - -#define VI_VERSION "2.2.0 (2020-08-01)" +#define VI_VERSION "2.2.1 (2023-09-25)" diff --git a/usr.bin/vi/options_def.h b/usr.bin/vi/options_def.h index 54dd6c20c891..cd6f5ba3eb26 100644 --- a/usr.bin/vi/options_def.h +++ b/usr.bin/vi/options_def.h @@ -1,84 +1,85 @@ -#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 +#define O_ALTNOTATION 0 +#define O_ALTWERASE 1 +#define O_AUTOINDENT 2 +#define O_AUTOPRINT 3 +#define O_AUTOWRITE 4 +#define O_BACKUP 5 +#define O_BEAUTIFY 6 +#define O_CDPATH 7 +#define O_CEDIT 8 +#define O_COLUMNS 9 +#define O_COMBINED 10 +#define O_COMMENT 11 +#define O_TMPDIR 12 +#define O_EDCOMPATIBLE 13 +#define O_ERRORBELLS 14 +#define O_ESCAPETIME 15 +#define O_EXPANDTAB 16 +#define O_EXRC 17 +#define O_EXTENDED 18 +#define O_FILEC 19 +#define O_FILEENCODING 20 +#define O_FLASH 21 +#define O_HARDTABS 22 +#define O_ICLOWER 23 +#define O_IGNORECASE 24 +#define O_INPUTENCODING 25 +#define O_KEYTIME 26 +#define O_LEFTRIGHT 27 +#define O_LINES 28 +#define O_LISP 29 +#define O_LIST 30 +#define O_LOCKFILES 31 +#define O_MAGIC 32 +#define O_MATCHCHARS 33 +#define O_MATCHTIME 34 +#define O_MESG 35 +#define O_MODELINE 36 +#define O_MSGCAT 37 +#define O_NOPRINT 38 +#define O_NUMBER 39 +#define O_OCTAL 40 +#define O_OPEN 41 +#define O_OPTIMIZE 42 +#define O_PARAGRAPHS 43 +#define O_PATH 44 +#define O_PRINT 45 +#define O_PROMPT 46 +#define O_READONLY 47 +#define O_RECDIR 48 +#define O_REDRAW 49 +#define O_REMAP 50 +#define O_REPORT 51 +#define O_RULER 52 +#define O_SCROLL 53 +#define O_SEARCHINCR 54 +#define O_SECTIONS 55 +#define O_SECURE 56 +#define O_SHELL 57 +#define O_SHELLMETA 58 +#define O_SHIFTWIDTH 59 +#define O_SHOWMATCH 60 +#define O_SHOWMODE 61 +#define O_SIDESCROLL 62 +#define O_SLOWOPEN 63 +#define O_SOURCEANY 64 +#define O_TABSTOP 65 +#define O_TAGLENGTH 66 +#define O_TAGS 67 +#define O_TERM 68 +#define O_TERSE 69 +#define O_TILDEOP 70 +#define O_TIMEOUT 71 +#define O_TTYWERASE 72 +#define O_VERBOSE 73 +#define O_W1200 74 +#define O_W300 75 +#define O_W9600 76 +#define O_WARN 77 +#define O_WINDOW 78 +#define O_WINDOWNAME 79 +#define O_WRAPLEN 80 +#define O_WRAPMARGIN 81 +#define O_WRAPSCAN 82 +#define O_WRITEANY 83 +#define O_OPTIONCOUNT 84